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,
|
GlobalWorkerOptions,
|
||||||
PixelsPerInch,
|
PixelsPerInch,
|
||||||
renderTextLayer,
|
renderTextLayer,
|
||||||
|
shadow,
|
||||||
XfaLayer,
|
XfaLayer,
|
||||||
} = pdfjsLib;
|
} = pdfjsLib;
|
||||||
const { SimpleLinkService } = pdfjsViewer;
|
const { SimpleLinkService } = pdfjsViewer;
|
||||||
@ -37,26 +38,25 @@ const RENDER_TASK_ON_CONTINUE_DELAY = 5; // ms
|
|||||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
function loadStyles(styles) {
|
function loadStyles(styles) {
|
||||||
styles = Object.values(styles);
|
const promises = [];
|
||||||
if (styles.every(style => style.promise)) {
|
|
||||||
return Promise.all(styles.map(style => style.promise));
|
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) {
|
return Promise.all(promises);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeSVG(svgElement, ctx) {
|
function writeSVG(svgElement, ctx) {
|
||||||
@ -144,98 +144,38 @@ async function resolveImages(node, silentErrors = false) {
|
|||||||
await Promise.all(loadedPromises);
|
await Promise.all(loadedPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
class Rasterize {
|
||||||
* @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() {
|
|
||||||
/**
|
/**
|
||||||
* For the reference tests, the entire annotation layer must be visible. To
|
* For the reference tests, the full content of the various layers must be
|
||||||
* achieve this, we load the common styles as used by the viewer and extend
|
* visible. To achieve this, we load the common styles as used by the viewer
|
||||||
* them with a set of overrides to make all elements visible.
|
* 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
|
* 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
|
* the overrides file because the browser does not resolve that when the
|
||||||
* styles are inserted via XHR. Therefore, we load and combine them here.
|
* styles are inserted via XHR. Therefore, we load and combine them here.
|
||||||
*/
|
*/
|
||||||
const styles = {
|
static get annotationStylePromise() {
|
||||||
common: {
|
const styles = [
|
||||||
file: "../web/annotation_layer_builder.css",
|
"../web/annotation_layer_builder.css",
|
||||||
promise: null,
|
"./annotation_layer_builder_overrides.css",
|
||||||
},
|
];
|
||||||
overrides: {
|
return shadow(this, "annotationStylePromise", loadStyles(styles));
|
||||||
file: "./annotation_layer_builder_overrides.css",
|
}
|
||||||
promise: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-shadow
|
static get textStylePromise() {
|
||||||
async function rasterizeAnnotationLayer(
|
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,
|
ctx,
|
||||||
viewport,
|
viewport,
|
||||||
annotations,
|
annotations,
|
||||||
@ -260,13 +200,12 @@ const rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
|
|||||||
foreignObject.setAttribute("width", viewport.width + "px");
|
foreignObject.setAttribute("width", viewport.width + "px");
|
||||||
foreignObject.setAttribute("height", viewport.height + "px");
|
foreignObject.setAttribute("height", viewport.height + "px");
|
||||||
const style = document.createElement("style");
|
const style = document.createElement("style");
|
||||||
const stylePromise = loadStyles(styles);
|
|
||||||
foreignObject.appendChild(style);
|
foreignObject.appendChild(style);
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.className = "annotationLayer";
|
div.className = "annotationLayer";
|
||||||
|
|
||||||
// Rendering annotation layer as HTML.
|
// Rendering annotation layer as HTML.
|
||||||
const [common, overrides] = await stylePromise;
|
const [common, overrides] = await this.annotationStylePromise;
|
||||||
style.textContent = common + "\n" + overrides;
|
style.textContent = common + "\n" + overrides;
|
||||||
|
|
||||||
const annotation_viewport = viewport.clone({ dontFlip: true });
|
const annotation_viewport = viewport.clone({ dontFlip: true });
|
||||||
@ -293,30 +232,56 @@ const rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
|
|||||||
|
|
||||||
await writeSVG(svg, ctx);
|
await writeSVG(svg, ctx);
|
||||||
} catch (reason) {
|
} 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);
|
||||||
|
|
||||||
/**
|
// Adding element to host our HTML (style + text layer div).
|
||||||
* @class
|
const foreignObject = document.createElementNS(
|
||||||
*/
|
SVG_NS,
|
||||||
const rasterizeXfaLayer = (function rasterizeXfaLayerClosure() {
|
"svg:foreignObject"
|
||||||
const styles = {
|
);
|
||||||
common: {
|
foreignObject.setAttribute("x", "0");
|
||||||
file: "../web/xfa_layer_builder.css",
|
foreignObject.setAttribute("y", "0");
|
||||||
promise: null,
|
foreignObject.setAttribute("width", viewport.width + "px");
|
||||||
},
|
foreignObject.setAttribute("height", viewport.height + "px");
|
||||||
overrides: {
|
const style = document.createElement("style");
|
||||||
file: "./xfa_layer_builder_overrides.css",
|
foreignObject.appendChild(style);
|
||||||
promise: null,
|
const div = document.createElement("div");
|
||||||
},
|
div.className = "textLayer";
|
||||||
};
|
foreignObject.appendChild(div);
|
||||||
|
|
||||||
// eslint-disable-next-line no-shadow
|
const [cssRules] = await this.textStylePromise;
|
||||||
async function rasterizeXfaLayer(
|
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,
|
ctx,
|
||||||
viewport,
|
viewport,
|
||||||
xfa,
|
xfa,
|
||||||
@ -338,12 +303,11 @@ const rasterizeXfaLayer = (function rasterizeXfaLayerClosure() {
|
|||||||
foreignObject.setAttribute("width", viewport.width + "px");
|
foreignObject.setAttribute("width", viewport.width + "px");
|
||||||
foreignObject.setAttribute("height", viewport.height + "px");
|
foreignObject.setAttribute("height", viewport.height + "px");
|
||||||
const style = document.createElement("style");
|
const style = document.createElement("style");
|
||||||
const stylePromise = loadStyles(styles);
|
|
||||||
foreignObject.appendChild(style);
|
foreignObject.appendChild(style);
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
foreignObject.appendChild(div);
|
foreignObject.appendChild(div);
|
||||||
|
|
||||||
const [common, overrides] = await stylePromise;
|
const [common, overrides] = await this.xfaStylePromise;
|
||||||
style.textContent = fontRules + "\n" + common + "\n" + overrides;
|
style.textContent = fontRules + "\n" + common + "\n" + overrides;
|
||||||
|
|
||||||
XfaLayer.render({
|
XfaLayer.render({
|
||||||
@ -362,12 +326,10 @@ const rasterizeXfaLayer = (function rasterizeXfaLayerClosure() {
|
|||||||
|
|
||||||
await writeSVG(svg, ctx);
|
await writeSVG(svg, ctx);
|
||||||
} catch (reason) {
|
} catch (reason) {
|
||||||
throw new Error(`rasterizeXfaLayer: "${reason?.message}".`);
|
throw new Error(`Rasterize.xfaLayer: "${reason?.message}".`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return rasterizeXfaLayer;
|
|
||||||
})();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} DriverOptions
|
* @typedef {Object} DriverOptions
|
||||||
@ -713,7 +675,7 @@ class Driver {
|
|||||||
includeMarkedContent: true,
|
includeMarkedContent: true,
|
||||||
})
|
})
|
||||||
.then(function (textContent) {
|
.then(function (textContent) {
|
||||||
return rasterizeTextLayer(
|
return Rasterize.textLayer(
|
||||||
textLayerContext,
|
textLayerContext,
|
||||||
viewport,
|
viewport,
|
||||||
textContent,
|
textContent,
|
||||||
@ -754,7 +716,7 @@ class Driver {
|
|||||||
annotationCanvasMap = new Map();
|
annotationCanvasMap = new Map();
|
||||||
} else {
|
} else {
|
||||||
initPromise = page.getXfa().then(function (xfa) {
|
initPromise = page.getXfa().then(function (xfa) {
|
||||||
return rasterizeXfaLayer(
|
return Rasterize.xfaLayer(
|
||||||
annotationLayerContext,
|
annotationLayerContext,
|
||||||
viewport,
|
viewport,
|
||||||
xfa,
|
xfa,
|
||||||
@ -817,7 +779,7 @@ class Driver {
|
|||||||
}
|
}
|
||||||
return renderTask.promise.then(function () {
|
return renderTask.promise.then(function () {
|
||||||
if (annotationCanvasMap) {
|
if (annotationCanvasMap) {
|
||||||
rasterizeAnnotationLayer(
|
Rasterize.annotationLayer(
|
||||||
annotationLayerContext,
|
annotationLayerContext,
|
||||||
viewport,
|
viewport,
|
||||||
data,
|
data,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user