Merge pull request #14247 from calixteman/button

[api-minor] Render pushbuttons on their own canvas (bug 1737260)
This commit is contained in:
Brendan Dahl 2021-11-16 08:10:40 -08:00 committed by GitHub
commit 3209c013c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 333 additions and 86 deletions

View File

@ -26,6 +26,7 @@ import {
isAscii, isAscii,
isString, isString,
OPS, OPS,
RenderingIntentFlag,
shadow, shadow,
stringToPDFString, stringToPDFString,
stringToUTF16BEString, stringToUTF16BEString,
@ -386,6 +387,7 @@ class Annotation {
modificationDate: this.modificationDate, modificationDate: this.modificationDate,
rect: this.rectangle, rect: this.rectangle,
subtype: params.subtype, subtype: params.subtype,
hasOwnCanvas: false,
}; };
if (params.collectFields) { if (params.collectFields) {
@ -708,8 +710,8 @@ class Annotation {
this.appearance = normalAppearanceState.get(as.name); this.appearance = normalAppearanceState.get(as.name);
} }
loadResources(keys) { loadResources(keys, appearance) {
return this.appearance.dict.getAsync("Resources").then(resources => { return appearance.dict.getAsync("Resources").then(resources => {
if (!resources) { if (!resources) {
return undefined; return undefined;
} }
@ -721,22 +723,24 @@ class Annotation {
}); });
} }
getOperatorList(evaluator, task, renderForms, annotationStorage) { getOperatorList(evaluator, task, intent, renderForms, annotationStorage) {
if (!this.appearance) { const data = this.data;
return Promise.resolve(new OperatorList()); let appearance = this.appearance;
const isUsingOwnCanvas =
data.hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY;
if (!appearance) {
if (!isUsingOwnCanvas) {
return Promise.resolve(new OperatorList());
}
appearance = new StringStream("");
appearance.dict = new Dict();
} }
const appearance = this.appearance;
const data = this.data;
const appearanceDict = appearance.dict; const appearanceDict = appearance.dict;
const resourcesPromise = this.loadResources([ const resourcesPromise = this.loadResources(
"ExtGState", ["ExtGState", "ColorSpace", "Pattern", "Shading", "XObject", "Font"],
"ColorSpace", appearance
"Pattern", );
"Shading",
"XObject",
"Font",
]);
const bbox = appearanceDict.getArray("BBox") || [0, 0, 1, 1]; const bbox = appearanceDict.getArray("BBox") || [0, 0, 1, 1];
const matrix = appearanceDict.getArray("Matrix") || [1, 0, 0, 1, 0, 0]; const matrix = appearanceDict.getArray("Matrix") || [1, 0, 0, 1, 0, 0];
const transform = getTransformMatrix(data.rect, bbox, matrix); const transform = getTransformMatrix(data.rect, bbox, matrix);
@ -748,6 +752,7 @@ class Annotation {
data.rect, data.rect,
transform, transform,
matrix, matrix,
isUsingOwnCanvas,
]); ]);
return evaluator return evaluator
@ -1329,7 +1334,7 @@ class WidgetAnnotation extends Annotation {
return !!(this.data.fieldFlags & flag); return !!(this.data.fieldFlags & flag);
} }
getOperatorList(evaluator, task, renderForms, annotationStorage) { getOperatorList(evaluator, task, intent, renderForms, annotationStorage) {
// Do not render form elements on the canvas when interactive forms are // Do not render form elements on the canvas when interactive forms are
// enabled. The display layer is responsible for rendering them instead. // enabled. The display layer is responsible for rendering them instead.
if (renderForms && !(this instanceof SignatureWidgetAnnotation)) { if (renderForms && !(this instanceof SignatureWidgetAnnotation)) {
@ -1340,6 +1345,7 @@ class WidgetAnnotation extends Annotation {
return super.getOperatorList( return super.getOperatorList(
evaluator, evaluator,
task, task,
intent,
renderForms, renderForms,
annotationStorage annotationStorage
); );
@ -1351,6 +1357,7 @@ class WidgetAnnotation extends Annotation {
return super.getOperatorList( return super.getOperatorList(
evaluator, evaluator,
task, task,
intent,
renderForms, renderForms,
annotationStorage annotationStorage
); );
@ -1936,17 +1943,25 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
} else if (this.data.radioButton) { } else if (this.data.radioButton) {
this._processRadioButton(params); this._processRadioButton(params);
} else if (this.data.pushButton) { } else if (this.data.pushButton) {
this.data.hasOwnCanvas = true;
this._processPushButton(params); this._processPushButton(params);
} else { } else {
warn("Invalid field flags for button widget annotation"); warn("Invalid field flags for button widget annotation");
} }
} }
async getOperatorList(evaluator, task, renderForms, annotationStorage) { async getOperatorList(
evaluator,
task,
intent,
renderForms,
annotationStorage
) {
if (this.data.pushButton) { if (this.data.pushButton) {
return super.getOperatorList( return super.getOperatorList(
evaluator, evaluator,
task, task,
intent,
false, // we use normalAppearance to render the button false, // we use normalAppearance to render the button
annotationStorage annotationStorage
); );
@ -1965,6 +1980,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
return super.getOperatorList( return super.getOperatorList(
evaluator, evaluator,
task, task,
intent,
renderForms, renderForms,
annotationStorage annotationStorage
); );
@ -1988,6 +2004,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
const operatorList = super.getOperatorList( const operatorList = super.getOperatorList(
evaluator, evaluator,
task, task,
intent,
renderForms, renderForms,
annotationStorage annotationStorage
); );

View File

@ -407,6 +407,7 @@ class Page {
.getOperatorList( .getOperatorList(
partialEvaluator, partialEvaluator,
task, task,
intent,
renderForms, renderForms,
annotationStorage annotationStorage
) )

View File

@ -198,7 +198,25 @@ class AnnotationElement {
page.view[3] - data.rect[3] + page.view[1], page.view[3] - data.rect[3] + page.view[1],
]); ]);
container.style.transform = `matrix(${viewport.transform.join(",")})`; if (data.hasOwnCanvas) {
const transform = viewport.transform.slice();
const [scaleX, scaleY] = Util.singularValueDecompose2dScale(transform);
width = Math.ceil(width * scaleX);
height = Math.ceil(height * scaleY);
rect[0] *= scaleX;
rect[1] *= scaleY;
// Reset the scale part of the transform matrix (which must be diagonal
// or anti-diagonal) in order to avoid to rescale the canvas.
// The canvas for the annotation is correctly scaled when it is drawn
// (see `beginAnnotation` in canvas.js).
for (let i = 0; i < 4; i++) {
transform[i] = Math.sign(transform[i]);
}
container.style.transform = `matrix(${transform.join(",")})`;
} else {
container.style.transform = `matrix(${viewport.transform.join(",")})`;
}
container.style.transformOrigin = `${-rect[0]}px ${-rect[1]}px`; container.style.transformOrigin = `${-rect[0]}px ${-rect[1]}px`;
if (!ignoreBorder && data.borderStyle.width > 0) { if (!ignoreBorder && data.borderStyle.width > 0) {
@ -258,8 +276,13 @@ class AnnotationElement {
container.style.left = `${rect[0]}px`; container.style.left = `${rect[0]}px`;
container.style.top = `${rect[1]}px`; container.style.top = `${rect[1]}px`;
container.style.width = `${width}px`;
container.style.height = `${height}px`; if (data.hasOwnCanvas) {
container.style.width = container.style.height = "auto";
} else {
container.style.width = `${width}px`;
container.style.height = `${height}px`;
}
return container; return container;
} }
@ -2318,10 +2341,12 @@ class AnnotationLayer {
sortedAnnotations.push(...popupAnnotations); sortedAnnotations.push(...popupAnnotations);
} }
const div = parameters.div;
for (const data of sortedAnnotations) { for (const data of sortedAnnotations) {
const element = AnnotationElementFactory.create({ const element = AnnotationElementFactory.create({
data, data,
layer: parameters.div, layer: div,
page: parameters.page, page: parameters.page,
viewport: parameters.viewport, viewport: parameters.viewport,
linkService: parameters.linkService, linkService: parameters.linkService,
@ -2343,19 +2368,21 @@ class AnnotationLayer {
} }
if (Array.isArray(rendered)) { if (Array.isArray(rendered)) {
for (const renderedElement of rendered) { for (const renderedElement of rendered) {
parameters.div.appendChild(renderedElement); div.appendChild(renderedElement);
} }
} else { } else {
if (element instanceof PopupAnnotationElement) { if (element instanceof PopupAnnotationElement) {
// Popup annotation elements should not be on top of other // Popup annotation elements should not be on top of other
// annotation elements to prevent interfering with mouse events. // annotation elements to prevent interfering with mouse events.
parameters.div.prepend(rendered); div.prepend(rendered);
} else { } else {
parameters.div.appendChild(rendered); div.appendChild(rendered);
} }
} }
} }
} }
this.#setAnnotationCanvasMap(div, parameters.annotationCanvasMap);
} }
/** /**
@ -2366,18 +2393,73 @@ class AnnotationLayer {
* @memberof AnnotationLayer * @memberof AnnotationLayer
*/ */
static update(parameters) { static update(parameters) {
const transform = `matrix(${parameters.viewport.transform.join(",")})`; const { page, viewport, annotations, annotationCanvasMap, div } =
for (const data of parameters.annotations) { parameters;
const elements = parameters.div.querySelectorAll( const transform = viewport.transform;
const matrix = `matrix(${transform.join(",")})`;
let scale, ownMatrix;
for (const data of annotations) {
const elements = div.querySelectorAll(
`[data-annotation-id="${data.id}"]` `[data-annotation-id="${data.id}"]`
); );
if (elements) { if (elements) {
for (const element of elements) { for (const element of elements) {
element.style.transform = transform; if (data.hasOwnCanvas) {
const rect = Util.normalizeRect([
data.rect[0],
page.view[3] - data.rect[1] + page.view[1],
data.rect[2],
page.view[3] - data.rect[3] + page.view[1],
]);
if (!ownMatrix) {
// When an annotation has its own canvas, then
// the scale has been already applied to the canvas,
// so we musn't scale it twice.
scale = Math.abs(transform[0] || transform[1]);
const ownTransform = transform.slice();
for (let i = 0; i < 4; i++) {
ownTransform[i] = Math.sign(ownTransform[i]);
}
ownMatrix = `matrix(${ownTransform.join(",")})`;
}
const left = rect[0] * scale;
const top = rect[1] * scale;
element.style.left = `${left}px`;
element.style.top = `${top}px`;
element.style.transformOrigin = `${-left}px ${-top}px`;
element.style.transform = ownMatrix;
} else {
element.style.transform = matrix;
}
} }
} }
} }
parameters.div.hidden = false;
this.#setAnnotationCanvasMap(div, annotationCanvasMap);
div.hidden = false;
}
static #setAnnotationCanvasMap(div, annotationCanvasMap) {
if (!annotationCanvasMap) {
return;
}
for (const [id, canvas] of annotationCanvasMap) {
const element = div.querySelector(`[data-annotation-id="${id}"]`);
if (!element) {
continue;
}
const { firstChild } = element;
if (firstChild.nodeName === "CANVAS") {
element.replaceChild(canvas, firstChild);
} else {
element.insertBefore(canvas, firstChild);
}
}
annotationCanvasMap.clear();
} }
} }

View File

@ -1161,6 +1161,8 @@ class PDFDocumentProxy {
* created from `PDFDocumentProxy.getOptionalContentConfig`. If `null`, * created from `PDFDocumentProxy.getOptionalContentConfig`. If `null`,
* the configuration will be fetched automatically with the default visibility * the configuration will be fetched automatically with the default visibility
* states set. * states set.
* @property {Map<string, Canvas>} [annotationCanvasMap] - Map some annotation
* ids with canvases used to render them.
*/ */
/** /**
@ -1374,6 +1376,7 @@ class PDFPageProxy {
canvasFactory = null, canvasFactory = null,
background = null, background = null,
optionalContentConfigPromise = null, optionalContentConfigPromise = null,
annotationCanvasMap = null,
}) { }) {
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC")) { if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC")) {
if (arguments[0]?.renderInteractiveForms !== undefined) { if (arguments[0]?.renderInteractiveForms !== undefined) {
@ -1491,6 +1494,7 @@ class PDFPageProxy {
}, },
objs: this.objs, objs: this.objs,
commonObjs: this.commonObjs, commonObjs: this.commonObjs,
annotationCanvasMap,
operatorList: intentState.operatorList, operatorList: intentState.operatorList,
pageIndex: this._pageIndex, pageIndex: this._pageIndex,
canvasFactory: canvasFactoryInstance, canvasFactory: canvasFactoryInstance,
@ -3216,6 +3220,7 @@ class InternalRenderTask {
params, params,
objs, objs,
commonObjs, commonObjs,
annotationCanvasMap,
operatorList, operatorList,
pageIndex, pageIndex,
canvasFactory, canvasFactory,
@ -3226,6 +3231,7 @@ class InternalRenderTask {
this.params = params; this.params = params;
this.objs = objs; this.objs = objs;
this.commonObjs = commonObjs; this.commonObjs = commonObjs;
this.annotationCanvasMap = annotationCanvasMap;
this.operatorListIdx = null; this.operatorListIdx = null;
this.operatorList = operatorList; this.operatorList = operatorList;
this._pageIndex = pageIndex; this._pageIndex = pageIndex;
@ -3284,7 +3290,8 @@ class InternalRenderTask {
this.objs, this.objs,
this.canvasFactory, this.canvasFactory,
imageLayer, imageLayer,
optionalContentConfig optionalContentConfig,
this.annotationCanvasMap
); );
this.gfx.beginDrawing({ this.gfx.beginDrawing({
transform, transform,

View File

@ -1068,7 +1068,8 @@ class CanvasGraphics {
objs, objs,
canvasFactory, canvasFactory,
imageLayer, imageLayer,
optionalContentConfig optionalContentConfig,
annotationCanvasMap
) { ) {
this.ctx = canvasCtx; this.ctx = canvasCtx;
this.current = new CanvasExtraState( this.current = new CanvasExtraState(
@ -1100,6 +1101,10 @@ class CanvasGraphics {
this.optionalContentConfig = optionalContentConfig; this.optionalContentConfig = optionalContentConfig;
this.cachedCanvases = new CachedCanvases(this.canvasFactory); this.cachedCanvases = new CachedCanvases(this.canvasFactory);
this.cachedPatterns = new Map(); this.cachedPatterns = new Map();
this.annotationCanvasMap = annotationCanvasMap;
this.viewportScale = 1;
this.outputScaleX = 1;
this.outputScaleY = 1;
if (canvasCtx) { if (canvasCtx) {
// NOTE: if mozCurrentTransform is polyfilled, then the current state of // NOTE: if mozCurrentTransform is polyfilled, then the current state of
// the transformation must already be set in canvasCtx._transformMatrix. // the transformation must already be set in canvasCtx._transformMatrix.
@ -1147,8 +1152,11 @@ class CanvasGraphics {
resetCtxToDefault(this.ctx); resetCtxToDefault(this.ctx);
if (transform) { if (transform) {
this.ctx.transform.apply(this.ctx, transform); this.ctx.transform.apply(this.ctx, transform);
this.outputScaleX = transform[0];
this.outputScaleY = transform[0];
} }
this.ctx.transform.apply(this.ctx, viewport.transform); this.ctx.transform.apply(this.ctx, viewport.transform);
this.viewportScale = viewport.scale;
this.baseTransform = this.ctx.mozCurrentTransform.slice(); this.baseTransform = this.ctx.mozCurrentTransform.slice();
this._combinedScaleFactor = Math.hypot( this._combinedScaleFactor = Math.hypot(
@ -2691,27 +2699,72 @@ class CanvasGraphics {
this.restore(); this.restore();
} }
beginAnnotation(id, rect, transform, matrix) { beginAnnotation(id, rect, transform, matrix, hasOwnCanvas) {
this.save(); this.save();
resetCtxToDefault(this.ctx);
this.current = new CanvasExtraState(
this.ctx.canvas.width,
this.ctx.canvas.height
);
if (Array.isArray(rect) && rect.length === 4) { if (Array.isArray(rect) && rect.length === 4) {
const width = rect[2] - rect[0]; const width = rect[2] - rect[0];
const height = rect[3] - rect[1]; const height = rect[3] - rect[1];
this.ctx.rect(rect[0], rect[1], width, height);
this.clip(); if (hasOwnCanvas && this.annotationCanvasMap) {
this.endPath(); transform = transform.slice();
transform[4] -= rect[0];
transform[5] -= rect[1];
rect = rect.slice();
rect[0] = rect[1] = 0;
rect[2] = width;
rect[3] = height;
const [scaleX, scaleY] = Util.singularValueDecompose2dScale(
this.ctx.mozCurrentTransform
);
const { viewportScale } = this;
const canvasWidth = Math.ceil(
width * this.outputScaleX * viewportScale
);
const canvasHeight = Math.ceil(
height * this.outputScaleY * viewportScale
);
this.annotationCanvas = this.canvasFactory.create(
canvasWidth,
canvasHeight
);
const { canvas, context } = this.annotationCanvas;
canvas.style.width = `calc(${width}px * var(--viewport-scale-factor))`;
canvas.style.height = `calc(${height}px * var(--viewport-scale-factor))`;
this.annotationCanvasMap.set(id, canvas);
this.annotationCanvas.savedCtx = this.ctx;
this.ctx = context;
this.ctx.setTransform(scaleX, 0, 0, -scaleY, 0, height * scaleY);
addContextCurrentTransform(this.ctx);
resetCtxToDefault(this.ctx);
} else {
resetCtxToDefault(this.ctx);
this.ctx.rect(rect[0], rect[1], width, height);
this.clip();
this.endPath();
}
} }
this.current = new CanvasExtraState(
this.ctx.canvas.width,
this.ctx.canvas.height
);
this.transform.apply(this, transform); this.transform.apply(this, transform);
this.transform.apply(this, matrix); this.transform.apply(this, matrix);
} }
endAnnotation() { endAnnotation() {
if (this.annotationCanvas) {
this.ctx = this.annotationCanvas.savedCtx;
delete this.annotationCanvas.savedCtx;
delete this.annotationCanvas;
}
this.restore(); this.restore();
} }

View File

@ -101,6 +101,25 @@ function inlineImages(images) {
return Promise.all(imagePromises); return Promise.all(imagePromises);
} }
async function convertCanvasesToImages(annotationCanvasMap) {
const results = new Map();
const promises = [];
for (const [key, canvas] of annotationCanvasMap) {
promises.push(
new Promise(resolve => {
canvas.toBlob(blob => {
const image = document.createElement("img");
image.onload = resolve;
results.set(key, image);
image.src = URL.createObjectURL(blob);
});
})
);
}
await Promise.all(promises);
return results;
}
async function resolveImages(node, silentErrors = false) { async function resolveImages(node, silentErrors = false) {
const images = node.getElementsByTagName("img"); const images = node.getElementsByTagName("img");
const data = await inlineImages(images); const data = await inlineImages(images);
@ -227,6 +246,7 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
ctx, ctx,
viewport, viewport,
annotations, annotations,
annotationCanvasMap,
page, page,
imageResourcesPath, imageResourcesPath,
renderForms = false renderForms = false
@ -255,6 +275,10 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
style.textContent = common + "\n" + overrides; style.textContent = common + "\n" + overrides;
var annotation_viewport = viewport.clone({ dontFlip: true }); var annotation_viewport = viewport.clone({ dontFlip: true });
const annotationImageMap = await convertCanvasesToImages(
annotationCanvasMap
);
var parameters = { var parameters = {
viewport: annotation_viewport, viewport: annotation_viewport,
div, div,
@ -263,6 +287,7 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
linkService: new SimpleLinkService(), linkService: new SimpleLinkService(),
imageResourcesPath, imageResourcesPath,
renderForms, renderForms,
annotationCanvasMap: annotationImageMap,
}; };
AnnotationLayer.render(parameters); AnnotationLayer.render(parameters);
@ -665,7 +690,8 @@ var Driver = (function DriverClosure() {
var renderAnnotations = false, var renderAnnotations = false,
renderForms = false, renderForms = false,
renderPrint = false, renderPrint = false,
renderXfa = false; renderXfa = false,
annotationCanvasMap = null;
if (task.annotationStorage) { if (task.annotationStorage) {
const entries = Object.entries(task.annotationStorage), const entries = Object.entries(task.annotationStorage),
@ -739,18 +765,8 @@ var Driver = (function DriverClosure() {
if (!renderXfa) { if (!renderXfa) {
// The annotation builder will draw its content // The annotation builder will draw its content
// on the canvas. // on the canvas.
initPromise = page initPromise = page.getAnnotations({ intent: "display" });
.getAnnotations({ intent: "display" }) annotationCanvasMap = new Map();
.then(function (annotations) {
return rasterizeAnnotationLayer(
annotationLayerContext,
viewport,
annotations,
page,
IMAGE_RESOURCES_PATH,
renderForms
);
});
} else { } else {
initPromise = page.getXfa().then(function (xfa) { initPromise = page.getXfa().then(function (xfa) {
return rasterizeXfaLayer( return rasterizeXfaLayer(
@ -768,11 +784,11 @@ var Driver = (function DriverClosure() {
initPromise = Promise.resolve(); initPromise = Promise.resolve();
} }
} }
var renderContext = { var renderContext = {
canvasContext: ctx, canvasContext: ctx,
viewport, viewport,
optionalContentConfigPromise: task.optionalContentConfigPromise, optionalContentConfigPromise: task.optionalContentConfigPromise,
annotationCanvasMap,
}; };
if (renderForms) { if (renderForms) {
renderContext.annotationMode = AnnotationMode.ENABLE_FORMS; renderContext.annotationMode = AnnotationMode.ENABLE_FORMS;
@ -805,7 +821,7 @@ var Driver = (function DriverClosure() {
self._snapshot(task, error); self._snapshot(task, error);
}; };
initPromise initPromise
.then(function () { .then(function (data) {
const renderTask = page.render(renderContext); const renderTask = page.render(renderContext);
if (task.renderTaskOnContinue) { if (task.renderTaskOnContinue) {
@ -815,7 +831,21 @@ var Driver = (function DriverClosure() {
}; };
} }
return renderTask.promise.then(function () { return renderTask.promise.then(function () {
completeRender(false); if (annotationCanvasMap) {
rasterizeAnnotationLayer(
annotationLayerContext,
viewport,
data,
annotationCanvasMap,
page,
IMAGE_RESOURCES_PATH,
renderForms
).then(() => {
completeRender(false);
});
} else {
completeRender(false);
}
}); });
}) })
.catch(function (error) { .catch(function (error) {

View File

@ -26,6 +26,7 @@ import {
AnnotationFlag, AnnotationFlag,
AnnotationType, AnnotationType,
OPS, OPS,
RenderingIntentFlag,
stringToBytes, stringToBytes,
stringToUTF8String, stringToUTF8String,
} from "../../src/shared/util.js"; } from "../../src/shared/util.js";
@ -1680,6 +1681,7 @@ describe("annotation", function () {
const operatorList = await annotation.getOperatorList( const operatorList = await annotation.getOperatorList(
partialEvaluator, partialEvaluator,
task, task,
RenderingIntentFlag.PRINT,
false, false,
annotationStorage annotationStorage
); );
@ -1694,6 +1696,7 @@ describe("annotation", function () {
[0, 0, 32, 10], [0, 0, 32, 10],
[32, 0, 0, 10, 0, 0], [32, 0, 0, 10, 0, 0],
[1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0],
false,
]); ]);
expect(operatorList.argsArray[1]).toEqual( expect(operatorList.argsArray[1]).toEqual(
new Uint8ClampedArray([26, 51, 76]) new Uint8ClampedArray([26, 51, 76])
@ -2319,6 +2322,7 @@ describe("annotation", function () {
const operatorList = await annotation.getOperatorList( const operatorList = await annotation.getOperatorList(
checkboxEvaluator, checkboxEvaluator,
task, task,
RenderingIntentFlag.PRINT,
false, false,
annotationStorage annotationStorage
); );
@ -2335,6 +2339,7 @@ describe("annotation", function () {
[0, 0, 0, 0], [0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0],
false,
]); ]);
expect(operatorList.argsArray[3][0][0].unicode).toEqual("4"); expect(operatorList.argsArray[3][0][0].unicode).toEqual("4");
}); });
@ -2378,6 +2383,7 @@ describe("annotation", function () {
let operatorList = await annotation.getOperatorList( let operatorList = await annotation.getOperatorList(
partialEvaluator, partialEvaluator,
task, task,
RenderingIntentFlag.PRINT,
false, false,
annotationStorage annotationStorage
); );
@ -2392,6 +2398,7 @@ describe("annotation", function () {
[0, 0, 0, 0], [0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0],
false,
]); ]);
expect(operatorList.argsArray[1]).toEqual( expect(operatorList.argsArray[1]).toEqual(
new Uint8ClampedArray([26, 51, 76]) new Uint8ClampedArray([26, 51, 76])
@ -2402,6 +2409,7 @@ describe("annotation", function () {
operatorList = await annotation.getOperatorList( operatorList = await annotation.getOperatorList(
partialEvaluator, partialEvaluator,
task, task,
RenderingIntentFlag.PRINT,
false, false,
annotationStorage annotationStorage
); );
@ -2416,6 +2424,7 @@ describe("annotation", function () {
[0, 0, 0, 0], [0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0],
false,
]); ]);
expect(operatorList.argsArray[1]).toEqual( expect(operatorList.argsArray[1]).toEqual(
new Uint8ClampedArray([76, 51, 26]) new Uint8ClampedArray([76, 51, 26])
@ -2464,6 +2473,7 @@ describe("annotation", function () {
const operatorList = await annotation.getOperatorList( const operatorList = await annotation.getOperatorList(
partialEvaluator, partialEvaluator,
task, task,
RenderingIntentFlag.PRINT,
false, false,
annotationStorage annotationStorage
); );
@ -2478,6 +2488,7 @@ describe("annotation", function () {
[0, 0, 0, 0], [0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0],
false,
]); ]);
expect(operatorList.argsArray[1]).toEqual( expect(operatorList.argsArray[1]).toEqual(
new Uint8ClampedArray([26, 51, 76]) new Uint8ClampedArray([26, 51, 76])
@ -2524,6 +2535,7 @@ describe("annotation", function () {
const operatorList = await annotation.getOperatorList( const operatorList = await annotation.getOperatorList(
partialEvaluator, partialEvaluator,
task, task,
RenderingIntentFlag.PRINT,
false, false,
annotationStorage annotationStorage
); );
@ -2538,6 +2550,7 @@ describe("annotation", function () {
[0, 0, 0, 0], [0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0],
false,
]); ]);
expect(operatorList.argsArray[1]).toEqual( expect(operatorList.argsArray[1]).toEqual(
new Uint8ClampedArray([26, 51, 76]) new Uint8ClampedArray([26, 51, 76])
@ -2727,6 +2740,7 @@ describe("annotation", function () {
let operatorList = await annotation.getOperatorList( let operatorList = await annotation.getOperatorList(
partialEvaluator, partialEvaluator,
task, task,
RenderingIntentFlag.PRINT,
false, false,
annotationStorage annotationStorage
); );
@ -2741,6 +2755,7 @@ describe("annotation", function () {
[0, 0, 0, 0], [0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0],
false,
]); ]);
expect(operatorList.argsArray[1]).toEqual( expect(operatorList.argsArray[1]).toEqual(
new Uint8ClampedArray([26, 51, 76]) new Uint8ClampedArray([26, 51, 76])
@ -2751,6 +2766,7 @@ describe("annotation", function () {
operatorList = await annotation.getOperatorList( operatorList = await annotation.getOperatorList(
partialEvaluator, partialEvaluator,
task, task,
RenderingIntentFlag.PRINT,
false, false,
annotationStorage annotationStorage
); );
@ -2765,6 +2781,7 @@ describe("annotation", function () {
[0, 0, 0, 0], [0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0],
false,
]); ]);
expect(operatorList.argsArray[1]).toEqual( expect(operatorList.argsArray[1]).toEqual(
new Uint8ClampedArray([76, 51, 26]) new Uint8ClampedArray([76, 51, 26])
@ -2811,6 +2828,7 @@ describe("annotation", function () {
const operatorList = await annotation.getOperatorList( const operatorList = await annotation.getOperatorList(
partialEvaluator, partialEvaluator,
task, task,
RenderingIntentFlag.PRINT,
false, false,
annotationStorage annotationStorage
); );
@ -2825,6 +2843,7 @@ describe("annotation", function () {
[0, 0, 0, 0], [0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0],
false,
]); ]);
expect(operatorList.argsArray[1]).toEqual( expect(operatorList.argsArray[1]).toEqual(
new Uint8ClampedArray([76, 51, 26]) new Uint8ClampedArray([76, 51, 26])

View File

@ -32,6 +32,13 @@
height: 100%; height: 100%;
} }
.annotationLayer .buttonWidgetAnnotation.pushButton > canvas {
position: relative;
top: 0;
left: 0;
z-index: -1;
}
.annotationLayer .linkAnnotation > a:hover, .annotationLayer .linkAnnotation > a:hover,
.annotationLayer .buttonWidgetAnnotation.pushButton > a:hover { .annotationLayer .buttonWidgetAnnotation.pushButton > a:hover {
opacity: 0.2; opacity: 0.2;

View File

@ -36,6 +36,7 @@ import { SimpleLinkService } from "./pdf_link_service.js";
* @property {Promise<Object<string, Array<Object>> | null>} * @property {Promise<Object<string, Array<Object>> | null>}
* [fieldObjectsPromise] * [fieldObjectsPromise]
* @property {Object} [mouseState] * @property {Object} [mouseState]
* @property {Map<string, Canvas>} [annotationCanvasMap]
*/ */
class AnnotationLayerBuilder { class AnnotationLayerBuilder {
@ -55,6 +56,7 @@ class AnnotationLayerBuilder {
hasJSActionsPromise = null, hasJSActionsPromise = null,
fieldObjectsPromise = null, fieldObjectsPromise = null,
mouseState = null, mouseState = null,
annotationCanvasMap = null,
}) { }) {
this.pageDiv = pageDiv; this.pageDiv = pageDiv;
this.pdfPage = pdfPage; this.pdfPage = pdfPage;
@ -68,6 +70,7 @@ class AnnotationLayerBuilder {
this._hasJSActionsPromise = hasJSActionsPromise; this._hasJSActionsPromise = hasJSActionsPromise;
this._fieldObjectsPromise = fieldObjectsPromise; this._fieldObjectsPromise = fieldObjectsPromise;
this._mouseState = mouseState; this._mouseState = mouseState;
this._annotationCanvasMap = annotationCanvasMap;
this.div = null; this.div = null;
this._cancelled = false; this._cancelled = false;
@ -105,6 +108,7 @@ class AnnotationLayerBuilder {
hasJSActions, hasJSActions,
fieldObjects, fieldObjects,
mouseState: this._mouseState, mouseState: this._mouseState,
annotationCanvasMap: this._annotationCanvasMap,
}; };
if (this.div) { if (this.div) {
@ -153,6 +157,8 @@ class DefaultAnnotationLayerFactory {
* @param {Object} [mouseState] * @param {Object} [mouseState]
* @param {Promise<Object<string, Array<Object>> | null>} * @param {Promise<Object<string, Array<Object>> | null>}
* [fieldObjectsPromise] * [fieldObjectsPromise]
* @param {Map<string, Canvas> | null} [annotationCanvasMap] - Map some
* annotation ids with canvases used to render them.
* @returns {AnnotationLayerBuilder} * @returns {AnnotationLayerBuilder}
*/ */
createAnnotationLayerBuilder( createAnnotationLayerBuilder(
@ -165,7 +171,8 @@ class DefaultAnnotationLayerFactory {
enableScripting = false, enableScripting = false,
hasJSActionsPromise = null, hasJSActionsPromise = null,
mouseState = null, mouseState = null,
fieldObjectsPromise = null fieldObjectsPromise = null,
annotationCanvasMap = null
) { ) {
return new AnnotationLayerBuilder({ return new AnnotationLayerBuilder({
pageDiv, pageDiv,
@ -179,6 +186,7 @@ class DefaultAnnotationLayerFactory {
hasJSActionsPromise, hasJSActionsPromise,
fieldObjectsPromise, fieldObjectsPromise,
mouseState, mouseState,
annotationCanvasMap,
}); });
} }
} }

View File

@ -850,7 +850,12 @@ class BaseViewer {
} }
return; return;
} }
this._doc.style.setProperty("--zoom-factor", newScale); this._doc.style.setProperty("--zoom-factor", newScale);
this._doc.style.setProperty(
"--viewport-scale-factor",
newScale * PixelsPerInch.PDF_TO_CSS_UNITS
);
const updateArgs = { scale: newScale }; const updateArgs = { scale: newScale };
for (const pageView of this._pages) { for (const pageView of this._pages) {
@ -1480,6 +1485,7 @@ class BaseViewer {
* @param {Object} [mouseState] * @param {Object} [mouseState]
* @param {Promise<Object<string, Array<Object>> | null>} * @param {Promise<Object<string, Array<Object>> | null>}
* [fieldObjectsPromise] * [fieldObjectsPromise]
* @param {Map<string, Canvas>} [annotationCanvasMap]
* @returns {AnnotationLayerBuilder} * @returns {AnnotationLayerBuilder}
*/ */
createAnnotationLayerBuilder( createAnnotationLayerBuilder(
@ -1492,7 +1498,8 @@ class BaseViewer {
enableScripting = null, enableScripting = null,
hasJSActionsPromise = null, hasJSActionsPromise = null,
mouseState = null, mouseState = null,
fieldObjectsPromise = null fieldObjectsPromise = null,
annotationCanvasMap = null
) { ) {
return new AnnotationLayerBuilder({ return new AnnotationLayerBuilder({
pageDiv, pageDiv,
@ -1510,6 +1517,7 @@ class BaseViewer {
fieldObjectsPromise: fieldObjectsPromise:
fieldObjectsPromise || this.pdfDocument?.getFieldObjects(), fieldObjectsPromise || this.pdfDocument?.getFieldObjects(),
mouseState: mouseState || this._scriptingManager?.mouseState, mouseState: mouseState || this._scriptingManager?.mouseState,
annotationCanvasMap,
}); });
} }

View File

@ -175,6 +175,8 @@ class IPDFAnnotationLayerFactory {
* @param {Object} [mouseState] * @param {Object} [mouseState]
* @param {Promise<Object<string, Array<Object>> | null>} * @param {Promise<Object<string, Array<Object>> | null>}
* [fieldObjectsPromise] * [fieldObjectsPromise]
* @property {Map<string, Canvas> | null} [annotationCanvasMap] - Map some
* annotation ids with canvases used to render them.
* @returns {AnnotationLayerBuilder} * @returns {AnnotationLayerBuilder}
*/ */
createAnnotationLayerBuilder( createAnnotationLayerBuilder(
@ -187,7 +189,8 @@ class IPDFAnnotationLayerFactory {
enableScripting = false, enableScripting = false,
hasJSActionsPromise = null, hasJSActionsPromise = null,
mouseState = null, mouseState = null,
fieldObjectsPromise = null fieldObjectsPromise = null,
annotationCanvasMap = null
) {} ) {}
} }

View File

@ -123,6 +123,8 @@ class PDFPageView {
this._renderError = null; this._renderError = null;
this._isStandalone = !this.renderingQueue?.hasViewer(); this._isStandalone = !this.renderingQueue?.hasViewer();
this._annotationCanvasMap = null;
this.annotationLayer = null; this.annotationLayer = null;
this.textLayer = null; this.textLayer = null;
this.zoomLayer = null; this.zoomLayer = null;
@ -322,17 +324,20 @@ class PDFPageView {
if (optionalContentConfigPromise instanceof Promise) { if (optionalContentConfigPromise instanceof Promise) {
this._optionalContentConfigPromise = optionalContentConfigPromise; this._optionalContentConfigPromise = optionalContentConfigPromise;
} }
if (this._isStandalone) {
const doc = document.documentElement;
doc.style.setProperty("--zoom-factor", this.scale);
}
const totalRotation = (this.rotation + this.pdfPageRotate) % 360; const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
const viewportScale = this.scale * PixelsPerInch.PDF_TO_CSS_UNITS;
this.viewport = this.viewport.clone({ this.viewport = this.viewport.clone({
scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS, scale: viewportScale,
rotation: totalRotation, rotation: totalRotation,
}); });
if (this._isStandalone) {
const { style } = document.documentElement;
style.setProperty("--zoom-factor", this.scale);
style.setProperty("--viewport-scale-factor", viewportScale);
}
if (this.svg) { if (this.svg) {
this.cssTransform({ this.cssTransform({
target: this.svg, target: this.svg,
@ -418,6 +423,7 @@ class PDFPageView {
) { ) {
this.annotationLayer.cancel(); this.annotationLayer.cancel();
this.annotationLayer = null; this.annotationLayer = null;
this._annotationCanvasMap = null;
} }
if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) { if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) {
this.xfaLayer.cancel(); this.xfaLayer.cancel();
@ -580,6 +586,27 @@ class PDFPageView {
} }
this.textLayer = textLayer; this.textLayer = textLayer;
if (
this._annotationMode !== AnnotationMode.DISABLE &&
this.annotationLayerFactory
) {
this._annotationCanvasMap ||= new Map();
this.annotationLayer ||=
this.annotationLayerFactory.createAnnotationLayerBuilder(
div,
pdfPage,
/* annotationStorage = */ null,
this.imageResourcesPath,
this._annotationMode === AnnotationMode.ENABLE_FORMS,
this.l10n,
/* enableScripting = */ null,
/* hasJSActionsPromise = */ null,
/* mouseState = */ null,
/* fieldObjectsPromise = */ null,
/* annotationCanvasMap */ this._annotationCanvasMap
);
}
if (this.xfaLayer?.div) { if (this.xfaLayer?.div) {
// The xfa layer needs to stay on top. // The xfa layer needs to stay on top.
div.appendChild(this.xfaLayer.div); div.appendChild(this.xfaLayer.div);
@ -653,6 +680,10 @@ class PDFPageView {
textLayer.setTextContentStream(readableStream); textLayer.setTextContentStream(readableStream);
textLayer.render(); textLayer.render();
} }
if (this.annotationLayer) {
this._renderAnnotationLayer();
}
}); });
}, },
function (reason) { function (reason) {
@ -660,28 +691,6 @@ class PDFPageView {
} }
); );
if (
this._annotationMode !== AnnotationMode.DISABLE &&
this.annotationLayerFactory
) {
if (!this.annotationLayer) {
this.annotationLayer =
this.annotationLayerFactory.createAnnotationLayerBuilder(
div,
pdfPage,
/* annotationStorage = */ null,
this.imageResourcesPath,
this._annotationMode === AnnotationMode.ENABLE_FORMS,
this.l10n,
/* enableScripting = */ null,
/* hasJSActionsPromise = */ null,
/* mouseState = */ null,
/* fieldObjectsPromise = */ null
);
}
this._renderAnnotationLayer();
}
if (this.xfaLayerFactory) { if (this.xfaLayerFactory) {
if (!this.xfaLayer) { if (!this.xfaLayer) {
this.xfaLayer = this.xfaLayerFactory.createXfaLayerBuilder( this.xfaLayer = this.xfaLayerFactory.createXfaLayerBuilder(
@ -804,6 +813,7 @@ class PDFPageView {
canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]); canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]);
canvas.style.width = roundToDivide(viewport.width, sfx[1]) + "px"; canvas.style.width = roundToDivide(viewport.width, sfx[1]) + "px";
canvas.style.height = roundToDivide(viewport.height, sfy[1]) + "px"; canvas.style.height = roundToDivide(viewport.height, sfy[1]) + "px";
// Add the viewport so it's known what it was originally drawn with. // Add the viewport so it's known what it was originally drawn with.
this.paintedViewportMap.set(canvas, viewport); this.paintedViewportMap.set(canvas, viewport);
@ -817,6 +827,7 @@ class PDFPageView {
viewport: this.viewport, viewport: this.viewport,
annotationMode: this._annotationMode, annotationMode: this._annotationMode,
optionalContentConfigPromise: this._optionalContentConfigPromise, optionalContentConfigPromise: this._optionalContentConfigPromise,
annotationCanvasMap: this._annotationCanvasMap,
}; };
const renderTask = this.pdfPage.render(renderContext); const renderTask = this.pdfPage.render(renderContext);
renderTask.onContinue = function (cont) { renderTask.onContinue = function (cont) {

View File

@ -22,6 +22,7 @@
--page-border: 9px solid transparent; --page-border: 9px solid transparent;
--spreadHorizontalWrapped-margin-LR: -3.5px; --spreadHorizontalWrapped-margin-LR: -3.5px;
--zoom-factor: 1; --zoom-factor: 1;
--viewport-scale-factor: 1;
} }
@media screen and (forced-colors: active) { @media screen and (forced-colors: active) {