Compare commits

...

10 Commits

Author SHA1 Message Date
Tim van der Meij
06f44916c8
Merge pull request #19889 from Snuffleupagus/loadXfaResources
Slightly re-factor how we pre-load fonts and images in XFA documents
2025-05-04 14:28:11 +02:00
Jonas Jenwald
d9548b1c18 Slightly re-factor how we pre-load fonts and images in XFA documents
Rather than "manually" invoking the methods from the `src/core/worker.js` file we introduce a single `PDFDocument`-method that handles this for us, and make the current methods private.
Since this code is only invoked at most *once* per document, and only for XFA documents, we can use `BasePdfManager.prototype.ensureDoc` directly rather than needing a stand-alone method.
2025-05-04 13:44:33 +02:00
Jonas Jenwald
604153957a Reduce duplication when parsing fonts in loadXfaFonts
Currently we repeat virtually the same code when calling the `PartialEvaluator.prototype.handleSetFont` method, which we can avoid by introducing an inline helper function.
2025-05-04 13:42:17 +02:00
Jonas Jenwald
2979e23f3c Ensure that XFAFactory.prototype.isValid returns a boolean value
Considering the name of the method, and how it's actually being used, you'd expect it to return a boolean value.
Given how it's currently being used this inconsistency doesn't cause any issues, however we should still fix this.
2025-05-04 13:42:17 +02:00
Tim van der Meij
5ca57fbd4b
Merge pull request #19885 from Snuffleupagus/loadXfaImages-simplify
Simplify the `loadXfaImages` method and related code
2025-05-04 13:41:06 +02:00
Tim van der Meij
22cb3080ee
Merge pull request #19887 from Snuffleupagus/serializeXfaData-simplify
Simplify the `serializeXfaData` method and related code
2025-05-04 13:38:01 +02:00
Tim van der Meij
bd81d390c7
Merge pull request #19888 from Snuffleupagus/rm-BasePdfManager-catalog-getter
Remove the `BasePdfManager.prototype.catalog` getter
2025-05-04 13:33:16 +02:00
Jonas Jenwald
b3e16800f5 Remove the BasePdfManager.prototype.catalog getter
This is only invoked *once* and it can be trivially replaced by the `ensureCatalog`-method, since the code where it's used is already asynchronous.
2025-05-03 13:40:23 +02:00
Jonas Jenwald
b531720d9c Simplify the serializeXfaData method and related code
Rather than having a dedicated `BasePdfManager`-method for this one call-site we can instead change `PDFDocument.prototype.serializeXfaData` to a non-async method, that we invoke via `BasePdfManager.prototype.ensureDoc`.
2025-05-03 11:20:42 +02:00
Jonas Jenwald
122822a750 Simplify the loadXfaImages method and related code
Currently we create an intermediate `Dict` during parsing, however that seems unnecessary since (note especially the second point):
 - The `NameOrNumberTree.prototype.getAll` method will already resolve any references, as needed, during parsing.
 - The `Catalog.prototype.xfaImages` getter is invoked, via the `BasePdfManager`-instance, such that any `MissingDataException`s are already handled correctly.
2025-05-02 11:53:41 +02:00
7 changed files with 59 additions and 94 deletions

View File

@ -1062,11 +1062,13 @@ class Catalog {
if (obj instanceof Dict && obj.has("XFAImages")) {
const nameTree = new NameTree(obj.getRaw("XFAImages"), this.xref);
for (const [key, value] of nameTree.getAll()) {
xfaImages ??= new Dict(this.xref);
xfaImages.set(
stringToPDFString(key, /* keepEscapeSequence = */ true),
value
);
if (value instanceof BaseStream) {
xfaImages ??= new Map();
xfaImages.set(
stringToPDFString(key, /* keepEscapeSequence = */ true),
value.getBytes()
);
}
}
}
return shadow(this, "xfaImages", xfaImages);

View File

@ -1237,28 +1237,15 @@ class PDFDocument {
return this.xfaFactory ? this.xfaFactory.getPages() : null;
}
async loadXfaImages() {
const xfaImagesDict = await this.pdfManager.ensureCatalog("xfaImages");
if (!xfaImagesDict) {
async #loadXfaImages() {
const xfaImages = await this.pdfManager.ensureCatalog("xfaImages");
if (!xfaImages) {
return;
}
const keys = xfaImagesDict.getKeys();
const objectLoader = new ObjectLoader(xfaImagesDict, keys, this.xref);
await objectLoader.load();
const xfaImages = new Map();
for (const key of keys) {
const stream = xfaImagesDict.get(key);
if (stream instanceof BaseStream) {
xfaImages.set(key, stream.getBytes());
}
}
this.xfaFactory.setImages(xfaImages);
}
async loadXfaFonts(handler, task) {
async #loadXfaFonts(handler, task) {
const acroForm = await this.pdfManager.ensureCatalog("acroForm");
if (!acroForm) {
return;
@ -1277,18 +1264,19 @@ class PDFDocument {
const options = Object.assign(
Object.create(null),
this.pdfManager.evaluatorOptions
this.pdfManager.evaluatorOptions,
{ useSystemFonts: false }
);
options.useSystemFonts = false;
const { builtInCMapCache, fontCache, standardFontDataCache } = this.catalog;
const partialEvaluator = new PartialEvaluator({
xref: this.xref,
handler,
pageIndex: -1,
idFactory: this._globalIdFactory,
fontCache: this.catalog.fontCache,
builtInCMapCache: this.catalog.builtInCMapCache,
standardFontDataCache: this.catalog.standardFontDataCache,
fontCache,
builtInCMapCache,
standardFontDataCache,
options,
});
const operatorList = new OperatorList();
@ -1305,6 +1293,23 @@ class PDFDocument {
},
};
const parseFont = (fontName, fallbackFontDict, cssFontInfo) =>
partialEvaluator
.handleSetFont(
resources,
[Name.get(fontName), 1],
/* fontRef = */ null,
operatorList,
task,
initialState,
fallbackFontDict,
cssFontInfo
)
.catch(reason => {
warn(`loadXfaFonts: "${reason}".`);
return null;
});
const promises = [];
for (const [fontName, font] of fontRes) {
const descriptor = font.get("FontDescriptor");
@ -1326,21 +1331,7 @@ class PDFDocument {
continue;
}
promises.push(
partialEvaluator
.handleSetFont(
resources,
[Name.get(fontName), 1],
/* fontRef = */ null,
operatorList,
task,
initialState,
/* fallbackFontDict = */ null,
/* cssFontInfo = */ cssFontInfo
)
.catch(function (reason) {
warn(`loadXfaFonts: "${reason}".`);
return null;
})
parseFont(fontName, /* fallbackFontDict = */ null, cssFontInfo)
);
}
@ -1378,28 +1369,13 @@ class PDFDocument {
{ name: "BoldItalic", fontWeight: 700, italicAngle: 12 },
]) {
const name = `${missing}-${fontInfo.name}`;
const dict = getXfaFontDict(name);
promises.push(
partialEvaluator
.handleSetFont(
resources,
[Name.get(name), 1],
/* fontRef = */ null,
operatorList,
task,
initialState,
/* fallbackFontDict = */ dict,
/* cssFontInfo = */ {
fontFamily: missing,
fontWeight: fontInfo.fontWeight,
italicAngle: fontInfo.italicAngle,
}
)
.catch(function (reason) {
warn(`loadXfaFonts: "${reason}".`);
return null;
})
parseFont(name, getXfaFontDict(name), {
fontFamily: missing,
fontWeight: fontInfo.fontWeight,
italicAngle: fontInfo.italicAngle,
})
);
}
}
@ -1408,7 +1384,16 @@ class PDFDocument {
this.xfaFactory.appendFonts(pdfFonts, reallyMissingFonts);
}
async serializeXfaData(annotationStorage) {
loadXfaResources(handler, task) {
return Promise.all([
this.#loadXfaFonts(handler, task).catch(() => {
// Ignore errors, to allow the document to load.
}),
this.#loadXfaImages(),
]);
}
serializeXfaData(annotationStorage) {
return this.xfaFactory
? this.xfaFactory.serializeData(annotationStorage)
: null;

View File

@ -95,10 +95,6 @@ class BasePdfManager {
return this._docBaseUrl;
}
get catalog() {
return this.pdfDocument.catalog;
}
ensureDoc(prop, args) {
return this.ensure(this.pdfDocument, prop, args);
}
@ -119,18 +115,6 @@ class BasePdfManager {
return this.pdfDocument.fontFallback(id, handler);
}
loadXfaFonts(handler, task) {
return this.pdfDocument.loadXfaFonts(handler, task);
}
loadXfaImages() {
return this.pdfDocument.loadXfaImages();
}
serializeXfaData(annotationStorage) {
return this.pdfDocument.serializeXfaData(annotationStorage);
}
cleanup(manuallyTriggered = false) {
return this.pdfDocument.cleanup(manuallyTriggered);
}

View File

@ -118,7 +118,7 @@ class StructTreeRoot {
pdfManager,
changes,
}) {
const root = pdfManager.catalog.cloneDict();
const root = await pdfManager.ensureCatalog("cloneDict");
const cache = new RefSetCache();
cache.put(catalogRef, root);

View File

@ -170,18 +170,11 @@ class WorkerMessageHandler {
const isPureXfa = await pdfManager.ensureDoc("isPureXfa");
if (isPureXfa) {
const task = new WorkerTask("loadXfaFonts");
const task = new WorkerTask("loadXfaResources");
startWorkerTask(task);
await Promise.all([
pdfManager
.loadXfaFonts(handler, task)
.catch(reason => {
// Ignore errors, to allow the document to load.
})
.then(() => finishWorkerTask(task)),
pdfManager.loadXfaImages(),
]);
await pdfManager.ensureDoc("loadXfaResources", [handler, task]);
finishWorkerTask(task);
}
const [numPages, fingerprints] = await Promise.all([
@ -617,7 +610,9 @@ class WorkerMessageHandler {
}
if (isPureXfa) {
promises.push(pdfManager.serializeXfaData(annotationStorage));
promises.push(
pdfManager.ensureDoc("serializeXfaData", [annotationStorage])
);
} else {
for (let pageIndex = 0; pageIndex < numPages; pageIndex++) {
promises.push(

View File

@ -43,7 +43,7 @@ class XFAFactory {
}
isValid() {
return this.root && this.form;
return !!(this.root && this.form);
}
/**

View File

@ -3412,8 +3412,7 @@ class Image extends StringObject {
return HTMLResult.EMPTY;
}
let buffer =
this[$globalData].images && this[$globalData].images.get(this.href);
let buffer = this[$globalData].images?.get(this.href);
if (!buffer && (this.href || !this[$content])) {
// In general, we don't get remote data and use what we have
// in the pdf itself, so no picture for non null href.