diff --git a/src/core/core.js b/src/core/core.js index 72aa2f4b4..94340f7c8 100644 --- a/src/core/core.js +++ b/src/core/core.js @@ -25,12 +25,13 @@ var Page = (function PageClosure() { - function Page(pdfManager, xref, pageIndex, pageDict, ref) { + function Page(pdfManager, xref, pageIndex, pageDict, ref, fontCache) { this.pdfManager = pdfManager; this.pageIndex = pageIndex; this.pageDict = pageDict; this.xref = xref; this.ref = ref; + this.fontCache = fontCache; this.idCounters = { obj: 0 }; @@ -160,7 +161,7 @@ var Page = (function PageClosure() { var partialEvaluator = new PartialEvaluator( pdfManager, this.xref, handler, this.pageIndex, 'p' + this.pageIndex + '_', - this.idCounters); + this.idCounters, this.fontCache); var dataPromises = Promise.all( [contentStreamPromise, resourcesPromise], reject); @@ -226,7 +227,7 @@ var Page = (function PageClosure() { var partialEvaluator = new PartialEvaluator( pdfManager, self.xref, handler, self.pageIndex, 'p' + self.pageIndex + '_', - self.idCounters); + self.idCounters, self.fontCache); var bidiTexts = partialEvaluator.getTextContent(contentStream, self.resources); @@ -496,6 +497,10 @@ var PDFDocument = (function PDFDocumentClosure() { getPage: function PDFDocument_getPage(pageIndex) { return this.catalog.getPage(pageIndex); + }, + + cleanup: function PDFDocument_cleanup() { + return this.catalog.cleanup(); } }; diff --git a/src/core/evaluator.js b/src/core/evaluator.js index bf1679880..5c4d49f7b 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -26,7 +26,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { function PartialEvaluator(pdfManager, xref, handler, pageIndex, - uniquePrefix, idCounters) { + uniquePrefix, idCounters, fontCache) { this.state = new EvalState(); this.stateStack = []; @@ -36,7 +36,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { this.pageIndex = pageIndex; this.uniquePrefix = uniquePrefix; this.idCounters = idCounters; - this.fontCache = new RefSetCache(); + this.fontCache = fontCache; } // Specifies properties for each command diff --git a/src/core/obj.js b/src/core/obj.js index 837ced393..95dd50caa 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -189,7 +189,7 @@ var RefSet = (function RefSetClosure() { var RefSetCache = (function RefSetCacheClosure() { function RefSetCache() { - this.dict = {}; + this.dict = Object.create(null); } RefSetCache.prototype = { @@ -203,6 +203,16 @@ var RefSetCache = (function RefSetCacheClosure() { put: function RefSetCache_put(ref, obj) { this.dict['R' + ref.num + '.' + ref.gen] = obj; + }, + + forEach: function RefSetCache_forEach(fn, thisArg) { + for (var i in this.dict) { + fn.call(thisArg, this.dict[i]); + } + }, + + clear: function RefSetCache_clear() { + this.dict = Object.create(null); } }; @@ -214,6 +224,7 @@ var Catalog = (function CatalogClosure() { this.pdfManager = pdfManager; this.xref = xref; this.catDict = xref.getCatalogObj(); + this.fontCache = new RefSetCache(); assertWellFormed(isDict(this.catDict), 'catalog object is not a dictionary'); @@ -400,13 +411,22 @@ var Catalog = (function CatalogClosure() { return shadow(this, 'javaScript', javaScript); }, + cleanup: function Catalog_cleanup() { + this.fontCache.forEach(function (font) { + delete font.sent; + delete font.translated; + }); + this.fontCache.clear(); + }, + getPage: function Catalog_getPage(pageIndex) { if (!(pageIndex in this.pagePromises)) { this.pagePromises[pageIndex] = this.getPageDict(pageIndex).then( function (a) { var dict = a[0]; var ref = a[1]; - return new Page(this.pdfManager, this.xref, pageIndex, dict, ref); + return new Page(this.pdfManager, this.xref, pageIndex, dict, ref, + this.fontCache); }.bind(this) ); } diff --git a/src/core/pdf_manager.js b/src/core/pdf_manager.js index a6641ae4c..ac0cd1130 100644 --- a/src/core/pdf_manager.js +++ b/src/core/pdf_manager.js @@ -46,6 +46,10 @@ var BasePdfManager = (function BasePdfManagerClosure() { return this.pdfModel.getPage(pageIndex); }, + cleanup: function BasePdfManager_cleanup() { + return this.pdfModel.cleanup(); + }, + ensure: function BasePdfManager_ensure(obj, prop, args) { return new NotImplementedException(); }, diff --git a/src/core/worker.js b/src/core/worker.js index b72d1c721..455227bea 100644 --- a/src/core/worker.js +++ b/src/core/worker.js @@ -379,6 +379,11 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { }); }); + handler.on('Cleanup', function wphCleanup(data, promise) { + pdfManager.cleanup(); + promise.resolve(true); + }); + handler.on('Terminate', function wphTerminate(data, promise) { pdfManager.terminate(); promise.resolve(); diff --git a/src/display/api.js b/src/display/api.js index 44b24c3de..56c497916 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -276,6 +276,9 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { dataLoaded: function PDFDocumentProxy_dataLoaded() { return this.transport.dataLoaded(); }, + cleanup: function PDFDocumentProxy_cleanup() { + this.transport.startCleanup(); + }, destroy: function PDFDocumentProxy_destroy() { this.transport.destroy(); } @@ -907,6 +910,21 @@ var WorkerTransport = (function WorkerTransportClosure() { } ); return promise; + }, + + startCleanup: function WorkerTransport_startCleanup() { + this.messageHandler.send('Cleanup', null, + function endCleanup() { + for (var i = 0, ii = this.pageCache.length; i < ii; i++) { + var page = this.pageCache[i]; + if (page) { + page.destroy(); + } + } + this.commonObjs.clear(); + FontLoader.clear(); + }.bind(this) + ); } }; return WorkerTransport; diff --git a/src/display/font_loader.js b/src/display/font_loader.js index 17d86782a..1b86e99bd 100644 --- a/src/display/font_loader.js +++ b/src/display/font_loader.js @@ -33,6 +33,12 @@ var FontLoader = { var styleSheet = styleElement.sheet; styleSheet.insertRule(rule, styleSheet.cssRules.length); }, + clear: function fontLoaderClear() { + var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG'); + if (styleElement) { + styleElement.parentNode.removeChild(styleElement); + } + }, //#if !(MOZCENTRAL) get loadTestFont() { // This is a CFF font with 1 glyph for '.' that fills its entire width and diff --git a/web/page_view.js b/web/page_view.js index 6b88a8f15..9b1cf116e 100644 --- a/web/page_view.js +++ b/web/page_view.js @@ -67,12 +67,16 @@ var PageView = function pageView(container, id, scale, } }; - this.reset = function pageViewReset() { + this.resetRenderingState = function pageViewResetRenderingState() { if (this.renderTask) { this.renderTask.cancel(); } this.resume = null; this.renderingState = RenderingStates.INITIAL; + }; + + this.reset = function pageViewReset() { + this.resetRenderingState(); div.style.width = Math.floor(this.viewport.width) + 'px'; div.style.height = Math.floor(this.viewport.height) + 'px'; diff --git a/web/viewer.js b/web/viewer.js index 838ab48d3..a5b2a4675 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -38,6 +38,7 @@ var SCALE_SELECT_CONTAINER_PADDING = 8; var SCALE_SELECT_PADDING = 22; var THUMBNAIL_SCROLL_MARGIN = -19; var USE_ONLY_CSS_ZOOM = false; +var CLEANUP_TIMEOUT = 30000; //#if B2G //USE_ONLY_CSS_ZOOM = true; //#endif @@ -106,6 +107,7 @@ var PDFView = { lastScroll: 0, previousPageNumber: 1, isViewerEmbedded: (window.parent !== window), + idleTimeout: null, // called once when the document is loaded initialize: function pdfViewInitialize() { @@ -1011,6 +1013,11 @@ var PDFView = { }, renderHighestPriority: function pdfViewRenderHighestPriority() { + if (PDFView.idleTimeout) { + clearTimeout(PDFView.idleTimeout); + PDFView.idleTimeout = null; + } + // Pages have a higher priority than thumbnails, so check them first. var visiblePages = this.getVisiblePages(); var pageView = this.getHighestPriority(visiblePages, this.pages, @@ -1025,9 +1032,24 @@ var PDFView = { var thumbView = this.getHighestPriority(visibleThumbs, this.thumbnails, this.thumbnailViewScroll.down); - if (thumbView) + if (thumbView) { this.renderView(thumbView, 'thumbnail'); + return; + } } + + PDFView.idleTimeout = setTimeout(function () { + PDFView.cleanup(); + }, CLEANUP_TIMEOUT); + }, + + cleanup: function pdfViewCleanup() { + for (var i = 0, ii = this.pages.length; i < ii; i++) { + if (this.pages[i]) { + this.pages[i].resetRenderingState(); + } + } + this.pdfDocument.cleanup(); }, getHighestPriority: function pdfViewGetHighestPriority(visible, views,