From 98f3bab65c58fb4d1080cf57c1410209c9626502 Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Mon, 31 Oct 2011 16:49:18 -0400 Subject: [PATCH] Lazy rendering --- src/canvas.js | 122 ++++++++++++++++++++++++++++++-------------------- src/core.js | 7 ++- web/viewer.js | 2 +- 3 files changed, 78 insertions(+), 53 deletions(-) diff --git a/src/canvas.js b/src/canvas.js index d771fa15e..d5db79446 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -60,7 +60,7 @@ var CanvasGraphics = (function canvasGraphics() { // if we execute longer then `kExecutionTime`. var kExecutionTimeCheck = 500; - function constructor(canvasCtx, objs, textLayer, textScale) { + function constructor(canvasCtx, objs, textLayer) { this.ctx = canvasCtx; this.current = new CanvasExtraState(); this.stateStack = []; @@ -70,7 +70,6 @@ var CanvasGraphics = (function canvasGraphics() { this.ScratchCanvas = ScratchCanvas; this.objs = objs; this.textLayer = textLayer; - this.textScale = textScale; } var LINE_CAP_STYLES = ['butt', 'round', 'square']; @@ -98,6 +97,10 @@ var CanvasGraphics = (function canvasGraphics() { } this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height); this.textDivs = []; + this.textLayerQueue = []; + // Prevent textLayerQueue to be rendered while rendering a new page + if (this.textLayerTimer) + clearTimeout(this.textLayerTimer); }, executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR, @@ -152,17 +155,37 @@ var CanvasGraphics = (function canvasGraphics() { }, endDrawing: function canvasGraphicsEndDrawing() { + var self = this; this.ctx.restore(); - // Text selection-specific - var textLayer = this.textLayer; - var textDivs = this.textDivs; - for (var i = 0, length = textDivs.length; i < length; ++i) { - if (textDivs[i].dataset.textLength>1) { // avoid div by zero - textLayer.appendChild(textDivs[i]); - // Adjust div width to match canvas text width - textDivs[i].style.letterSpacing = ((textDivs[i].dataset.canvasWidth - textDivs[i].offsetWidth)/(textDivs[i].dataset.textLength-1)) + 'px'; + var textLayer = self.textLayer; + if (textLayer) { + var renderTextLayer = function canvasRenderTextLayer() { + var textDivs = self.textDivs; + for (var i = 0, length = textDivs.length; i < length; ++i) { + if (textDivs[i].dataset.textLength>1) { // avoid div by zero + textLayer.appendChild(textDivs[i]); + // Adjust div width (via letterSpacing) to match canvas text + // Due to the .offsetWidth calls, this is slow + textDivs[i].style.letterSpacing = + ((textDivs[i].dataset.canvasWidth + - textDivs[i].offsetWidth)/(textDivs[i].dataset.textLength-1)) + + 'px'; + } + } } + var textLayerQueue = self.textLayerQueue; + textLayerQueue.push(renderTextLayer); + + // Lazy textLayer rendering (to prevent UI hangs) + // Only render queue if activity has stopped, where "no activity" == + // "no beginDrawing() calls in the last N ms" + self.textLayerTimer = setTimeout(function renderTextLayerQueue(){ + // Render most recent (==most relevant) layers first + for (var i=textLayerQueue.length-1; i>=0; i--) { + textLayerQueue.pop().call(); + } + }, 500); } }, @@ -516,31 +539,32 @@ var CanvasGraphics = (function canvasGraphics() { var font = current.font; var text = {str:'', length:0, canvasWidth:0, spaceWidth:0, geom:{}}; - // Text selection-specific - text.spaceWidth = this.current.font.charsToGlyphs(' ')[0].width; - if (!text.spaceWidth>0) { - // Hack (space is sometimes not encoded) - text.spaceWidth = this.current.font.charsToGlyphs('i')[0].width; - } + if (textLayer) { + text.spaceWidth = this.current.font.charsToGlyphs(' ')[0].width; + if (!text.spaceWidth>0) { + // Hack (space is sometimes not encoded) + text.spaceWidth = this.current.font.charsToGlyphs('i')[0].width; + } - // Compute text.geom - // TODO: refactor the series of transformations below, and share it with showText() - ctx.save(); - ctx.transform.apply(ctx, current.textMatrix); - ctx.scale(1, -1); - ctx.translate(current.x, -1 * current.y); - ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX); - ctx.scale(1 / textHScale, 1); - var inv = ctx.mozCurrentTransform; - if (inv) { - var bl = Util.applyTransform([0, 0], inv); - var tr = Util.applyTransform([1, 1], inv); - text.geom.x = bl[0]; - text.geom.y = bl[1]; - text.geom.xFactor = tr[0] - bl[0]; - text.geom.yFactor = tr[1] - bl[1]; + // Compute text.geom + // TODO: refactor the series of transformations below, and share it with showText() + ctx.save(); + ctx.transform.apply(ctx, current.textMatrix); + ctx.scale(1, -1); + ctx.translate(current.x, -1 * current.y); + ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX); + ctx.scale(1 / textHScale, 1); + var inv = ctx.mozCurrentTransform; + if (inv) { + var bl = Util.applyTransform([0, 0], inv); + var tr = Util.applyTransform([1, 1], inv); + text.geom.x = bl[0]; + text.geom.y = bl[1]; + text.geom.xFactor = tr[0] - bl[0]; + text.geom.yFactor = tr[1] - bl[1]; + } + ctx.restore(); } - ctx.restore(); for (var i = 0; i < arrLength; ++i) { var e = arr[i]; @@ -548,26 +572,28 @@ var CanvasGraphics = (function canvasGraphics() { var spacingLength = -e * 0.001 * fontSize * textHScale; current.x += spacingLength; - // Text selection-specific - // Emulate arbitrary spacing via HTML spaces - text.canvasWidth += spacingLength; - if (e<0 && text.spaceWidth>0) { // avoid div by zero - var numFakeSpaces = Math.round(-e / text.spaceWidth); - for (var j = 0; j < numFakeSpaces; ++j) - text.str += ' '; - text.length += numFakeSpaces>0 ? 1 : 0; + if (textLayer) { + // Emulate precise spacing via HTML spaces + text.canvasWidth += spacingLength; + if (e<0 && text.spaceWidth>0) { // avoid div by zero + var numFakeSpaces = Math.round(-e / text.spaceWidth); + for (var j = 0; j < numFakeSpaces; ++j) + text.str += ' '; + text.length += numFakeSpaces>0 ? 1 : 0; + } } } else if (isString(e)) { var shownText = this.showText(e); - // Text selection-specific - if (shownText.chars === ' ') { - text.str += ' '; - } else { - text.str += shownText.chars; + if (textLayer) { + if (shownText.chars === ' ') { + text.str += ' '; + } else { + text.str += shownText.chars; + } + text.canvasWidth += shownText.width; + text.length += e.length; } - text.canvasWidth += shownText.width; - text.length += e.length; } else { malformed('TJ array element ' + e + ' is not string or num'); } diff --git a/src/core.js b/src/core.js index 7e7bb6ea8..4313959a8 100644 --- a/src/core.js +++ b/src/core.js @@ -7,7 +7,7 @@ var globalScope = (typeof window === 'undefined') ? this : window; var ERRORS = 0, WARNINGS = 1, TODOS = 5; var verbosity = WARNINGS; -var useWorker = false; +var useWorker = true; // The global PDFJS object exposes the API // In production, it will be declared outside a global wrapper @@ -157,7 +157,7 @@ var Page = (function pagePage() { IRQueue, fonts) { var self = this; this.IRQueue = IRQueue; - var gfx = new CanvasGraphics(this.ctx, this.objs, this.textLayer, this.textScale); + var gfx = new CanvasGraphics(this.ctx, this.objs, this.textLayer); var startTime = Date.now(); var displayContinuation = function pageDisplayContinuation() { @@ -306,11 +306,10 @@ var Page = (function pagePage() { } return links; }, - startRendering: function(ctx, callback, textLayer, textScale) { + startRendering: function(ctx, callback, textLayer) { this.ctx = ctx; this.callback = callback; this.textLayer = textLayer; - this.textScale = textScale; this.startRenderingTime = Date.now(); this.pdf.startRendering(this); diff --git a/web/viewer.js b/web/viewer.js index 523b7dc56..d8a59e651 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -491,7 +491,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, ctx.translate(-this.x * scale, -this.y * scale); stats.begin = Date.now(); - this.content.startRendering(ctx, this.updateStats, textLayer, scale); + this.content.startRendering(ctx, this.updateStats, textLayer); setupLinks(this.content, this.scale); div.setAttribute('data-loaded', true);