Skip to content

Commit

Permalink
tiled canvas
Browse files Browse the repository at this point in the history
  • Loading branch information
AkiSakurai committed Dec 3, 2023
1 parent 6b3ae44 commit e67bf68
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 52 deletions.
7 changes: 6 additions & 1 deletion src/display/canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -1818,7 +1818,12 @@ class CanvasGraphics {
}

const intersect = this.current.getClippedPathBoundingBox();
if (this.contentVisible && intersect !== null) {
if (
this.contentVisible &&
intersect !== null &&
intersect[2] - intersect[0] > 0 &&
intersect[3] - intersect[1] > 0
) {
if (this.pendingEOFill) {
ctx.fill("evenodd");
this.pendingEOFill = false;
Expand Down
1 change: 1 addition & 0 deletions web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ const PDFViewerApplication = {
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
isOffscreenCanvasSupported,
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
maxTiles: AppOptions.get("maxTiles"),
enablePermissions: AppOptions.get("enablePermissions"),
pageColors,
});
Expand Down
5 changes: 5 additions & 0 deletions web/app_options.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ const defaultOptions = {
value: 16777216,
kind: OptionKind.VIEWER,
},
maxTiles: {
/** @type {number} */
value: 512,
kind: OptionKind.VIEWER,
},
forcePageColors: {
/** @type {boolean} */
value: false,
Expand Down
118 changes: 71 additions & 47 deletions web/pdf_page_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
roundToDivide,
TextLayerMode,
} from "./ui_utils.js";

import { AnnotationEditorLayerBuilder } from "./annotation_editor_layer_builder.js";
import { AnnotationLayerBuilder } from "./annotation_layer_builder.js";
import { compatibilityParams } from "./app_options.js";
Expand All @@ -49,6 +50,7 @@ import { StructTreeLayerBuilder } from "./struct_tree_layer_builder.js";
import { TextAccessibilityManager } from "./text_accessibility.js";
import { TextHighlighter } from "./text_highlighter.js";
import { TextLayerBuilder } from "./text_layer_builder.js";
import { TileLayer } from "./tile_canvas.js";
import { XfaLayerBuilder } from "./xfa_layer_builder.js";

/**
Expand Down Expand Up @@ -77,6 +79,8 @@ import { XfaLayerBuilder } from "./xfa_layer_builder.js";
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
* total pixels, i.e. width * height. Use `-1` for no limit, or `0` for
* CSS-only zooming. The default value is 4096 * 4096 (16 mega-pixels).
* @property {number} [maxTiles] - The maximum supported tiles,
* default is 1.
* @property {Object} [pageColors] - Overwrites background and foreground colors
* with user defined ones in order to improve readability in high contrast
* mode.
Expand All @@ -86,6 +90,7 @@ import { XfaLayerBuilder } from "./xfa_layer_builder.js";
*/

const MAX_CANVAS_PIXELS = compatibilityParams.maxCanvasPixels || 16777216;
const MAX_TILE_SIZE = compatibilityParams.maxTiles || 1;

const DEFAULT_LAYER_PROPERTIES =
typeof PDFJSDev === "undefined" || !PDFJSDev.test("COMPONENTS")
Expand Down Expand Up @@ -157,6 +162,7 @@ class PDFPageView {
this.isOffscreenCanvasSupported =
options.isOffscreenCanvasSupported ?? true;
this.maxCanvasPixels = options.maxCanvasPixels ?? MAX_CANVAS_PIXELS;
this.maxTiles = options.maxTiles ?? MAX_TILE_SIZE;
this.pageColors = options.pageColors || null;

this.eventBus = options.eventBus;
Expand Down Expand Up @@ -448,7 +454,7 @@ class PDFPageView {
if (treeDom) {
// Pause translation when inserting the structTree in the DOM.
this.l10n.pause();
this.canvas?.append(treeDom);
this.tileLayer?.element.append(treeDom);
this.l10n.resume();
}
this.structTreeLayer?.show();
Expand All @@ -471,16 +477,16 @@ class PDFPageView {
if (!this.zoomLayer) {
return;
}
const zoomLayerCanvas = this.zoomLayer.firstChild;
this.#viewportMap.delete(zoomLayerCanvas);
const zoomLayerDiv = this.zoomLayer.element;
this.#viewportMap.delete(zoomLayerDiv);

// Zeroing the width and height causes Firefox to release graphics
// resources immediately, which can greatly reduce memory consumption.
zoomLayerCanvas.width = 0;
zoomLayerCanvas.height = 0;
this.zoomLayer.clear();

if (removeFromDOM) {
// Note: `ChildNode.remove` doesn't throw if the parent node is undefined.
this.zoomLayer.remove();
this.zoomLayer.element.remove();
}
this.zoomLayer = null;
}
Expand Down Expand Up @@ -543,13 +549,12 @@ class PDFPageView {
this.structTreeLayer?.hide();

if (!zoomLayerNode) {
if (this.canvas) {
this.#viewportMap.delete(this.canvas);
if (this.tileLayer) {
this.#viewportMap.delete(this.tileLayer.element);
// Zeroing the width and height causes Firefox to release graphics
// resources immediately, which can greatly reduce memory consumption.
this.canvas.width = 0;
this.canvas.height = 0;
delete this.canvas;
this.tileLayer.clear();
delete this.tileLayer;
}
this._resetZoomLayer();
}
Expand Down Expand Up @@ -610,7 +615,7 @@ class PDFPageView {
this._container?.style.setProperty("--scale-factor", this.viewport.scale);
}

if (this.canvas) {
if (this.tileLayer) {
let onlyCssZoom = false;
if (this.#hasRestrictedScaling) {
if (
Expand Down Expand Up @@ -653,7 +658,7 @@ class PDFPageView {
}

this.cssTransform({
target: this.canvas,
target: this.tileLayer.element,
redrawAnnotationLayer: true,
redrawAnnotationEditorLayer: true,
redrawXfaLayer: true,
Expand All @@ -675,13 +680,13 @@ class PDFPageView {
});
return;
}
if (!this.zoomLayer && !this.canvas.hidden) {
this.zoomLayer = this.canvas.parentNode;
this.zoomLayer.style.position = "absolute";
if (!this.zoomLayer && !this.tileLayer.hidden) {
this.zoomLayer = this.tileLayer;
this.zoomLayer.element.parentNode.style.position = "absolute";
}
}
if (this.zoomLayer) {
this.cssTransform({ target: this.zoomLayer.firstChild });
this.cssTransform({ target: this.zoomLayer.element });
}
this.reset({
keepZoomLayer: true,
Expand Down Expand Up @@ -753,9 +758,9 @@ class PDFPageView {
// Scale target (canvas), its wrapper and page container.
if (
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
!(target instanceof HTMLCanvasElement)
!(target instanceof HTMLDivElement)
) {
throw new Error("Expected `target` to be a canvas.");
throw new Error("Expected `target` to be a HTMLDivElement.");
}
if (!target.hasAttribute("zooming")) {
target.setAttribute("zooming", true);
Expand All @@ -777,6 +782,7 @@ class PDFPageView {
scaleX = height / width;
scaleY = width / height;
}

target.style.transform = `rotate(${relativeRotation}deg) scale(${scaleX}, ${scaleY})`;
}

Expand Down Expand Up @@ -935,27 +941,7 @@ class PDFPageView {
};

const { width, height } = viewport;
const canvas = document.createElement("canvas");
canvas.setAttribute("role", "presentation");

// Keep the canvas hidden until the first draw callback, or until drawing
// is complete when `!this.renderingQueue`, to prevent black flickering.
canvas.hidden = true;
const hasHCM = !!(pageColors?.background && pageColors?.foreground);

let showCanvas = isLastShow => {
// In HCM, a final filter is applied on the canvas which means that
// before it's applied we've normal colors. Consequently, to avoid to have
// a final flash we just display it once all the drawing is done.
if (!hasHCM || isLastShow) {
canvas.hidden = false;
showCanvas = null; // Only invoke the function once.
}
};
canvasWrapper.append(canvas);
this.canvas = canvas;

const ctx = canvas.getContext("2d", { alpha: false });
const outputScale = (this.outputScale = new OutputScale());

if (
Expand All @@ -969,8 +955,11 @@ class PDFPageView {
outputScale.sy *= invScale;
this.#hasRestrictedScaling = true;
} else if (this.maxCanvasPixels > 0) {
const maxTiledCanvasPixels = this.maxCanvasPixels * this.maxTiles;

console.log(this.maxCanvasPixels, this.maxTiles);
const pixelsInViewport = width * height;
const maxScale = Math.sqrt(this.maxCanvasPixels / pixelsInViewport);
const maxScale = Math.sqrt(maxTiledCanvasPixels / pixelsInViewport);
if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
outputScale.sx = maxScale;
outputScale.sy = maxScale;
Expand All @@ -982,29 +971,60 @@ class PDFPageView {
const sfx = approximateFraction(outputScale.sx);
const sfy = approximateFraction(outputScale.sy);

canvas.width = roundToDivide(width * outputScale.sx, sfx[0]);
canvas.height = roundToDivide(height * outputScale.sy, sfy[0]);
const { style } = canvas;
const canvasWidth = roundToDivide(width * outputScale.sx, sfx[0]);
const canvasHeight = roundToDivide(height * outputScale.sy, sfy[0]);

const tileLayer = new TileLayer(
canvasWidth,
canvasHeight,
this.maxCanvasPixels,
sfx,
sfy
);

tileLayer.setAttribute("role", "presentation");

// Keep the canvas hidden until the first draw callback, or until drawing
// is complete when `!this.renderingQueue`, to prevent black flickering.
tileLayer.hidden = true;
const hasHCM = !!(pageColors?.background && pageColors?.foreground);

let showCanvas = isLastShow => {
// In HCM, a final filter is applied on the canvas which means that
// before it's applied we've normal colors. Consequently, to avoid to have
// a final flash we just display it once all the drawing is done.
if (!hasHCM || isLastShow) {
tileLayer.hidden = false;
showCanvas = null; // Only invoke the function once.
}
};
canvasWrapper.append(tileLayer.element);
this.tileLayer = tileLayer;

const { style } = tileLayer.element;
style.width = roundToDivide(width, sfx[1]) + "px";
style.height = roundToDivide(height, sfy[1]) + "px";

// Add the viewport so it's known what it was originally drawn with.
this.#viewportMap.set(canvas, viewport);
this.#viewportMap.set(tileLayer.element, viewport);

// Rendering area
const transform = outputScale.scaled
? [outputScale.sx, 0, 0, outputScale.sy, 0, 0]
: null;

const renderContext = {
canvasContext: ctx,
transform,
viewport,
annotationMode: this.#annotationMode,
optionalContentConfigPromise: this._optionalContentConfigPromise,
annotationCanvasMap: this._annotationCanvasMap,
pageColors,
};
const renderTask = (this.renderTask = this.pdfPage.render(renderContext));
const renderTask = (this.renderTask = tileLayer.render(
this.pdfPage,
renderContext
));
renderTask.onContinue = renderContinueCallback;

const resultPromise = renderTask.promise.then(
Expand Down Expand Up @@ -1102,8 +1122,12 @@ class PDFPageView {
get thumbnailCanvas() {
const { directDrawing, initialOptionalContent, regularAnnotations } =
this.#useThumbnailCanvas;
const canvas =
this.tileLayer?.tiles?.length === 1
? this.tileLayer.tiles[0].canvas
: null;
return directDrawing && initialOptionalContent && regularAnnotations
? this.canvas
? canvas
: null;
}
}
Expand Down
8 changes: 4 additions & 4 deletions web/pdf_viewer.css
Original file line number Diff line number Diff line change
Expand Up @@ -146,20 +146,20 @@
}
/*#endif*/

.pdfViewer .page canvas {
.pdfViewer .page .tileLayer {
margin: 0;
display: block;
}

.pdfViewer .page canvas .structTree {
.pdfViewer .page .tileLayer .structTree {
contain: strict;
}

.pdfViewer .page canvas[hidden] {
.pdfViewer .page .tileLayer[hidden] {
display: none;
}

.pdfViewer .page canvas[zooming] {
.pdfViewer .page .tileLayer[zooming] {
width: 100%;
height: 100%;
}
Expand Down
2 changes: 2 additions & 0 deletions web/pdf_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ class PDFViewer {
this.isOffscreenCanvasSupported =
options.isOffscreenCanvasSupported ?? true;
this.maxCanvasPixels = options.maxCanvasPixels;
this.maxTiles = options.maxTiles;
this.l10n = options.l10n || NullL10n;
this.#enablePermissions = options.enablePermissions || false;
this.pageColors = options.pageColors || null;
Expand Down Expand Up @@ -910,6 +911,7 @@ class PDFViewer {
imageResourcesPath: this.imageResourcesPath,
isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
maxCanvasPixels: this.maxCanvasPixels,
maxTiles: this.maxTiles,
pageColors: this.pageColors,
l10n: this.l10n,
layerProperties: this._layerProperties,
Expand Down
Loading

0 comments on commit e67bf68

Please sign in to comment.