Move the rasterization logic into one single class
This refactoring ensures that we can get rid of the closures and encapsulate the logic in a nicer way with e.g., getters for the style promises.
This commit is contained in:
parent
33dc0628a0
commit
03506f25c0
228
test/driver.js
228
test/driver.js
@ -23,6 +23,7 @@ const {
|
||||
GlobalWorkerOptions,
|
||||
PixelsPerInch,
|
||||
renderTextLayer,
|
||||
shadow,
|
||||
XfaLayer,
|
||||
} = pdfjsLib;
|
||||
const { SimpleLinkService } = pdfjsViewer;
|
||||
@ -37,26 +38,25 @@ const RENDER_TASK_ON_CONTINUE_DELAY = 5; // ms
|
||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
|
||||
function loadStyles(styles) {
|
||||
styles = Object.values(styles);
|
||||
if (styles.every(style => style.promise)) {
|
||||
return Promise.all(styles.map(style => style.promise));
|
||||
const promises = [];
|
||||
|
||||
for (const file of styles) {
|
||||
promises.push(
|
||||
new Promise(function (resolve, reject) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", file);
|
||||
xhr.onload = function () {
|
||||
resolve(xhr.responseText);
|
||||
};
|
||||
xhr.onerror = function (e) {
|
||||
reject(new Error(`Error fetching style (${file}): ${e}`));
|
||||
};
|
||||
xhr.send(null);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
for (const style of styles) {
|
||||
style.promise = new Promise(function (resolve, reject) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", style.file);
|
||||
xhr.onload = function () {
|
||||
resolve(xhr.responseText);
|
||||
};
|
||||
xhr.onerror = function (e) {
|
||||
reject(new Error(`Error fetching style (${style.file}): ${e}`));
|
||||
};
|
||||
xhr.send(null);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.all(styles.map(style => style.promise));
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
function writeSVG(svgElement, ctx) {
|
||||
@ -144,98 +144,38 @@ async function resolveImages(node, silentErrors = false) {
|
||||
await Promise.all(loadedPromises);
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
*/
|
||||
const rasterizeTextLayer = (function rasterizeTextLayerClosure() {
|
||||
const styles = {
|
||||
common: {
|
||||
file: "./text_layer_test.css",
|
||||
promise: null,
|
||||
},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
async function rasterizeTextLayer(
|
||||
ctx,
|
||||
viewport,
|
||||
textContent,
|
||||
enhanceTextSelection
|
||||
) {
|
||||
try {
|
||||
// Building SVG with size of the viewport.
|
||||
const svg = document.createElementNS(SVG_NS, "svg:svg");
|
||||
svg.setAttribute("width", viewport.width + "px");
|
||||
svg.setAttribute("height", viewport.height + "px");
|
||||
// items are transformed to have 1px font size
|
||||
svg.setAttribute("font-size", 1);
|
||||
|
||||
// Adding element to host our HTML (style + text layer div).
|
||||
const foreignObject = document.createElementNS(
|
||||
SVG_NS,
|
||||
"svg:foreignObject"
|
||||
);
|
||||
foreignObject.setAttribute("x", "0");
|
||||
foreignObject.setAttribute("y", "0");
|
||||
foreignObject.setAttribute("width", viewport.width + "px");
|
||||
foreignObject.setAttribute("height", viewport.height + "px");
|
||||
const style = document.createElement("style");
|
||||
const stylePromise = loadStyles(styles);
|
||||
foreignObject.appendChild(style);
|
||||
const div = document.createElement("div");
|
||||
div.className = "textLayer";
|
||||
foreignObject.appendChild(div);
|
||||
|
||||
const [cssRules] = await stylePromise;
|
||||
style.textContent = cssRules;
|
||||
|
||||
// Rendering text layer as HTML.
|
||||
const task = renderTextLayer({
|
||||
textContent,
|
||||
container: div,
|
||||
viewport,
|
||||
enhanceTextSelection,
|
||||
});
|
||||
await task.promise;
|
||||
|
||||
task.expandTextDivs(true);
|
||||
svg.appendChild(foreignObject);
|
||||
|
||||
await writeSVG(svg, ctx);
|
||||
} catch (reason) {
|
||||
throw new Error(`rasterizeTextLayer: "${reason?.message}".`);
|
||||
}
|
||||
}
|
||||
|
||||
return rasterizeTextLayer;
|
||||
})();
|
||||
|
||||
/**
|
||||
* @class
|
||||
*/
|
||||
const rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
|
||||
class Rasterize {
|
||||
/**
|
||||
* For the reference tests, the entire annotation layer must be visible. To
|
||||
* achieve this, we load the common styles as used by the viewer and extend
|
||||
* them with a set of overrides to make all elements visible.
|
||||
* For the reference tests, the full content of the various layers must be
|
||||
* visible. To achieve this, we load the common styles as used by the viewer
|
||||
* and extend them with a set of overrides to make all elements visible.
|
||||
*
|
||||
* Note that we cannot simply use `@import` to import the common styles in
|
||||
* the overrides file because the browser does not resolve that when the
|
||||
* styles are inserted via XHR. Therefore, we load and combine them here.
|
||||
*/
|
||||
const styles = {
|
||||
common: {
|
||||
file: "../web/annotation_layer_builder.css",
|
||||
promise: null,
|
||||
},
|
||||
overrides: {
|
||||
file: "./annotation_layer_builder_overrides.css",
|
||||
promise: null,
|
||||
},
|
||||
};
|
||||
static get annotationStylePromise() {
|
||||
const styles = [
|
||||
"../web/annotation_layer_builder.css",
|
||||
"./annotation_layer_builder_overrides.css",
|
||||
];
|
||||
return shadow(this, "annotationStylePromise", loadStyles(styles));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
async function rasterizeAnnotationLayer(
|
||||
static get textStylePromise() {
|
||||
const styles = ["./text_layer_test.css"];
|
||||
return shadow(this, "textStylePromise", loadStyles(styles));
|
||||
}
|
||||
|
||||
static get xfaStylePromise() {
|
||||
const styles = [
|
||||
"../web/xfa_layer_builder.css",
|
||||
"./xfa_layer_builder_overrides.css",
|
||||
];
|
||||
return shadow(this, "xfaStylePromise", loadStyles(styles));
|
||||
}
|
||||
|
||||
static async annotationLayer(
|
||||
ctx,
|
||||
viewport,
|
||||
annotations,
|
||||
@ -260,13 +200,12 @@ const rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
|
||||
foreignObject.setAttribute("width", viewport.width + "px");
|
||||
foreignObject.setAttribute("height", viewport.height + "px");
|
||||
const style = document.createElement("style");
|
||||
const stylePromise = loadStyles(styles);
|
||||
foreignObject.appendChild(style);
|
||||
const div = document.createElement("div");
|
||||
div.className = "annotationLayer";
|
||||
|
||||
// Rendering annotation layer as HTML.
|
||||
const [common, overrides] = await stylePromise;
|
||||
const [common, overrides] = await this.annotationStylePromise;
|
||||
style.textContent = common + "\n" + overrides;
|
||||
|
||||
const annotation_viewport = viewport.clone({ dontFlip: true });
|
||||
@ -293,30 +232,56 @@ const rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
|
||||
|
||||
await writeSVG(svg, ctx);
|
||||
} catch (reason) {
|
||||
throw new Error(`rasterizeAnnotationLayer: "${reason?.message}".`);
|
||||
throw new Error(`Rasterize.annotationLayer: "${reason?.message}".`);
|
||||
}
|
||||
}
|
||||
|
||||
return rasterizeAnnotationLayer;
|
||||
})();
|
||||
static async textLayer(ctx, viewport, textContent, enhanceTextSelection) {
|
||||
try {
|
||||
// Building SVG with size of the viewport.
|
||||
const svg = document.createElementNS(SVG_NS, "svg:svg");
|
||||
svg.setAttribute("width", viewport.width + "px");
|
||||
svg.setAttribute("height", viewport.height + "px");
|
||||
// items are transformed to have 1px font size
|
||||
svg.setAttribute("font-size", 1);
|
||||
|
||||
/**
|
||||
* @class
|
||||
*/
|
||||
const rasterizeXfaLayer = (function rasterizeXfaLayerClosure() {
|
||||
const styles = {
|
||||
common: {
|
||||
file: "../web/xfa_layer_builder.css",
|
||||
promise: null,
|
||||
},
|
||||
overrides: {
|
||||
file: "./xfa_layer_builder_overrides.css",
|
||||
promise: null,
|
||||
},
|
||||
};
|
||||
// Adding element to host our HTML (style + text layer div).
|
||||
const foreignObject = document.createElementNS(
|
||||
SVG_NS,
|
||||
"svg:foreignObject"
|
||||
);
|
||||
foreignObject.setAttribute("x", "0");
|
||||
foreignObject.setAttribute("y", "0");
|
||||
foreignObject.setAttribute("width", viewport.width + "px");
|
||||
foreignObject.setAttribute("height", viewport.height + "px");
|
||||
const style = document.createElement("style");
|
||||
foreignObject.appendChild(style);
|
||||
const div = document.createElement("div");
|
||||
div.className = "textLayer";
|
||||
foreignObject.appendChild(div);
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
async function rasterizeXfaLayer(
|
||||
const [cssRules] = await this.textStylePromise;
|
||||
style.textContent = cssRules;
|
||||
|
||||
// Rendering text layer as HTML.
|
||||
const task = renderTextLayer({
|
||||
textContent,
|
||||
container: div,
|
||||
viewport,
|
||||
enhanceTextSelection,
|
||||
});
|
||||
await task.promise;
|
||||
|
||||
task.expandTextDivs(true);
|
||||
svg.appendChild(foreignObject);
|
||||
|
||||
await writeSVG(svg, ctx);
|
||||
} catch (reason) {
|
||||
throw new Error(`Rasterize.textLayer: "${reason?.message}".`);
|
||||
}
|
||||
}
|
||||
|
||||
static async xfaLayer(
|
||||
ctx,
|
||||
viewport,
|
||||
xfa,
|
||||
@ -338,12 +303,11 @@ const rasterizeXfaLayer = (function rasterizeXfaLayerClosure() {
|
||||
foreignObject.setAttribute("width", viewport.width + "px");
|
||||
foreignObject.setAttribute("height", viewport.height + "px");
|
||||
const style = document.createElement("style");
|
||||
const stylePromise = loadStyles(styles);
|
||||
foreignObject.appendChild(style);
|
||||
const div = document.createElement("div");
|
||||
foreignObject.appendChild(div);
|
||||
|
||||
const [common, overrides] = await stylePromise;
|
||||
const [common, overrides] = await this.xfaStylePromise;
|
||||
style.textContent = fontRules + "\n" + common + "\n" + overrides;
|
||||
|
||||
XfaLayer.render({
|
||||
@ -362,12 +326,10 @@ const rasterizeXfaLayer = (function rasterizeXfaLayerClosure() {
|
||||
|
||||
await writeSVG(svg, ctx);
|
||||
} catch (reason) {
|
||||
throw new Error(`rasterizeXfaLayer: "${reason?.message}".`);
|
||||
throw new Error(`Rasterize.xfaLayer: "${reason?.message}".`);
|
||||
}
|
||||
}
|
||||
|
||||
return rasterizeXfaLayer;
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} DriverOptions
|
||||
@ -713,7 +675,7 @@ class Driver {
|
||||
includeMarkedContent: true,
|
||||
})
|
||||
.then(function (textContent) {
|
||||
return rasterizeTextLayer(
|
||||
return Rasterize.textLayer(
|
||||
textLayerContext,
|
||||
viewport,
|
||||
textContent,
|
||||
@ -754,7 +716,7 @@ class Driver {
|
||||
annotationCanvasMap = new Map();
|
||||
} else {
|
||||
initPromise = page.getXfa().then(function (xfa) {
|
||||
return rasterizeXfaLayer(
|
||||
return Rasterize.xfaLayer(
|
||||
annotationLayerContext,
|
||||
viewport,
|
||||
xfa,
|
||||
@ -817,7 +779,7 @@ class Driver {
|
||||
}
|
||||
return renderTask.promise.then(function () {
|
||||
if (annotationCanvasMap) {
|
||||
rasterizeAnnotationLayer(
|
||||
Rasterize.annotationLayer(
|
||||
annotationLayerContext,
|
||||
viewport,
|
||||
data,
|
||||
|
Loading…
x
Reference in New Issue
Block a user