From 69b72078c0134b09ae2a621092dca3fda4a82624 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Sun, 28 Oct 2012 15:10:34 -0500 Subject: [PATCH] Separate page objects/images from the fonts; does not store large images --- src/api.js | 61 ++++++++++++++++++++++++++++++++++-------------- src/canvas.js | 13 ++++++++--- src/core.js | 60 ++++++++++++++--------------------------------- src/evaluator.js | 14 ++++++----- src/obj.js | 18 ++++++++++++-- src/pattern.js | 2 +- src/worker.js | 2 +- 7 files changed, 97 insertions(+), 73 deletions(-) diff --git a/src/api.js b/src/api.js index ebe1d9bca..02bb87941 100644 --- a/src/api.js +++ b/src/api.js @@ -172,8 +172,10 @@ var PDFPageProxy = (function PDFPageProxyClosure() { this.transport = transport; this.stats = new StatTimer(); this.stats.enabled = !!globalScope.PDFJS.enableStats; - this.objs = transport.objs; + this.commonObjs = transport.commonObjs; + this.objs = new PDFObjects(); this.renderInProgress = false; + this.cleanupAfterRender = false; } PDFPageProxy.prototype = { /** @@ -263,9 +265,10 @@ var PDFPageProxy = (function PDFPageProxyClosure() { var self = this; function complete(error) { self.renderInProgress = false; - if (self.destroyed) { - delete self.operatorList; + if (self.destroyed || self.cleanupAfterRender) { delete self.displayReadyPromise; + delete self.operatorList; + self.objs.clear(); } if (error) @@ -283,7 +286,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { return; } - var gfx = new CanvasGraphics(params.canvasContext, + var gfx = new CanvasGraphics(params.canvasContext, this.commonObjs, this.objs, params.textLayer); try { this.display(gfx, params.viewport, complete, continueCallback); @@ -329,7 +332,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { // Convert the font names to the corresponding font obj. var fontObjs = []; for (var i = 0, ii = fonts.length; i < ii; i++) { - var obj = this.objs.objs[fonts[i]].data; + var obj = this.commonObjs.getData(fonts[i]); if (obj.error) { warn('Error during font loading: ' + obj.error); continue; @@ -423,6 +426,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { if (!this.renderInProgress) { delete this.operatorList; delete this.displayReadyPromise; + this.objs.clear(); } } }; @@ -434,7 +438,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { var WorkerTransport = (function WorkerTransportClosure() { function WorkerTransport(workerInitializedPromise, workerReadyPromise) { this.workerReadyPromise = workerReadyPromise; - this.objs = new PDFObjects(); + this.commonObjs = new PDFObjects(); this.pageCache = []; this.pagePromises = []; @@ -569,21 +573,13 @@ var WorkerTransport = (function WorkerTransportClosure() { page.startRenderingFromOperatorList(data.operatorList, depFonts); }, this); - messageHandler.on('obj', function transportObj(data) { + messageHandler.on('commonobj', function transportObj(data) { var id = data[0]; var type = data[1]; - if (this.objs.hasData(id)) + if (this.commonObjs.hasData(id)) return; switch (type) { - case 'JpegStream': - var imageData = data[2]; - loadJpegStream(id, imageData, this.objs); - break; - case 'Image': - var imageData = data[2]; - this.objs.resolve(id, imageData); - break; case 'Font': var exportedData = data[2]; @@ -594,10 +590,39 @@ var WorkerTransport = (function WorkerTransportClosure() { font = new ErrorFont(exportedData.error); else font = new Font(exportedData); - this.objs.resolve(id, font); + this.commonObjs.resolve(id, font); break; default: - error('Got unkown object type ' + type); + error('Got unknown common object type ' + type); + } + }, this); + + messageHandler.on('obj', function transportObj(data) { + var id = data[0]; + var pageIndex = data[1]; + var type = data[2]; + var pageProxy = this.pageCache[pageIndex]; + if (pageProxy.objs.hasData(id)) + return; + + switch (type) { + case 'JpegStream': + var imageData = data[3]; + loadJpegStream(id, imageData, pageProxy.objs); + break; + case 'Image': + var imageData = data[3]; + pageProxy.objs.resolve(id, imageData); + + // heuristics that will allow not to store large data + var MAX_IMAGE_SIZE_TO_STORE = 8000000; + if ('data' in imageData && + imageData.data.length > MAX_IMAGE_SIZE_TO_STORE) { + pageProxy.cleanupAfterRender = true; + } + break; + default: + error('Got unknown object type ' + type); } }, this); diff --git a/src/canvas.js b/src/canvas.js index 38111c7d3..69b1ba9cd 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -208,13 +208,14 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // before it stops and shedules a continue of execution. var kExecutionTime = 15; - function CanvasGraphics(canvasCtx, objs, textLayer) { + function CanvasGraphics(canvasCtx, commonObjs, objs, textLayer) { this.ctx = canvasCtx; this.current = new CanvasExtraState(); this.stateStack = []; this.pendingClip = null; this.res = null; this.xobjs = null; + this.commonObjs = commonObjs; this.objs = objs; this.textLayer = textLayer; if (canvasCtx) { @@ -283,6 +284,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var executionEndIdx; var endTime = Date.now() + kExecutionTime; + var commonObjs = this.commonObjs; var objs = this.objs; var fnName; var slowCommands = this.slowCommands; @@ -301,13 +303,18 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var deps = argsArray[i]; for (var n = 0, nn = deps.length; n < nn; n++) { var depObjId = deps[n]; + var common = depObjId.substring(0, 2) == 'g_'; // If the promise isn't resolved yet, add the continueCallback // to the promise and bail out. - if (!objs.isResolved(depObjId)) { + if (!common && !objs.isResolved(depObjId)) { objs.get(depObjId, continueCallback); return i; } + if (common && !commonObjs.isResolved(depObjId)) { + commonObjs.get(depObjId, continueCallback); + return i; + } } } @@ -620,7 +627,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.current.leading = -leading; }, setFont: function CanvasGraphics_setFont(fontRefName, size) { - var fontObj = this.objs.get(fontRefName); + var fontObj = this.commonObjs.get(fontRefName); var current = this.current; if (!fontObj) diff --git a/src/core.js b/src/core.js index e1e6bf3f9..41f41ca9d 100644 --- a/src/core.js +++ b/src/core.js @@ -97,8 +97,8 @@ globalScope.PDFJS.getPdf = getPdf; globalScope.PDFJS.pdfBug = false; var Page = (function PageClosure() { - function Page(xref, pageNumber, pageDict, ref) { - this.pageNumber = pageNumber; + function Page(xref, pageIndex, pageDict, ref) { + this.pageIndex = pageIndex; this.pageDict = pageDict; this.xref = xref; this.ref = ref; @@ -167,14 +167,11 @@ var Page = (function PageClosure() { } return shadow(this, 'rotate', rotate); }, - - getOperatorList: function Page_getOperatorList(handler, dependency) { - var xref = this.xref; + getContentStream: function Page_getContentStream() { var content = this.content; - var resources = this.resources; if (isArray(content)) { // fetching items - var streams = []; + var xref = this.xref; var i, n = content.length; var streams = []; for (i = 0; i < n; ++i) @@ -184,13 +181,19 @@ var Page = (function PageClosure() { content.reset(); } else if (!content) { // replacing non-existent page content with empty one - content = new Stream(new Uint8Array(0)); + content = new NullStream(); } - + return content; + }, + getOperatorList: function Page_getOperatorList(handler, dependency) { + var xref = this.xref; + var contentStream = this.getContentStream(); + var resources = this.resources; var pe = this.pe = new PartialEvaluator( - xref, handler, 'p' + this.pageNumber + '_'); + xref, handler, this.pageIndex, + 'p' + this.pageIndex + '_'); - return pe.getOperatorList(content, resources, dependency); + return pe.getOperatorList(contentStream, resources, dependency); }, extractTextContent: function Page_extractTextContent() { var handler = { @@ -199,40 +202,13 @@ var Page = (function PageClosure() { }; var xref = this.xref; - var content = xref.fetchIfRef(this.content); + var contentStream = this.getContentStream(); var resources = xref.fetchIfRef(this.resources); - if (isArray(content)) { - // fetching items - var i, n = content.length; - var streams = []; - for (i = 0; i < n; ++i) - streams.push(xref.fetchIfRef(content[i])); - content = new StreamsSequenceStream(streams); - } else if (isStream(content)) { - content.reset(); - } var pe = new PartialEvaluator( - xref, handler, 'p' + this.pageNumber + '_'); - return pe.getTextContent(content, resources); - }, - - ensureFonts: function Page_ensureFonts(fonts, callback) { - this.stats.time('Font Loading'); - // Convert the font names to the corresponding font obj. - for (var i = 0, ii = fonts.length; i < ii; i++) { - fonts[i] = this.objs.objs[fonts[i]].data; - } - - // Load all the fonts - FontLoader.bind( - fonts, - function pageEnsureFontsFontObjs(fontObjs) { - this.stats.timeEnd('Font Loading'); - - callback.call(this); - }.bind(this) - ); + xref, handler, this.pageIndex, + 'p' + this.pageIndex + '_'); + return pe.getTextContent(contentStream, resources); }, getLinks: function Page_getLinks() { var links = []; diff --git a/src/evaluator.js b/src/evaluator.js index 353eabd67..bf1594352 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -18,12 +18,13 @@ 'use strict'; var PartialEvaluator = (function PartialEvaluatorClosure() { - function PartialEvaluator(xref, handler, uniquePrefix) { + function PartialEvaluator(xref, handler, pageIndex, uniquePrefix) { this.state = new EvalState(); this.stateStack = []; this.xref = xref; this.handler = handler; + this.pageIndex = pageIndex; this.uniquePrefix = uniquePrefix; this.objIdCounter = 0; this.fontIdCounter = 0; @@ -151,7 +152,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { if (!isDict(font)) { return { translated: new ErrorFont('Font ' + fontName + ' is not available'), - loadedName: 'font_' + this.uniquePrefix + this.fontIdCounter + loadedName: 'g_font_' + this.uniquePrefix + this.fontIdCounter }; } @@ -159,7 +160,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { if (!loadedName) { // keep track of each font we translated so the caller can // load them asynchronously before calling display on a page - loadedName = 'font_' + this.uniquePrefix + this.fontIdCounter; + loadedName = 'g_font_' + this.uniquePrefix + this.fontIdCounter; font.loadedName = loadedName; var translated; @@ -197,6 +198,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var self = this; var xref = this.xref; var handler = this.handler; + var pageIndex = this.pageIndex; var uniquePrefix = this.uniquePrefix || ''; function insertDependency(depList) { @@ -217,7 +219,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { if (!font.sent) { var data = font.translated.exportData(); - handler.send('obj', [ + handler.send('commonobj', [ loadedName, 'Font', data @@ -271,7 +273,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { image.isNativelySupported(xref, resources)) { // These JPEGs don't need any more processing so we can just send it. fn = 'paintJpegXObject'; - handler.send('obj', [objId, 'JpegStream', image.getIR()]); + handler.send('obj', [objId, pageIndex, 'JpegStream', image.getIR()]); return; } @@ -287,7 +289,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }; var pixels = imgData.data; imageObj.fillRgbaBuffer(pixels, drawWidth, drawHeight); - handler.send('obj', [objId, 'Image', imgData]); + handler.send('obj', [objId, pageIndex, 'Image', imgData]); }, handler, xref, resources, image, inline); } diff --git a/src/obj.js b/src/obj.js index 5f246f2c9..a49177881 100644 --- a/src/obj.js +++ b/src/obj.js @@ -754,8 +754,6 @@ var PDFObjects = (function PDFObjectsClosure() { } PDFObjects.prototype = { - objs: null, - /** * Internal function. * Ensures there is an object defined for `objId`. Stores `data` on the @@ -832,6 +830,18 @@ var PDFObjects = (function PDFObjectsClosure() { } }, + /** + * Returns the data of `objId` if object exists, null otherwise. + */ + getData: function PDFObjects_getData(objId) { + var objs = this.objs; + if (!objs[objId] || !objs[objId].hasData) { + return null; + } else { + return objs[objId].data; + } + }, + /** * Sets the data of an object but *doesn't* resolve it. */ @@ -839,6 +849,10 @@ var PDFObjects = (function PDFObjectsClosure() { // Watchout! If you call `this.ensureObj(objId, data)` you're going to // create a *resolved* promise which shouldn't be the case! this.ensureObj(objId).data = data; + }, + + clear: function PDFObjects_clear() { + this.objs = {}; } }; return PDFObjects; diff --git a/src/pattern.js b/src/pattern.js index 45c4a3279..e366c0942 100644 --- a/src/pattern.js +++ b/src/pattern.js @@ -286,7 +286,7 @@ var TilingPattern = (function TilingPatternClosure() { // set the new canvas element context as the graphics context var tmpCtx = tmpCanvas.getContext('2d'); - var graphics = new CanvasGraphics(tmpCtx, objs); + var graphics = new CanvasGraphics(tmpCtx, null, objs); switch (paintType) { case PaintType.COLORED: diff --git a/src/worker.js b/src/worker.js index 5c9ce60c7..33caa4049 100644 --- a/src/worker.js +++ b/src/worker.js @@ -259,7 +259,7 @@ var WorkerMessageHandler = { var fonts = {}; for (var i = 0, ii = dependency.length; i < ii; i++) { var dep = dependency[i]; - if (dep.indexOf('font_') == 0) { + if (dep.indexOf('g_font_') == 0) { fonts[dep] = true; } }