Skip to content

Commit f51badb

Browse files
committed
Render pushbuttons on their own canvas
- 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.
1 parent 38efd13 commit f51badb

10 files changed

+264
-59
lines changed

src/core/annotation.js

+18-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
isAscii,
2727
isString,
2828
OPS,
29+
RenderingIntentFlag,
2930
shadow,
3031
stringToPDFString,
3132
stringToUTF16BEString,
@@ -386,6 +387,7 @@ class Annotation {
386387
modificationDate: this.modificationDate,
387388
rect: this.rectangle,
388389
subtype: params.subtype,
390+
hasOwnCanvas: false,
389391
};
390392

391393
if (params.collectFields) {
@@ -721,7 +723,7 @@ class Annotation {
721723
});
722724
}
723725

724-
getOperatorList(evaluator, task, renderForms, annotationStorage) {
726+
getOperatorList(evaluator, task, intent, renderForms, annotationStorage) {
725727
if (!this.appearance) {
726728
return Promise.resolve(new OperatorList());
727729
}
@@ -748,6 +750,7 @@ class Annotation {
748750
data.rect,
749751
transform,
750752
matrix,
753+
this.data.hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY,
751754
]);
752755

753756
return evaluator
@@ -1329,7 +1332,7 @@ class WidgetAnnotation extends Annotation {
13291332
return !!(this.data.fieldFlags & flag);
13301333
}
13311334

1332-
getOperatorList(evaluator, task, renderForms, annotationStorage) {
1335+
getOperatorList(evaluator, task, intent, renderForms, annotationStorage) {
13331336
// Do not render form elements on the canvas when interactive forms are
13341337
// enabled. The display layer is responsible for rendering them instead.
13351338
if (renderForms && !(this instanceof SignatureWidgetAnnotation)) {
@@ -1340,6 +1343,7 @@ class WidgetAnnotation extends Annotation {
13401343
return super.getOperatorList(
13411344
evaluator,
13421345
task,
1346+
intent,
13431347
renderForms,
13441348
annotationStorage
13451349
);
@@ -1351,6 +1355,7 @@ class WidgetAnnotation extends Annotation {
13511355
return super.getOperatorList(
13521356
evaluator,
13531357
task,
1358+
intent,
13541359
renderForms,
13551360
annotationStorage
13561361
);
@@ -1936,17 +1941,25 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
19361941
} else if (this.data.radioButton) {
19371942
this._processRadioButton(params);
19381943
} else if (this.data.pushButton) {
1944+
this.data.hasOwnCanvas = true;
19391945
this._processPushButton(params);
19401946
} else {
19411947
warn("Invalid field flags for button widget annotation");
19421948
}
19431949
}
19441950

1945-
async getOperatorList(evaluator, task, renderForms, annotationStorage) {
1951+
async getOperatorList(
1952+
evaluator,
1953+
task,
1954+
intent,
1955+
renderForms,
1956+
annotationStorage
1957+
) {
19461958
if (this.data.pushButton) {
19471959
return super.getOperatorList(
19481960
evaluator,
19491961
task,
1962+
intent,
19501963
false, // we use normalAppearance to render the button
19511964
annotationStorage
19521965
);
@@ -1965,6 +1978,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
19651978
return super.getOperatorList(
19661979
evaluator,
19671980
task,
1981+
intent,
19681982
renderForms,
19691983
annotationStorage
19701984
);
@@ -1988,6 +2002,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
19882002
const operatorList = super.getOperatorList(
19892003
evaluator,
19902004
task,
2005+
intent,
19912006
renderForms,
19922007
annotationStorage
19932008
);

src/core/document.js

+1
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ class Page {
407407
.getOperatorList(
408408
partialEvaluator,
409409
task,
410+
intent,
410411
renderForms,
411412
annotationStorage
412413
)

src/display/annotation_layer.js

+71-5
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,25 @@ class AnnotationElement {
198198
page.view[3] - data.rect[3] + page.view[1],
199199
]);
200200

201-
container.style.transform = `matrix(${viewport.transform.join(",")})`;
201+
if (data.hasOwnCanvas) {
202+
const transform = viewport.transform.slice();
203+
const scale = Math.abs(transform[0] || transform[1]);
204+
width = Math.ceil(width * scale);
205+
height = Math.ceil(height * scale);
206+
rect[0] *= scale;
207+
rect[1] *= scale;
208+
// Reset the scale part of the transform matrix (which must be diagonal
209+
// or anti-diagonal) in order to avoid to rescale the canvas.
210+
// The canvas for the annotation is correctly scaled when it is drawn
211+
// (see `beginAnnotation` in canvas.js).
212+
for (let i = 0; i < 4; i++) {
213+
transform[i] = Math.sign(transform[i]);
214+
}
215+
container.style.transform = `matrix(${transform.join(",")})`;
216+
} else {
217+
container.style.transform = `matrix(${viewport.transform.join(",")})`;
218+
}
219+
202220
container.style.transformOrigin = `${-rect[0]}px ${-rect[1]}px`;
203221

204222
if (!ignoreBorder && data.borderStyle.width > 0) {
@@ -258,8 +276,13 @@ class AnnotationElement {
258276

259277
container.style.left = `${rect[0]}px`;
260278
container.style.top = `${rect[1]}px`;
261-
container.style.width = `${width}px`;
262-
container.style.height = `${height}px`;
279+
280+
if (data.pushButton) {
281+
container.style.width = container.style.height = "auto";
282+
} else {
283+
container.style.width = `${width}px`;
284+
container.style.height = `${height}px`;
285+
}
263286
return container;
264287
}
265288

@@ -2366,19 +2389,62 @@ class AnnotationLayer {
23662389
* @memberof AnnotationLayer
23672390
*/
23682391
static update(parameters) {
2369-
const transform = `matrix(${parameters.viewport.transform.join(",")})`;
2392+
const { page, viewport, annotations } = parameters;
2393+
const transform = viewport.transform;
2394+
const matrix = `matrix(${transform.join(",")})`;
2395+
2396+
let scale, ownMatrix;
2397+
if (annotations.some(annotation => annotation.hasOwnCanvas)) {
2398+
scale = Math.abs(transform[0] || transform[1]);
2399+
const ownTransform = transform.slice();
2400+
for (let i = 0; i < 4; i++) {
2401+
ownTransform[i] = Math.sign(ownTransform[i]);
2402+
}
2403+
ownMatrix = `matrix(${ownTransform.join(",")})`;
2404+
}
2405+
23702406
for (const data of parameters.annotations) {
23712407
const elements = parameters.div.querySelectorAll(
23722408
`[data-annotation-id="${data.id}"]`
23732409
);
23742410
if (elements) {
23752411
for (const element of elements) {
2376-
element.style.transform = transform;
2412+
if (data.hasOwnCanvas) {
2413+
const rect = Util.normalizeRect([
2414+
data.rect[0],
2415+
page.view[3] - data.rect[1] + page.view[1],
2416+
data.rect[2],
2417+
page.view[3] - data.rect[3] + page.view[1],
2418+
]);
2419+
2420+
const left = rect[0] * scale;
2421+
const top = rect[1] * scale;
2422+
element.style.left = `${left}px`;
2423+
element.style.top = `${top}px`;
2424+
element.style.transformOrigin = `${-left}px ${-top}px`;
2425+
element.style.transform = ownMatrix;
2426+
} else {
2427+
element.style.transform = matrix;
2428+
}
23772429
}
23782430
}
23792431
}
23802432
parameters.div.hidden = false;
23812433
}
2434+
2435+
static setAnnotationCanvases(div, canvases) {
2436+
for (const [id, canvas] of canvases.entries()) {
2437+
const element = div.querySelector(`[data-annotation-id="${id}"]`);
2438+
if (!element) {
2439+
continue;
2440+
}
2441+
if (element.firstChild.nodeName === "CANVAS") {
2442+
element.replaceChild(element.firstChild, canvas);
2443+
} else {
2444+
element.appendChild(canvas);
2445+
}
2446+
}
2447+
}
23822448
}
23832449

23842450
export { AnnotationLayer };

src/display/api.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -1374,6 +1374,7 @@ class PDFPageProxy {
13741374
canvasFactory = null,
13751375
background = null,
13761376
optionalContentConfigPromise = null,
1377+
annotationCanvases = null,
13771378
}) {
13781379
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC")) {
13791380
if (arguments[0]?.renderInteractiveForms !== undefined) {
@@ -1491,6 +1492,7 @@ class PDFPageProxy {
14911492
},
14921493
objs: this.objs,
14931494
commonObjs: this.commonObjs,
1495+
annotationCanvases,
14941496
operatorList: intentState.operatorList,
14951497
pageIndex: this._pageIndex,
14961498
canvasFactory: canvasFactoryInstance,
@@ -3216,6 +3218,7 @@ class InternalRenderTask {
32163218
params,
32173219
objs,
32183220
commonObjs,
3221+
annotationCanvases,
32193222
operatorList,
32203223
pageIndex,
32213224
canvasFactory,
@@ -3226,6 +3229,7 @@ class InternalRenderTask {
32263229
this.params = params;
32273230
this.objs = objs;
32283231
this.commonObjs = commonObjs;
3232+
this.annotationCanvases = annotationCanvases;
32293233
this.operatorListIdx = null;
32303234
this.operatorList = operatorList;
32313235
this._pageIndex = pageIndex;
@@ -3284,7 +3288,8 @@ class InternalRenderTask {
32843288
this.objs,
32853289
this.canvasFactory,
32863290
imageLayer,
3287-
optionalContentConfig
3291+
optionalContentConfig,
3292+
this.annotationCanvases
32883293
);
32893294
this.gfx.beginDrawing({
32903295
transform,

src/display/canvas.js

+48-10
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,8 @@ class CanvasGraphics {
10681068
objs,
10691069
canvasFactory,
10701070
imageLayer,
1071-
optionalContentConfig
1071+
optionalContentConfig,
1072+
annotationCanvases
10721073
) {
10731074
this.ctx = canvasCtx;
10741075
this.current = new CanvasExtraState(
@@ -1100,6 +1101,7 @@ class CanvasGraphics {
11001101
this.optionalContentConfig = optionalContentConfig;
11011102
this.cachedCanvases = new CachedCanvases(this.canvasFactory);
11021103
this.cachedPatterns = new Map();
1104+
this.annotationCanvases = annotationCanvases;
11031105
if (canvasCtx) {
11041106
// NOTE: if mozCurrentTransform is polyfilled, then the current state of
11051107
// the transformation must already be set in canvasCtx._transformMatrix.
@@ -2691,27 +2693,63 @@ class CanvasGraphics {
26912693
this.restore();
26922694
}
26932695

2694-
beginAnnotation(id, rect, transform, matrix) {
2696+
beginAnnotation(id, rect, transform, matrix, hasOwnCanvas) {
26952697
this.save();
2696-
resetCtxToDefault(this.ctx);
2697-
this.current = new CanvasExtraState(
2698-
this.ctx.canvas.width,
2699-
this.ctx.canvas.height
2700-
);
27012698

27022699
if (Array.isArray(rect) && rect.length === 4) {
27032700
const width = rect[2] - rect[0];
27042701
const height = rect[3] - rect[1];
2705-
this.ctx.rect(rect[0], rect[1], width, height);
2706-
this.clip();
2707-
this.endPath();
2702+
2703+
if (hasOwnCanvas && this.annotationCanvases) {
2704+
transform = transform.slice();
2705+
transform[4] -= rect[0];
2706+
transform[5] -= rect[1];
2707+
2708+
rect = rect.slice();
2709+
rect[0] = rect[1] = 0;
2710+
rect[2] = width;
2711+
rect[3] = height;
2712+
2713+
const currentTransform = this.ctx.mozCurrentTransform;
2714+
const scale = Math.abs(currentTransform[0] || currentTransform[1]);
2715+
const canvasWidth = Math.ceil(width * scale);
2716+
const canvasHeight = Math.ceil(height * scale);
2717+
2718+
this.annotationCanvas = this.canvasFactory.create(
2719+
canvasWidth,
2720+
canvasHeight
2721+
);
2722+
this.annotationCanvases.set(id, this.annotationCanvas.canvas);
2723+
this.annotationCanvas.savedCtx = this.ctx;
2724+
this.ctx = this.annotationCanvas.context;
2725+
this.ctx.setTransform(scale, 0, 0, -scale, 0, height * scale);
2726+
addContextCurrentTransform(this.ctx);
2727+
2728+
resetCtxToDefault(this.ctx);
2729+
} else {
2730+
resetCtxToDefault(this.ctx);
2731+
2732+
this.ctx.rect(rect[0], rect[1], width, height);
2733+
this.clip();
2734+
this.endPath();
2735+
}
27082736
}
27092737

2738+
this.current = new CanvasExtraState(
2739+
this.ctx.canvas.width,
2740+
this.ctx.canvas.height
2741+
);
2742+
27102743
this.transform.apply(this, transform);
27112744
this.transform.apply(this, matrix);
27122745
}
27132746

27142747
endAnnotation() {
2748+
if (this.annotationCanvas) {
2749+
this.ctx = this.annotationCanvas.savedCtx;
2750+
delete this.annotationCanvas.savedCtx;
2751+
delete this.annotationCanvas;
2752+
}
27152753
this.restore();
27162754
}
27172755

0 commit comments

Comments
 (0)