diff --git a/src/display/api.js b/src/display/api.js index c4bea7cd2..450483e58 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -658,6 +658,8 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { * calling of PDFPage.getViewport method. * @property {string} intent - Rendering intent, can be 'display' or 'print' * (default value is 'display'). + * @property {Array} transform - (optional) Additional transform, applied + * just before viewport transform. * @property {Object} imageLayer - (optional) An object that has beginLayout, * endLayout and appendImage functions. * @property {function} continueCallback - (deprecated) A function that will be @@ -1730,7 +1732,7 @@ var InternalRenderTask = (function InternalRenderTaskClosure() { this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs, this.objs, params.imageLayer); - this.gfx.beginDrawing(params.viewport, transparency); + this.gfx.beginDrawing(params.transform, params.viewport, transparency); this.operatorListIdx = 0; this.graphicsReady = true; if (this.graphicsReadyCallback) { diff --git a/src/display/canvas.js b/src/display/canvas.js index ad58f84f1..2fcf81bc5 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -160,13 +160,15 @@ function addContextCurrentTransform(ctx) { } var CachedCanvases = (function CachedCanvasesClosure() { - var cache = {}; - return { + function CachedCanvases() { + this.cache = Object.create(null); + } + CachedCanvases.prototype = { getCanvas: function CachedCanvases_getCanvas(id, width, height, trackTransform) { var canvasEntry; - if (cache[id] !== undefined) { - canvasEntry = cache[id]; + if (this.cache[id] !== undefined) { + canvasEntry = this.cache[id]; canvasEntry.canvas.width = width; canvasEntry.canvas.height = height; // reset canvas transform for emulated mozCurrentTransform, if needed @@ -177,21 +179,22 @@ var CachedCanvases = (function CachedCanvasesClosure() { if (trackTransform) { addContextCurrentTransform(ctx); } - cache[id] = canvasEntry = {canvas: canvas, context: ctx}; + this.cache[id] = canvasEntry = {canvas: canvas, context: ctx}; } return canvasEntry; }, clear: function () { - for (var id in cache) { - var canvasEntry = cache[id]; + for (var id in this.cache) { + var canvasEntry = this.cache[id]; // Zeroing the width and height causes Firefox to release graphics // resources immediately, which can greatly reduce memory consumption. canvasEntry.canvas.width = 0; canvasEntry.canvas.height = 0; - delete cache[id]; + delete this.cache[id]; } } }; + return CachedCanvases; })(); function compileType3Glyph(imgData) { @@ -429,6 +432,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.smaskStack = []; this.smaskCounter = 0; this.tempSMask = null; + this.cachedCanvases = new CachedCanvases(); if (canvasCtx) { // NOTE: if mozCurrentTransform is polyfilled, then the current state of // the transformation must already be set in canvasCtx._transformMatrix. @@ -705,28 +709,39 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { CanvasGraphics.prototype = { - beginDrawing: function CanvasGraphics_beginDrawing(viewport, transparency) { + beginDrawing: function CanvasGraphics_beginDrawing(transform, viewport, + transparency) { // For pdfs that use blend modes we have to clear the canvas else certain // blend modes can look wrong since we'd be blending with a white // backdrop. The problem with a transparent backdrop though is we then - // don't get sub pixel anti aliasing on text, so we fill with white if - // we can. + // don't get sub pixel anti aliasing on text, creating temporary + // transparent canvas when we have blend modes. var width = this.ctx.canvas.width; var height = this.ctx.canvas.height; - if (transparency) { - this.ctx.clearRect(0, 0, width, height); - } else { - this.ctx.mozOpaque = true; - this.ctx.save(); - this.ctx.fillStyle = 'rgb(255, 255, 255)'; - this.ctx.fillRect(0, 0, width, height); - this.ctx.restore(); - } - - var transform = viewport.transform; this.ctx.save(); - this.ctx.transform.apply(this.ctx, transform); + this.ctx.fillStyle = 'rgb(255, 255, 255)'; + this.ctx.fillRect(0, 0, width, height); + this.ctx.restore(); + + if (transparency) { + var transparentCanvas = this.cachedCanvases.getCanvas( + 'transparent', width, height, true); + this.compositeCtx = this.ctx; + this.transparentCanvas = transparentCanvas.canvas; + this.ctx = transparentCanvas.context; + this.ctx.save(); + // The transform can be applied before rendering, transferring it to + // the new canvas. + this.ctx.transform.apply(this.ctx, + this.compositeCtx.mozCurrentTransform); + } + + this.ctx.save(); + if (transform) { + this.ctx.transform.apply(this.ctx, transform); + } + this.ctx.transform.apply(this.ctx, viewport.transform); this.baseTransform = this.ctx.mozCurrentTransform.slice(); @@ -808,7 +823,14 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { endDrawing: function CanvasGraphics_endDrawing() { this.ctx.restore(); - CachedCanvases.clear(); + + if (this.transparentCanvas) { + this.ctx = this.compositeCtx; + this.ctx.drawImage(this.transparentCanvas, 0, 0); + this.transparentCanvas = null; + } + + this.cachedCanvases.clear(); WebGLUtils.clear(); if (this.imageLayer) { @@ -922,7 +944,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var drawnWidth = activeSMask.canvas.width; var drawnHeight = activeSMask.canvas.height; var cacheId = 'smaskGroupAt' + this.groupLevel; - var scratchCanvas = CachedCanvases.getCanvas( + var scratchCanvas = this.cachedCanvases.getCanvas( cacheId, drawnWidth, drawnHeight, true); var currentCtx = this.ctx; @@ -1706,7 +1728,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // Using two cache entries is case if masks are used one after another. cacheId += '_smask_' + ((this.smaskCounter++) % 2); } - var scratchCanvas = CachedCanvases.getCanvas( + var scratchCanvas = this.cachedCanvases.getCanvas( cacheId, drawnWidth, drawnHeight, true); var groupCtx = scratchCanvas.context; @@ -1847,7 +1869,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { return; } - var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height); + var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas', + width, height); var maskCtx = maskCanvas.context; maskCtx.save(); @@ -1872,7 +1895,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var fillColor = this.current.fillColor; var isPatternFill = this.current.patternFill; - var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height); + var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas', + width, height); var maskCtx = maskCanvas.context; maskCtx.save(); @@ -1907,7 +1931,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var image = images[i]; var width = image.width, height = image.height; - var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height); + var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas', + width, height); var maskCtx = maskCanvas.context; maskCtx.save(); @@ -1980,7 +2005,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { if (imgData instanceof HTMLElement || !imgData.data) { imgToPaint = imgData; } else { - tmpCanvas = CachedCanvases.getCanvas('inlineImage', width, height); + tmpCanvas = this.cachedCanvases.getCanvas('inlineImage', + width, height); var tmpCtx = tmpCanvas.context; putBinaryImageData(tmpCtx, imgData); imgToPaint = tmpCanvas.canvas; @@ -2002,7 +2028,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { newHeight = Math.ceil(paintHeight / 2); heightScale /= paintHeight / newHeight; } - tmpCanvas = CachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight); + tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId, + newWidth, newHeight); tmpCtx = tmpCanvas.context; tmpCtx.clearRect(0, 0, newWidth, newHeight); tmpCtx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight, @@ -2034,7 +2061,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var w = imgData.width; var h = imgData.height; - var tmpCanvas = CachedCanvases.getCanvas('inlineImage', w, h); + var tmpCanvas = this.cachedCanvases.getCanvas('inlineImage', w, h); var tmpCtx = tmpCanvas.context; putBinaryImageData(tmpCtx, imgData); diff --git a/src/display/pattern_helper.js b/src/display/pattern_helper.js index d8693aa40..bff9bda08 100644 --- a/src/display/pattern_helper.js +++ b/src/display/pattern_helper.js @@ -147,7 +147,7 @@ var createMeshCanvas = (function createMeshCanvasClosure() { } function createMeshCanvas(bounds, combinesScale, coords, colors, figures, - backgroundColor) { + backgroundColor, cachedCanvases) { // we will increase scale on some weird factor to let antialiasing take // care of "rough" edges var EXPECTED_SCALE = 1.1; @@ -181,11 +181,11 @@ var createMeshCanvas = (function createMeshCanvasClosure() { figures, context); // https://bugzilla.mozilla.org/show_bug.cgi?id=972126 - tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false); + tmpCanvas = cachedCanvases.getCanvas('mesh', width, height, false); tmpCanvas.context.drawImage(canvas, 0, 0); canvas = tmpCanvas.canvas; } else { - tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false); + tmpCanvas = cachedCanvases.getCanvas('mesh', width, height, false); var tmpCtx = tmpCanvas.context; var data = tmpCtx.createImageData(width, height); @@ -241,7 +241,8 @@ ShadingIRs.Mesh = { // Rasterizing on the main thread since sending/queue large canvases // might cause OOM. var temporaryPatternCanvas = createMeshCanvas(bounds, scale, coords, - colors, figures, shadingFill ? null : background); + colors, figures, shadingFill ? null : background, + owner.cachedCanvases); if (!shadingFill) { ctx.setTransform.apply(ctx, owner.baseTransform); @@ -344,7 +345,8 @@ var TilingPattern = (function TilingPatternClosure() { height = Math.min(Math.ceil(Math.abs(height * combinedScale[1])), MAX_PATTERN_SIZE); - var tmpCanvas = CachedCanvases.getCanvas('pattern', width, height, true); + var tmpCanvas = owner.cachedCanvases.getCanvas('pattern', + width, height, true); var tmpCtx = tmpCanvas.context; var graphics = new CanvasGraphics(tmpCtx, commonObjs, objs); graphics.groupLevel = owner.groupLevel; diff --git a/test/driver.js b/test/driver.js index 2802dde11..c6c295f88 100644 --- a/test/driver.js +++ b/test/driver.js @@ -48,6 +48,12 @@ var SimpleTextLayerBuilder = (function SimpleTextLayerBuilderClosure() { this.ctx = ctx; this.viewport = viewport; this.textCounter = 0; + + // clear canvas + ctx.save(); + ctx.fillStyle = 'rgb(255,255,255)'; + ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.restore(); } SimpleTextLayerBuilder.prototype = { @@ -269,6 +275,7 @@ var Driver = (function DriverClosure() { _nextPage: function Driver_nextPage(task, loadError) { var self = this; var failure = loadError || ''; + var ctx; if (!task.pdfDoc) { var dataUrl = this.canvas.toDataURL('image/png'); @@ -299,7 +306,11 @@ var Driver = (function DriverClosure() { // Empty the canvas this.canvas.width = 1; this.canvas.height = 1; - this._clearCanvas(); + ctx = this.canvas.getContext('2d', {alpha: false}); + ctx.save(); + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, 1, 1); + ctx.restore(); this._snapshot(task, ''); return; @@ -309,7 +320,8 @@ var Driver = (function DriverClosure() { try { this._log(' Loading page ' + task.pageNum + '/' + task.pdfDoc.numPages + '... '); - var ctx = this.canvas.getContext('2d'); + this.canvas.mozOpaque = true; + ctx = this.canvas.getContext('2d', {alpha: false}); task.pdfDoc.getPage(task.pageNum).then(function(page) { var viewport = page.getViewport(PDF_TO_CSS_UNITS); self.canvas.width = viewport.width; @@ -369,7 +381,7 @@ var Driver = (function DriverClosure() { }, _clearCanvas: function Driver_clearCanvas() { - var ctx = this.canvas.getContext('2d'); + var ctx = this.canvas.getContext('2d', {alpha: false}); ctx.beginPath(); ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); }, diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index b75f1f43c..561ac8116 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -164,8 +164,7 @@ var PDFPageView = (function PDFPageViewClosure() { var isScalingRestricted = false; if (this.canvas && PDFJS.maxCanvasPixels > 0) { - var ctx = this.canvas.getContext('2d'); - var outputScale = getOutputScale(ctx); + var outputScale = this.outputScale; var pixelsInViewport = this.viewport.width * this.viewport.height; var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport); if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) * @@ -309,6 +308,7 @@ var PDFPageView = (function PDFPageViewClosure() { var canvas = document.createElement('canvas'); canvas.id = 'page' + this.id; + canvas.setAttribute('hidden', 'hidden'); canvasWrapper.appendChild(canvas); if (this.annotationLayer && this.annotationLayer.div) { // annotationLayer needs to stay on top @@ -316,10 +316,15 @@ var PDFPageView = (function PDFPageViewClosure() { } else { div.appendChild(canvasWrapper); } + var isCanvasHidden = true; this.canvas = canvas; - var ctx = canvas.getContext('2d'); +//#if MOZCENTRAL || FIREFOX || GENERIC + canvas.mozOpaque = true; +//#endif + var ctx = canvas.getContext('2d', {alpha: false}); var outputScale = getOutputScale(ctx); + this.outputScale = outputScale; if (PDFJS.useOnlyCssZoom) { var actualSizeViewport = viewport.clone({scale: CSS_UNITS}); @@ -372,14 +377,6 @@ var PDFPageView = (function PDFPageViewClosure() { } this.textLayer = textLayer; - if (outputScale.scaled) { -//#if !(MOZCENTRAL || FIREFOX) - // Used by the mozCurrentTransform polyfill in src/display/canvas.js. - ctx._transformMatrix = [outputScale.sx, 0, 0, outputScale.sy, 0, 0]; -//#endif - ctx.scale(outputScale.sx, outputScale.sy); - } - var resolveRenderPromise, rejectRenderPromise; var promise = new Promise(function (resolve, reject) { resolveRenderPromise = resolve; @@ -459,12 +456,19 @@ var PDFPageView = (function PDFPageViewClosure() { }; return; } + if (isCanvasHidden) { + self.canvas.removeAttribute('hidden'); + isCanvasHidden = false; + } cont(); }; } + var transform = !outputScale.scaled ? null : + [outputScale.sx, 0, 0, outputScale.sy, 0, 0]; var renderContext = { canvasContext: ctx, + transform: transform, viewport: this.viewport, // intent: 'default', // === 'display' }; diff --git a/web/pdf_thumbnail_view.js b/web/pdf_thumbnail_view.js index ec754b28e..13bcd5b1f 100644 --- a/web/pdf_thumbnail_view.js +++ b/web/pdf_thumbnail_view.js @@ -44,7 +44,10 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() { // Since this is a temporary canvas, we need to fill the canvas with a white // background ourselves. |_getPageDrawContext| uses CSS rules for this. - var ctx = tempCanvas.getContext('2d'); +//#if MOZCENTRAL || FIREFOX || GENERIC + tempCanvas.mozOpaque = true; +//#endif + var ctx = tempCanvas.getContext('2d', {alpha: false}); ctx.save(); ctx.fillStyle = 'rgb(255, 255, 255)'; ctx.fillRect(0, 0, width, height); @@ -184,7 +187,10 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() { var canvas = document.createElement('canvas'); this.canvas = canvas; - var ctx = canvas.getContext('2d'); +//#if MOZCENTRAL || FIREFOX || GENERIC + canvas.mozOpaque = true; +//#endif + var ctx = canvas.getContext('2d', {alpha: false}); var outputScale = getOutputScale(ctx); canvas.width = (this.canvasWidth * outputScale.sx) | 0; diff --git a/web/text_layer_builder.js b/web/text_layer_builder.js index baebc2bfd..0691c8c14 100644 --- a/web/text_layer_builder.js +++ b/web/text_layer_builder.js @@ -73,7 +73,10 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { var textDivs = this.textDivs; var textDivsLength = textDivs.length; var canvas = document.createElement('canvas'); - var ctx = canvas.getContext('2d'); +//#if MOZCENTRAL || FIREFOX || GENERIC + canvas.mozOpaque = true; +//#endif + var ctx = canvas.getContext('2d', {alpha: false}); // No point in rendering many divs as it would make the browser // unusable even after the divs are rendered.