mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-22 16:18:08 +02:00
[api-minor] Render pushbuttons on their own canvas (bug 1737260)
- First step to fix https://bugzilla.mozilla.org/show_bug.cgi?id=1737260; - several interactive pdfs use the possibility to hide/show buttons to show different icons; - render pushbuttons on their own canvas and then insert it the annotation_layer; - update test/driver.js in order to convert canvases for pushbuttons into images.
This commit is contained in:
parent
891f21fba6
commit
33ea817b20
13 changed files with 333 additions and 86 deletions
|
@ -26,6 +26,7 @@ import {
|
|||
isAscii,
|
||||
isString,
|
||||
OPS,
|
||||
RenderingIntentFlag,
|
||||
shadow,
|
||||
stringToPDFString,
|
||||
stringToUTF16BEString,
|
||||
|
@ -386,6 +387,7 @@ class Annotation {
|
|||
modificationDate: this.modificationDate,
|
||||
rect: this.rectangle,
|
||||
subtype: params.subtype,
|
||||
hasOwnCanvas: false,
|
||||
};
|
||||
|
||||
if (params.collectFields) {
|
||||
|
@ -708,8 +710,8 @@ class Annotation {
|
|||
this.appearance = normalAppearanceState.get(as.name);
|
||||
}
|
||||
|
||||
loadResources(keys) {
|
||||
return this.appearance.dict.getAsync("Resources").then(resources => {
|
||||
loadResources(keys, appearance) {
|
||||
return appearance.dict.getAsync("Resources").then(resources => {
|
||||
if (!resources) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -721,22 +723,24 @@ class Annotation {
|
|||
});
|
||||
}
|
||||
|
||||
getOperatorList(evaluator, task, renderForms, annotationStorage) {
|
||||
if (!this.appearance) {
|
||||
return Promise.resolve(new OperatorList());
|
||||
getOperatorList(evaluator, task, intent, renderForms, annotationStorage) {
|
||||
const data = this.data;
|
||||
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 resourcesPromise = this.loadResources([
|
||||
"ExtGState",
|
||||
"ColorSpace",
|
||||
"Pattern",
|
||||
"Shading",
|
||||
"XObject",
|
||||
"Font",
|
||||
]);
|
||||
const resourcesPromise = this.loadResources(
|
||||
["ExtGState", "ColorSpace", "Pattern", "Shading", "XObject", "Font"],
|
||||
appearance
|
||||
);
|
||||
const bbox = appearanceDict.getArray("BBox") || [0, 0, 1, 1];
|
||||
const matrix = appearanceDict.getArray("Matrix") || [1, 0, 0, 1, 0, 0];
|
||||
const transform = getTransformMatrix(data.rect, bbox, matrix);
|
||||
|
@ -748,6 +752,7 @@ class Annotation {
|
|||
data.rect,
|
||||
transform,
|
||||
matrix,
|
||||
isUsingOwnCanvas,
|
||||
]);
|
||||
|
||||
return evaluator
|
||||
|
@ -1329,7 +1334,7 @@ class WidgetAnnotation extends Annotation {
|
|||
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
|
||||
// enabled. The display layer is responsible for rendering them instead.
|
||||
if (renderForms && !(this instanceof SignatureWidgetAnnotation)) {
|
||||
|
@ -1340,6 +1345,7 @@ class WidgetAnnotation extends Annotation {
|
|||
return super.getOperatorList(
|
||||
evaluator,
|
||||
task,
|
||||
intent,
|
||||
renderForms,
|
||||
annotationStorage
|
||||
);
|
||||
|
@ -1351,6 +1357,7 @@ class WidgetAnnotation extends Annotation {
|
|||
return super.getOperatorList(
|
||||
evaluator,
|
||||
task,
|
||||
intent,
|
||||
renderForms,
|
||||
annotationStorage
|
||||
);
|
||||
|
@ -1936,17 +1943,25 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
|
|||
} else if (this.data.radioButton) {
|
||||
this._processRadioButton(params);
|
||||
} else if (this.data.pushButton) {
|
||||
this.data.hasOwnCanvas = true;
|
||||
this._processPushButton(params);
|
||||
} else {
|
||||
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) {
|
||||
return super.getOperatorList(
|
||||
evaluator,
|
||||
task,
|
||||
intent,
|
||||
false, // we use normalAppearance to render the button
|
||||
annotationStorage
|
||||
);
|
||||
|
@ -1965,6 +1980,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
|
|||
return super.getOperatorList(
|
||||
evaluator,
|
||||
task,
|
||||
intent,
|
||||
renderForms,
|
||||
annotationStorage
|
||||
);
|
||||
|
@ -1988,6 +2004,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
|
|||
const operatorList = super.getOperatorList(
|
||||
evaluator,
|
||||
task,
|
||||
intent,
|
||||
renderForms,
|
||||
annotationStorage
|
||||
);
|
||||
|
|
|
@ -407,6 +407,7 @@ class Page {
|
|||
.getOperatorList(
|
||||
partialEvaluator,
|
||||
task,
|
||||
intent,
|
||||
renderForms,
|
||||
annotationStorage
|
||||
)
|
||||
|
|
|
@ -198,7 +198,25 @@ class AnnotationElement {
|
|||
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`;
|
||||
|
||||
if (!ignoreBorder && data.borderStyle.width > 0) {
|
||||
|
@ -258,8 +276,13 @@ class AnnotationElement {
|
|||
|
||||
container.style.left = `${rect[0]}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;
|
||||
}
|
||||
|
||||
|
@ -2318,10 +2341,12 @@ class AnnotationLayer {
|
|||
sortedAnnotations.push(...popupAnnotations);
|
||||
}
|
||||
|
||||
const div = parameters.div;
|
||||
|
||||
for (const data of sortedAnnotations) {
|
||||
const element = AnnotationElementFactory.create({
|
||||
data,
|
||||
layer: parameters.div,
|
||||
layer: div,
|
||||
page: parameters.page,
|
||||
viewport: parameters.viewport,
|
||||
linkService: parameters.linkService,
|
||||
|
@ -2343,19 +2368,21 @@ class AnnotationLayer {
|
|||
}
|
||||
if (Array.isArray(rendered)) {
|
||||
for (const renderedElement of rendered) {
|
||||
parameters.div.appendChild(renderedElement);
|
||||
div.appendChild(renderedElement);
|
||||
}
|
||||
} else {
|
||||
if (element instanceof PopupAnnotationElement) {
|
||||
// Popup annotation elements should not be on top of other
|
||||
// annotation elements to prevent interfering with mouse events.
|
||||
parameters.div.prepend(rendered);
|
||||
div.prepend(rendered);
|
||||
} else {
|
||||
parameters.div.appendChild(rendered);
|
||||
div.appendChild(rendered);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.#setAnnotationCanvasMap(div, parameters.annotationCanvasMap);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2366,18 +2393,73 @@ class AnnotationLayer {
|
|||
* @memberof AnnotationLayer
|
||||
*/
|
||||
static update(parameters) {
|
||||
const transform = `matrix(${parameters.viewport.transform.join(",")})`;
|
||||
for (const data of parameters.annotations) {
|
||||
const elements = parameters.div.querySelectorAll(
|
||||
const { page, viewport, annotations, annotationCanvasMap, div } =
|
||||
parameters;
|
||||
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}"]`
|
||||
);
|
||||
if (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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1161,6 +1161,8 @@ class PDFDocumentProxy {
|
|||
* created from `PDFDocumentProxy.getOptionalContentConfig`. If `null`,
|
||||
* the configuration will be fetched automatically with the default visibility
|
||||
* 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,
|
||||
background = null,
|
||||
optionalContentConfigPromise = null,
|
||||
annotationCanvasMap = null,
|
||||
}) {
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC")) {
|
||||
if (arguments[0]?.renderInteractiveForms !== undefined) {
|
||||
|
@ -1491,6 +1494,7 @@ class PDFPageProxy {
|
|||
},
|
||||
objs: this.objs,
|
||||
commonObjs: this.commonObjs,
|
||||
annotationCanvasMap,
|
||||
operatorList: intentState.operatorList,
|
||||
pageIndex: this._pageIndex,
|
||||
canvasFactory: canvasFactoryInstance,
|
||||
|
@ -3216,6 +3220,7 @@ class InternalRenderTask {
|
|||
params,
|
||||
objs,
|
||||
commonObjs,
|
||||
annotationCanvasMap,
|
||||
operatorList,
|
||||
pageIndex,
|
||||
canvasFactory,
|
||||
|
@ -3226,6 +3231,7 @@ class InternalRenderTask {
|
|||
this.params = params;
|
||||
this.objs = objs;
|
||||
this.commonObjs = commonObjs;
|
||||
this.annotationCanvasMap = annotationCanvasMap;
|
||||
this.operatorListIdx = null;
|
||||
this.operatorList = operatorList;
|
||||
this._pageIndex = pageIndex;
|
||||
|
@ -3284,7 +3290,8 @@ class InternalRenderTask {
|
|||
this.objs,
|
||||
this.canvasFactory,
|
||||
imageLayer,
|
||||
optionalContentConfig
|
||||
optionalContentConfig,
|
||||
this.annotationCanvasMap
|
||||
);
|
||||
this.gfx.beginDrawing({
|
||||
transform,
|
||||
|
|
|
@ -1068,7 +1068,8 @@ class CanvasGraphics {
|
|||
objs,
|
||||
canvasFactory,
|
||||
imageLayer,
|
||||
optionalContentConfig
|
||||
optionalContentConfig,
|
||||
annotationCanvasMap
|
||||
) {
|
||||
this.ctx = canvasCtx;
|
||||
this.current = new CanvasExtraState(
|
||||
|
@ -1100,6 +1101,10 @@ class CanvasGraphics {
|
|||
this.optionalContentConfig = optionalContentConfig;
|
||||
this.cachedCanvases = new CachedCanvases(this.canvasFactory);
|
||||
this.cachedPatterns = new Map();
|
||||
this.annotationCanvasMap = annotationCanvasMap;
|
||||
this.viewportScale = 1;
|
||||
this.outputScaleX = 1;
|
||||
this.outputScaleY = 1;
|
||||
if (canvasCtx) {
|
||||
// NOTE: if mozCurrentTransform is polyfilled, then the current state of
|
||||
// the transformation must already be set in canvasCtx._transformMatrix.
|
||||
|
@ -1147,8 +1152,11 @@ class CanvasGraphics {
|
|||
resetCtxToDefault(this.ctx);
|
||||
if (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.viewportScale = viewport.scale;
|
||||
|
||||
this.baseTransform = this.ctx.mozCurrentTransform.slice();
|
||||
this._combinedScaleFactor = Math.hypot(
|
||||
|
@ -2691,27 +2699,72 @@ class CanvasGraphics {
|
|||
this.restore();
|
||||
}
|
||||
|
||||
beginAnnotation(id, rect, transform, matrix) {
|
||||
beginAnnotation(id, rect, transform, matrix, hasOwnCanvas) {
|
||||
this.save();
|
||||
resetCtxToDefault(this.ctx);
|
||||
this.current = new CanvasExtraState(
|
||||
this.ctx.canvas.width,
|
||||
this.ctx.canvas.height
|
||||
);
|
||||
|
||||
if (Array.isArray(rect) && rect.length === 4) {
|
||||
const width = rect[2] - rect[0];
|
||||
const height = rect[3] - rect[1];
|
||||
this.ctx.rect(rect[0], rect[1], width, height);
|
||||
this.clip();
|
||||
this.endPath();
|
||||
|
||||
if (hasOwnCanvas && this.annotationCanvasMap) {
|
||||
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, matrix);
|
||||
}
|
||||
|
||||
endAnnotation() {
|
||||
if (this.annotationCanvas) {
|
||||
this.ctx = this.annotationCanvas.savedCtx;
|
||||
delete this.annotationCanvas.savedCtx;
|
||||
delete this.annotationCanvas;
|
||||
}
|
||||
this.restore();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue