From 6dd82ad5d4e79228d7dedbf33fbc9d02b7ec1424 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 5 Sep 2011 09:26:07 -0700 Subject: [PATCH 001/123] Track loaded such that setFont doesnt need to touch xref anymore --- pdf.js | 56 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/pdf.js b/pdf.js index 37afdc3765c06..e53ff1cb3ecee 100644 --- a/pdf.js +++ b/pdf.js @@ -4051,6 +4051,8 @@ var EvalState = (function() { return constructor; })(); +var FontsMap = {}; + var PartialEvaluator = (function() { function constructor() { this.state = new EvalState(); @@ -4208,17 +4210,23 @@ var PartialEvaluator = (function() { images.bind(xobj); // monitoring image load } } else if (cmd == 'Tf') { // eagerly collect all fonts - var fontRes = resources.get('Font'); - if (fontRes) { - fontRes = xref.fetchIfRef(fontRes); - var font = xref.fetchIfRef(fontRes.get(args[0].name)); - assertWellFormed(IsDict(font)); - if (!font.translated) { - font.translated = this.translateFont(font, xref, resources); - if (fonts && font.translated) { - // keep track of each font we translated so the caller can - // load them asynchronously before calling display on a page - fonts.push(font.translated); + var fontName = args[0].name; + + // Check if this font is known already and process it otherwise. + if (!FontsMap[fontName]) { + var fontRes = resources.get('Font'); + if (fontRes) { + fontRes = xref.fetchIfRef(fontRes); + var font = xref.fetchIfRef(fontRes.get(fontName)); + assertWellFormed(IsDict(font)); + if (!font.translated) { + font.translated = this.translateFont(font, xref, resources); + if (fonts && font.translated) { + // keep track of each font we translated so the caller can + // load them asynchronously before calling display on a page + fonts.push(font.translated); + FontsMap[fontName] = font; + } } } } @@ -4233,6 +4241,10 @@ var PartialEvaluator = (function() { } } + // Expose arrays for debugging purpose. + window.fnArray = fnArray; + window.argsArray = argsArray; + return function(gfx) { for (var i = 0, length = argsArray.length; i < length; i++) gfx[fnArray[i]].apply(gfx, argsArray[i]); @@ -4914,16 +4926,20 @@ var CanvasGraphics = (function() { this.current.leading = -leading; }, setFont: function(fontRef, size) { - var font = this.xref.fetchIfRef(this.res.get('Font')); - if (!IsDict(font)) - return; - - font = font.get(fontRef.name); - font = this.xref.fetchIfRef(font); - if (!font) - return; + // Lookup the fontObj using fontRef only. + var fontRefName = fontRef.name; + var fontObj = FontsMap[fontRefName].fontObj; + + if (!fontObj) { + throw "Can't find font for " + fontRefName; + } + + var name = fontObj.loadedName; + if (!name) { + // TODO: fontDescriptor is not available, fallback to default font + name = 'sans-serif'; + } - var fontObj = font.fontObj; this.current.font = fontObj; this.current.fontSize = size; From e9f4a3d8ee082011ccfeaba6fffa66fd2f022cfe Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 5 Sep 2011 11:19:44 -0700 Subject: [PATCH 002/123] Need to use font.translated.name as unique identifier --- pdf.js | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/pdf.js b/pdf.js index e53ff1cb3ecee..13ec62e15cc40 100644 --- a/pdf.js +++ b/pdf.js @@ -4212,23 +4212,24 @@ var PartialEvaluator = (function() { } else if (cmd == 'Tf') { // eagerly collect all fonts var fontName = args[0].name; - // Check if this font is known already and process it otherwise. - if (!FontsMap[fontName]) { - var fontRes = resources.get('Font'); - if (fontRes) { - fontRes = xref.fetchIfRef(fontRes); - var font = xref.fetchIfRef(fontRes.get(fontName)); - assertWellFormed(IsDict(font)); - if (!font.translated) { - font.translated = this.translateFont(font, xref, resources); - if (fonts && font.translated) { - // keep track of each font we translated so the caller can - // load them asynchronously before calling display on a page - fonts.push(font.translated); - FontsMap[fontName] = font; - } + var fontRes = resources.get('Font'); + if (fontRes) { + fontRes = xref.fetchIfRef(fontRes); + var font = xref.fetchIfRef(fontRes.get(fontName)); + assertWellFormed(IsDict(font)); + if (!font.translated) { + font.translated = this.translateFont(font, xref, resources); + if (fonts && font.translated) { + // keep track of each font we translated so the caller can + // load them asynchronously before calling display on a page + fonts.push(font.translated); + FontsMap[font.translated.name] = font; } } + args[0].name = font.translated.name; + } else { + // TODO: TOASK: Is it possible to get here? If so, what does + // args[0].name should be like??? } } From 2c51c6fb9e944c22c4dab44be4884320a3c76c56 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 5 Sep 2011 16:06:31 -0700 Subject: [PATCH 003/123] Set loadedName in Partial Eval --- fonts.js | 14 +++++++------ pdf.js | 64 +++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/fonts.js b/fonts.js index 7d51e2c4b2acd..ed2496727d97c 100755 --- a/fonts.js +++ b/fonts.js @@ -472,16 +472,18 @@ var Font = (function Font() { this.data = data; this.type = properties.type; this.textMatrix = properties.textMatrix; - this.loadedName = getUniqueName(); this.composite = properties.composite; + this.loadedName = properties.loadedName; + + // TODO: Remove this once we can be sure nothing got broken to du changes + // in this commit. + if (!this.loadedName) { + throw "There has to be a `loadedName`"; + } + this.loading = true; }; - var numFonts = 0; - function getUniqueName() { - return 'pdfFont' + numFonts++; - } - function stringToArray(str) { var array = []; for (var i = 0; i < str.length; ++i) diff --git a/pdf.js b/pdf.js index 13ec62e15cc40..7122c0eb4c4ad 100644 --- a/pdf.js +++ b/pdf.js @@ -3373,28 +3373,51 @@ var Page = (function() { // Firefox error reporting from XHR callbacks. setTimeout(function() { var exc = null; - try { + // try { self.display(gfx); stats.render = Date.now(); - } catch (e) { - exc = e.toString(); - } - if (continuation) continuation(exc); + // } catch (e) { + // exc = e.toString(); + // } + continuation(exc); }); }; + + // Make a copy of the necessary datat to build a font later. The `font` + // object will be sent to the main thread later on. + var fontsBackup = fonts; + fonts = []; + for (var i = 0; i < fontsBackup.length; i++) { + var orgFont = fontsBackup[i]; + var orgFontObj = orgFont.fontObj; + + var font = { + name: orgFont.name, + file: orgFont.file, + properties: orgFont.properties + } + fonts.push(font); + } var fontObjs = FontLoader.bind( fonts, function() { + // Rebuild the FontsMap. This is emulating the behavior of the main + // thread. + if (fontObjs) { + // Replace the FontsMap hash with the fontObjs. + for (var i = 0; i < fontObjs.length; i++) { + FontsMap[fontObjs[i].loadedName] = {fontObj: fontObjs[i]}; + } + } + stats.fonts = Date.now(); images.notifyOnLoad(function() { stats.images = Date.now(); displayContinuation(); }); - }); - - for (var i = 0, ii = fonts.length; i < ii; ++i) - fonts[i].dict.fontObj = fontObjs[i]; + } + ); }, @@ -4052,6 +4075,7 @@ var EvalState = (function() { })(); var FontsMap = {}; +var FontLoadedCounter = 0; var PartialEvaluator = (function() { function constructor() { @@ -4157,7 +4181,9 @@ var PartialEvaluator = (function() { }; constructor.prototype = { - evaluate: function(stream, xref, resources, fonts, images) { + evalRaw: function(stream, xref, resources, fonts, images, uniquePrefix) { + uniquePrefix = uniquePrefix || ""; + resources = xref.fetchIfRef(resources) || new Dict(); var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict(); var patterns = xref.fetchIfRef(resources.get('Pattern')) || new Dict(); @@ -4223,10 +4249,13 @@ var PartialEvaluator = (function() { // keep track of each font we translated so the caller can // load them asynchronously before calling display on a page fonts.push(font.translated); - FontsMap[font.translated.name] = font; + + var loadedName = uniquePrefix + "font_" + (FontLoadedCounter++); + font.translated.properties.loadedName = loadedName; + FontsMap[loadedName] = font; } } - args[0].name = font.translated.name; + args[0].name = font.translated.properties.loadedName; } else { // TODO: TOASK: Is it possible to get here? If so, what does // args[0].name should be like??? @@ -4246,7 +4275,18 @@ var PartialEvaluator = (function() { window.fnArray = fnArray; window.argsArray = argsArray; + return { + fnArray: fnArray, + argsArray: argsArray + }; + }, + + eval: function(stream, xref, resources, fonts, images, uniquePrefix) { + var ret = this.evalRaw(stream, xref, resources, fonts, images, uniquePrefix); + return function(gfx) { + var argsArray = ret.argsArray; + var fnArray = ret.fnArray; for (var i = 0, length = argsArray.length; i < length; i++) gfx[fnArray[i]].apply(gfx, argsArray[i]); }; From 0a571899c806c21cf1457a8929b01cdcd138d4a3 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 5 Sep 2011 16:07:03 -0700 Subject: [PATCH 004/123] Very basic worker implementation --- web/viewer.html | 1 + web/viewer.js | 2 +- worker.js | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 worker.js diff --git a/web/viewer.html b/web/viewer.html index a53593df308bc..d236f840e74c0 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -11,6 +11,7 @@ + diff --git a/web/viewer.js b/web/viewer.js index d7c9d6b660d90..082dfaa87815e 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -152,7 +152,7 @@ var PDFView = { while (container.hasChildNodes()) container.removeChild(container.lastChild); - var pdf = new PDFDoc(new Stream(data)); + var pdf = new WorkerPDFDoc(data); var pagesCount = pdf.numPages; document.getElementById('numPages').innerHTML = pagesCount; diff --git a/worker.js b/worker.js new file mode 100644 index 0000000000000..d26735005c4c9 --- /dev/null +++ b/worker.js @@ -0,0 +1,67 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +var WorkerPage = (function() { + function constructor(workerPDF, page) { + this.workerPDF = workerPDF; + this.page = page; + + this.ref = page.ref; + } + + constructor.prototype = { + get width() { + return this.page.width; + }, + + get height() { + return this.page.height; + }, + + get stats() { + return this.page.stats; + }, + + startRendering: function(ctx, callback, errback) { + // TODO: Place the worker magic HERE. + this.page.startRendering(ctx, callback, errback); + }, + + getLinks: function() { + return this.page.getLinks(); + } + }; + + return constructor; +})(); + +var WorkerPDFDoc = (function() { + function constructor(data) { + this.data = data; + this.stream = new Stream(data); + this.pdf = new PDFDoc(this.stream); + + this.catalog = this.pdf.catalog; + + this.pageCache = []; + } + + constructor.prototype = { + get numPages() { + return this.pdf.numPages; + }, + + getPage: function(n) { + if (this.pageCache[n]) { + return this.pageCache[n]; + } + + var page = this.pdf.getPage(n); + return this.pageCache[n] = new WorkerPage(this, page); + } + }; + + return constructor; +})(); From e3dd329739ac1b89fc7263041becccfa42f4bc52 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 5 Sep 2011 16:40:15 -0700 Subject: [PATCH 005/123] Move ensureFont code part of Page into its own function --- pdf.js | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/pdf.js b/pdf.js index 7122c0eb4c4ad..475137475ad5d 100644 --- a/pdf.js +++ b/pdf.js @@ -3356,6 +3356,7 @@ var Page = (function() { } return shadow(this, 'rotate', rotate); }, + startRendering: function(canvasCtx, continuation) { var self = this; var stats = self.stats; @@ -3399,28 +3400,14 @@ var Page = (function() { fonts.push(font); } - var fontObjs = FontLoader.bind( - fonts, - function() { - // Rebuild the FontsMap. This is emulating the behavior of the main - // thread. - if (fontObjs) { - // Replace the FontsMap hash with the fontObjs. - for (var i = 0; i < fontObjs.length; i++) { - FontsMap[fontObjs[i].loadedName] = {fontObj: fontObjs[i]}; - } - } - - stats.fonts = Date.now(); - images.notifyOnLoad(function() { - stats.images = Date.now(); - displayContinuation(); - }); - } - ); + this.ensureFonts(fonts, function() { + images.notifyOnLoad(function() { + stats.images = Date.now(); + displayContinuation(); + }); + }) }, - compile: function(gfx, fonts, images) { if (this.code) { // content was compiled @@ -3439,6 +3426,27 @@ var Page = (function() { } this.code = gfx.compile(content, xref, resources, fonts, images); }, + + ensureFonts: function(fonts, callback) { + var fontObjs = FontLoader.bind( + fonts, + function() { + // Rebuild the FontsMap. This is emulating the behavior of the main + // thread. + if (fontObjs) { + // Replace the FontsMap hash with the fontObjs. + for (var i = 0; i < fontObjs.length; i++) { + FontsMap[fontObjs[i].loadedName] = {fontObj: fontObjs[i]}; + } + } + + this.stats.fonts = Date.now(); + + callback.call(this); + }.bind(this) + ); + }, + display: function(gfx) { assert(this.code instanceof Function, 'page content must be compiled first'); From 0134143c67d9995249e83b73cf42180daa3463e0 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 5 Sep 2011 17:01:02 -0700 Subject: [PATCH 006/123] Split compilation up in preCompile and Compile. --- pdf.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/pdf.js b/pdf.js index 475137475ad5d..bc75e8b521667 100644 --- a/pdf.js +++ b/pdf.js @@ -3408,7 +3408,7 @@ var Page = (function() { }) }, - compile: function(gfx, fonts, images) { + preCompile: function(gfx, fonts, images) { if (this.code) { // content was compiled return; @@ -3424,7 +3424,12 @@ var Page = (function() { content[i] = xref.fetchIfRef(content[i]); content = new StreamsSequenceStream(content); } - this.code = gfx.compile(content, xref, resources, fonts, images); + return gfx.preCompile(content, xref, resources, fonts, images); + }, + + compile: function(gfx, fonts, images) { + var preCompilation = this.preCompile(gfx, fonts, images); + this.code = gfx.postCompile(preCompilation); }, ensureFonts: function(fonts, callback) { @@ -4299,6 +4304,15 @@ var PartialEvaluator = (function() { gfx[fnArray[i]].apply(gfx, argsArray[i]); }; }, + + evalFromRaw: function(raw) { + return function(gfx) { + var argsArray = raw.argsArray; + var fnArray = raw.fnArray; + for (var i = 0, length = argsArray.length; i < length; i++) + gfx[fnArray[i]].apply(gfx, argsArray[i]); + }; + }, extractEncoding: function(dict, xref, properties) { var type = properties.type, encoding; @@ -4761,9 +4775,13 @@ var CanvasGraphics = (function() { this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height); }, - compile: function(stream, xref, resources, fonts, images) { - var pe = new PartialEvaluator(); - return pe.evaluate(stream, xref, resources, fonts, images); + preCompile: function(stream, xref, resources, fonts, images) { + var pe = this.pe = new PartialEvaluator(); + return pe.evalRaw(stream, xref, resources, fonts, images); + }, + + postCompile: function(raw) { + return this.pe.evalFromRaw(raw); }, execute: function(code, xref, resources) { From 71ff8ee5865a077f0235ec0065d87b3c61f9c7a2 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 5 Sep 2011 17:42:58 -0700 Subject: [PATCH 007/123] backup --- pdf.js | 60 ++++++++++++++----------- web/viewer.html | 1 + worker.js | 32 ++++++++++++- worker/boot.js | 63 ++++++++++++++++++++++++++ worker/console.js | 2 +- worker/message_handler.js | 23 ++++++++++ worker/pdf.js | 95 --------------------------------------- 7 files changed, 154 insertions(+), 122 deletions(-) create mode 100644 worker/boot.js create mode 100644 worker/message_handler.js delete mode 100644 worker/pdf.js diff --git a/pdf.js b/pdf.js index bc75e8b521667..39d36780f6aa9 100644 --- a/pdf.js +++ b/pdf.js @@ -3358,31 +3358,23 @@ var Page = (function() { }, startRendering: function(canvasCtx, continuation) { + var gfx = new CanvasGraphics(canvasCtx); + + // If there is already some code to render, then use it directly. + if (this.code) { + this.display(gfx); + return; + } + var self = this; var stats = self.stats; stats.compile = stats.fonts = stats.render = 0; - - var gfx = new CanvasGraphics(canvasCtx); + var fonts = []; var images = new ImagesLoader(); - this.compile(gfx, fonts, images); + var preCompilation = this.preCompile(gfx, fonts, images); stats.compile = Date.now(); - - var displayContinuation = function() { - // Always defer call to display() to work around bug in - // Firefox error reporting from XHR callbacks. - setTimeout(function() { - var exc = null; - // try { - self.display(gfx); - stats.render = Date.now(); - // } catch (e) { - // exc = e.toString(); - // } - continuation(exc); - }); - }; // Make a copy of the necessary datat to build a font later. The `font` // object will be sent to the main thread later on. @@ -3390,7 +3382,6 @@ var Page = (function() { fonts = []; for (var i = 0; i < fontsBackup.length; i++) { var orgFont = fontsBackup[i]; - var orgFontObj = orgFont.fontObj; var font = { name: orgFont.name, @@ -3400,6 +3391,29 @@ var Page = (function() { fonts.push(font); } + this.startRenderingFromPreCompilation(gfx, preCompilation, fonts, images, continuation); + }, + + startRenderingFromPreCompilation: function(gfx, preCompilation, fonts, images, continuation) { + var self = this; + + var displayContinuation = function() { + self.code = gfx.postCompile(preCompilation); + + // Always defer call to display() to work around bug in + // Firefox error reporting from XHR callbacks. + setTimeout(function() { + var exc = null; + // try { + self.display(gfx); + stats.render = Date.now(); + // } catch (e) { + // exc = e.toString(); + // } + continuation(exc); + }); + }; + this.ensureFonts(fonts, function() { images.notifyOnLoad(function() { stats.images = Date.now(); @@ -3424,12 +3438,8 @@ var Page = (function() { content[i] = xref.fetchIfRef(content[i]); content = new StreamsSequenceStream(content); } - return gfx.preCompile(content, xref, resources, fonts, images); - }, - - compile: function(gfx, fonts, images) { - var preCompilation = this.preCompile(gfx, fonts, images); - this.code = gfx.postCompile(preCompilation); + return gfx.preCompile(content, xref, resources, fonts, images, + this.pageNumber + "_"); }, ensureFonts: function(fonts, callback) { diff --git a/web/viewer.html b/web/viewer.html index d236f840e74c0..871db8ce6c204 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -12,6 +12,7 @@ + diff --git a/worker.js b/worker.js index d26735005c4c9..61f8c0934bc75 100644 --- a/worker.js +++ b/worker.js @@ -25,8 +25,21 @@ var WorkerPage = (function() { }, startRendering: function(ctx, callback, errback) { + this.ctx = ctx; + this.callback = callback; // TODO: Place the worker magic HERE. - this.page.startRendering(ctx, callback, errback); + // this.page.startRendering(ctx, callback, errback); + + this.workerPDF.startRendering(this) + }, + + startRenderingFromPreCompilation: function(preCompilation, fonts, images) { + var gfx = new CanvasGraphics(this.ctx); + + // TODO: Add proper handling for images loaded by the worker. + var images = new ImagesLoader(); + + this.page.startRenderingFromPreCompilation(gfx, preCompilation, fonts, images, this.callback); }, getLinks: function() { @@ -46,6 +59,19 @@ var WorkerPDFDoc = (function() { this.catalog = this.pdf.catalog; this.pageCache = []; + + this.worker = new Worker("worker/boot.js"); + this.handler = new MessageHandler({ + "page": function(data) { + var pageNum = data.pageNum; + var page = this.pageCache[pageNum]; + + page.startRenderingFromPreCompilation(data.preCompilation, data.fonts, data.images); + } + }, this.worker.postMessage, this); + this.worker.onmessage = this.handler.onMessage; + + this.handler.send("doc", data); } constructor.prototype = { @@ -53,6 +79,10 @@ var WorkerPDFDoc = (function() { return this.pdf.numPages; }, + startRendering: function(page) { + this.handler.send("page", page.page.pageNumber); + }, + getPage: function(n) { if (this.pageCache[n]) { return this.pageCache[n]; diff --git a/worker/boot.js b/worker/boot.js new file mode 100644 index 0000000000000..4ec65a1de9e62 --- /dev/null +++ b/worker/boot.js @@ -0,0 +1,63 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +// +importScripts('console.js'); +importScripts('event_handler.js'); +importScripts('../pdf.js'); +importScripts('../fonts.js'); +importScripts('../crypto.js'); +importScripts('../glyphlist.js'); + +// Listen for messages from the main thread. +var pdfDoc = null; + +var handler = new MessageHandler({ + "doc": function(data) { + pdfDocument = new PDFDoc(new Stream(data)); + console.log("setup pdfDoc"); + }, + + "page": function(pageNum) { + pageNum = parseInt(pageNum); + console.log("about to process page", pageNum); + + var page = pdfDocument.getPage(pageNum); + + // The following code does quite the same as Page.prototype.startRendering, + // but stops at one point and sends the result back to the main thread. + var gfx = new CanvasGraphics(canvasCtx); + var fonts = []; + // TODO: Figure out how image loading is handled inside the worker. + var images = new ImagesLoader(); + + // Pre compile the pdf page and fetch the fonts/images. + var preCompilation = page.preCompile(gfx, fonts, images); + + // Extract the minimum of font data that is required to build all required + // font stuff on the main thread. + var fontsMin = []; + for (var i = 0; i < fonts.length; i++) { + var font = fonts[i]; + + fontsMin.push({ + name: orgFont.name, + file: orgFont.file, + properties: orgFont.properties + }); + } + + // TODO: Handle images here. + + handler.send("page", { + pageNum: pageNum, + fonts: fontsMin, + images: [], + preCompilation: preCompilation, + }); + } +}, postMessage); + +onmessage = handler.onMessage; diff --git a/worker/console.js b/worker/console.js index fc49583a67118..74a2d56a3c9ae 100644 --- a/worker/console.js +++ b/worker/console.js @@ -9,7 +9,7 @@ var console = { var args = Array.prototype.slice.call(arguments); postMessage({ action: 'log', - data: args + data: args }); }, diff --git a/worker/message_handler.js b/worker/message_handler.js new file mode 100644 index 0000000000000..b97558ee6c2b1 --- /dev/null +++ b/worker/message_handler.js @@ -0,0 +1,23 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + + +function MessageHandler(actionHandler, postMessage, scope) { + this.onMessage = function(event) { + var data = event.data; + if (data.action in actionHandler) { + actionHandler[data.action].call(scope, data.data); + } else { + throw 'Unkown action from worker: ' + data.action; + } + }; + + this.send = function(actionName, data) { + postMessage({ + action: actionName, + data: data + }); + } +} \ No newline at end of file diff --git a/worker/pdf.js b/worker/pdf.js deleted file mode 100644 index 8cb6342db0446..0000000000000 --- a/worker/pdf.js +++ /dev/null @@ -1,95 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ - -'use strict'; - -var consoleTimer = {}; -var console = { - log: function log() { - var args = Array.prototype.slice.call(arguments); - postMessage({ - action: 'log', - data: args - }); - }, - - time: function(name) { - consoleTimer[name] = Date.now(); - }, - - timeEnd: function(name) { - var time = consoleTimer[name]; - if (time == null) { - throw 'Unkown timer name ' + name; - } - this.log('Timer:', name, Date.now() - time); - } -}; - -// -importScripts('console.js'); -importScripts('canvas.js'); -importScripts('../pdf.js'); -importScripts('../fonts.js'); -importScripts('../crypto.js'); -importScripts('../glyphlist.js'); - -// Use the JpegStreamProxy proxy. -JpegStream = JpegStreamProxy; - -// Create the WebWorkerProxyCanvas. -var canvas = new CanvasProxy(1224, 1584); - -// Listen for messages from the main thread. -var pdfDocument = null; -onmessage = function(event) { - var data = event.data; - // If there is no pdfDocument yet, then the sent data is the PDFDocument. - if (!pdfDocument) { - pdfDocument = new PDFDoc(new Stream(data)); - postMessage({ - action: 'pdf_num_pages', - data: pdfDocument.numPages - }); - return; - } - // User requested to render a certain page. - else { - console.time('compile'); - - // Let's try to render the first page... - var page = pdfDocument.getPage(parseInt(data)); - - var pdfToCssUnitsCoef = 96.0 / 72.0; - var pageWidth = (page.mediaBox[2] - page.mediaBox[0]) * pdfToCssUnitsCoef; - var pageHeight = (page.mediaBox[3] - page.mediaBox[1]) * pdfToCssUnitsCoef; - postMessage({ - action: 'setup_page', - data: pageWidth + ',' + pageHeight - }); - - // Set canvas size. - canvas.width = pageWidth; - canvas.height = pageHeight; - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - var fonts = []; - var gfx = new CanvasGraphics(canvas.getContext('2d'), CanvasProxy); - page.compile(gfx, fonts); - console.timeEnd('compile'); - - // Send fonts to the main thread. - console.time('fonts'); - postMessage({ - action: 'fonts', - data: fonts - }); - console.timeEnd('fonts'); - - console.time('display'); - page.display(gfx); - canvas.flush(); - console.timeEnd('display'); - } -}; From 5a1488df9fe7412bdc6348e966a06adbef89642b Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 5 Sep 2011 18:10:57 -0700 Subject: [PATCH 008/123] Expose FontMeasure only if running on the main thread as the worker doesnt have a document to attach the canvas to --- fonts.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/fonts.js b/fonts.js index ed2496727d97c..69bb50590ca91 100755 --- a/fonts.js +++ b/fonts.js @@ -117,6 +117,47 @@ var serifFonts = { 'Wide Latin': true, 'Windsor': true, 'XITS': true }; +// Create the FontMeasure object only on the main thread. +if (!isWorker) { + var FontMeasure = (function FontMeasure() { + var kScalePrecision = 30; + var ctx = document.createElement('canvas').getContext('2d'); + ctx.scale(1 / kScalePrecision, 1); + + var current; + var measureCache; + + return { + setActive: function fonts_setActive(font, size) { + if (current == font) { + var sizes = current.sizes; + if (!(measureCache = sizes[size])) + measureCache = sizes[size] = Object.create(null); + } else { + measureCache = null; + } + + var name = font.loadedName; + var bold = font.bold ? 'bold' : 'normal'; + var italic = font.italic ? 'italic' : 'normal'; + size *= kScalePrecision; + var rule = italic + ' ' + bold + ' ' + size + 'px "' + name + '"'; + ctx.font = rule; + current = font; + }, + measureText: function fonts_measureText(text) { + var width; + if (measureCache && (width = measureCache[text])) + return width; + width = ctx.measureText(text).width / kScalePrecision; + if (measureCache) + measureCache[text] = width; + return width; + } + }; + })(); +} + var FontLoader = { listeningForFontLoad: false, From cc17707da62bd5de2b5e26df48e55721105fe7c2 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 5 Sep 2011 18:12:03 -0700 Subject: [PATCH 009/123] First page is rendering using new worker infrastructure --- pdf.js | 11 +++++------ worker.js | 23 ++++++++++++++++++----- worker/boot.js | 21 +++++++++------------ worker/console.js | 10 +++++++++- worker/message_handler.js | 27 +++++++++++++++++++++------ 5 files changed, 62 insertions(+), 30 deletions(-) diff --git a/pdf.js b/pdf.js index 39d36780f6aa9..4deb1f93408e3 100644 --- a/pdf.js +++ b/pdf.js @@ -3406,7 +3406,7 @@ var Page = (function() { var exc = null; // try { self.display(gfx); - stats.render = Date.now(); + self.stats.render = Date.now(); // } catch (e) { // exc = e.toString(); // } @@ -3416,7 +3416,7 @@ var Page = (function() { this.ensureFonts(fonts, function() { images.notifyOnLoad(function() { - stats.images = Date.now(); + self.stats.images = Date.now(); displayContinuation(); }); }) @@ -4294,10 +4294,6 @@ var PartialEvaluator = (function() { } } - // Expose arrays for debugging purpose. - window.fnArray = fnArray; - window.argsArray = argsArray; - return { fnArray: fnArray, argsArray: argsArray @@ -4791,6 +4787,9 @@ var CanvasGraphics = (function() { }, postCompile: function(raw) { + if (!this.pe) { + this.pe = new PartialEvaluator(); + } return this.pe.evalFromRaw(raw); }, diff --git a/worker.js b/worker.js index 61f8c0934bc75..1eb65b7b9c82e 100644 --- a/worker.js +++ b/worker.js @@ -60,16 +60,29 @@ var WorkerPDFDoc = (function() { this.pageCache = []; - this.worker = new Worker("worker/boot.js"); - this.handler = new MessageHandler({ + this.worker = new Worker("../worker/boot.js"); + this.handler = new MessageHandler("main", { "page": function(data) { var pageNum = data.pageNum; var page = this.pageCache[pageNum]; + // Add necessary shape back to fonts. + var fonts = data.fonts; + for (var i = 0; i < fonts.length; i++) { + var font = fonts[i]; + + var fontFileDict = new Dict(); + fontFileDict.map = font.file.dict.map; + + var fontFile = new Stream(font.file.bytes, font.file.start, + font.file.end - font.file.start, fontFileDict); + font.file = new FlateStream(fontFile); + } + + console.log("startRenderingFromPreCompilation:", "numberOfFonts", fonts.length); page.startRenderingFromPreCompilation(data.preCompilation, data.fonts, data.images); } - }, this.worker.postMessage, this); - this.worker.onmessage = this.handler.onMessage; + }, this.worker, this); this.handler.send("doc", data); } @@ -80,7 +93,7 @@ var WorkerPDFDoc = (function() { }, startRendering: function(page) { - this.handler.send("page", page.page.pageNumber); + this.handler.send("page", page.page.pageNumber + 1); }, getPage: function(n) { diff --git a/worker/boot.js b/worker/boot.js index 4ec65a1de9e62..da09f21c50990 100644 --- a/worker/boot.js +++ b/worker/boot.js @@ -3,9 +3,8 @@ 'use strict'; -// importScripts('console.js'); -importScripts('event_handler.js'); +importScripts('message_handler.js'); importScripts('../pdf.js'); importScripts('../fonts.js'); importScripts('../crypto.js'); @@ -14,9 +13,9 @@ importScripts('../glyphlist.js'); // Listen for messages from the main thread. var pdfDoc = null; -var handler = new MessageHandler({ +var handler = new MessageHandler("worker", { "doc": function(data) { - pdfDocument = new PDFDoc(new Stream(data)); + pdfDoc = new PDFDoc(new Stream(data)); console.log("setup pdfDoc"); }, @@ -24,11 +23,11 @@ var handler = new MessageHandler({ pageNum = parseInt(pageNum); console.log("about to process page", pageNum); - var page = pdfDocument.getPage(pageNum); + var page = pdfDoc.getPage(pageNum); // The following code does quite the same as Page.prototype.startRendering, // but stops at one point and sends the result back to the main thread. - var gfx = new CanvasGraphics(canvasCtx); + var gfx = new CanvasGraphics(null); var fonts = []; // TODO: Figure out how image loading is handled inside the worker. var images = new ImagesLoader(); @@ -43,9 +42,9 @@ var handler = new MessageHandler({ var font = fonts[i]; fontsMin.push({ - name: orgFont.name, - file: orgFont.file, - properties: orgFont.properties + name: font.name, + file: font.file, + properties: font.properties }); } @@ -58,6 +57,4 @@ var handler = new MessageHandler({ preCompilation: preCompilation, }); } -}, postMessage); - -onmessage = handler.onMessage; +}, this); diff --git a/worker/console.js b/worker/console.js index 74a2d56a3c9ae..debcba3aa6d48 100644 --- a/worker/console.js +++ b/worker/console.js @@ -8,7 +8,15 @@ var console = { log: function log() { var args = Array.prototype.slice.call(arguments); postMessage({ - action: 'log', + action: 'console_log', + data: args + }); + }, + + error: function error() { + var args = Array.prototype.slice.call(arguments); + postMessage({ + action: 'console_error', data: args }); }, diff --git a/worker/message_handler.js b/worker/message_handler.js index b97558ee6c2b1..17c6e4aea18a5 100644 --- a/worker/message_handler.js +++ b/worker/message_handler.js @@ -4,8 +4,18 @@ 'use strict'; -function MessageHandler(actionHandler, postMessage, scope) { - this.onMessage = function(event) { +function MessageHandler(name, actionHandler, comObj, scope) { + this.name = name; + + actionHandler["console_log"] = function(data) { + console.log.apply(console, data); + } + actionHandler["console_error"] = function(data) { + console.error.apply(console, data); + } + + + comObj.onmessage = function(event) { var data = event.data; if (data.action in actionHandler) { actionHandler[data.action].call(scope, data.data); @@ -15,9 +25,14 @@ function MessageHandler(actionHandler, postMessage, scope) { }; this.send = function(actionName, data) { - postMessage({ - action: actionName, - data: data - }); + try { + comObj.postMessage({ + action: actionName, + data: data + }); + } catch (e) { + console.error("FAILED to send data from", this.name); + throw e; + } } } \ No newline at end of file From eca8f6b80bf9503db13ec717e5f5a1dc55972256 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 5 Sep 2011 18:41:11 -0700 Subject: [PATCH 010/123] Working towards embedded image support --- pdf.js | 37 +++++++++++++++++++++++++++++++++---- worker/boot.js | 14 ++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/pdf.js b/pdf.js index 4deb1f93408e3..f12f2213a1481 100644 --- a/pdf.js +++ b/pdf.js @@ -4251,12 +4251,33 @@ var PartialEvaluator = (function() { ); if ('Form' == type.name) { - args[0].code = this.evaluate(xobj, xref, - xobj.dict.get('Resources'), fonts, - images); + // console.log("got xobj that is a Form"); + args[0].code = this.eval(xobj, xref, xobj.dict.get('Resources'), + fonts, images); } - if (xobj instanceof JpegStream) + if (xobj instanceof JpegStream) { images.bind(xobj); // monitoring image load + // console.log("got xobj that is a JpegStream"); + } + + if (xobj.dict.get('Subtype').name == "Image") { + // Check if we have an image that is not rendered by the platform. + // Needs to be rendered ourself. + if (!(xobj instanceof JpegStream)) { + var image = xobj; + var dict = image.dict; + var w = dict.get('Width', 'W'); + var h = dict.get('Height', 'H'); + + var inline = false; + + var imageObj = new PDFImage(xref, resources, image, inline); + + console.log("xobj subtype image", w, h, imageObj.imageMask); + } + } + console.log("xobj subtype", xobj.dict.get('Subtype').name); + } } else if (cmd == 'Tf') { // eagerly collect all fonts var fontName = args[0].name; @@ -4285,6 +4306,14 @@ var PartialEvaluator = (function() { } } + var skips = ["paintXObject"]; + + if (skips.indexOf(fn) != -1) { + // console.log("skipping", fn); + args = []; + continue; + } + fnArray.push(fn); argsArray.push(args); args = []; diff --git a/worker/boot.js b/worker/boot.js index da09f21c50990..57314c13f1530 100644 --- a/worker/boot.js +++ b/worker/boot.js @@ -50,6 +50,20 @@ var handler = new MessageHandler("worker", { // TODO: Handle images here. + console.log("about to send page", pageNum); + + // Make a copy of the fnArray and show all cmds it has. + var fnArray = preCompilation.fnArray.slice(0).sort(); + for (var i = 0; i < fnArray.length; true) { + if (fnArray[i] == fnArray[i + 1]) { + fnArray.splice(i, 1); + } else { + i++; + } + } + console.log("cmds", fnArray); + + handler.send("page", { pageNum: pageNum, fonts: fontsMin, From 67f745443e3cc7df8bcc1fba645ce3b5770682ca Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 5 Sep 2011 19:38:22 -0700 Subject: [PATCH 011/123] Add support for xobj embedded images that are not rendered by the platform + has no mask --- pdf.js | 54 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/pdf.js b/pdf.js index f12f2213a1481..db4d23179062a 100644 --- a/pdf.js +++ b/pdf.js @@ -4272,6 +4272,21 @@ var PartialEvaluator = (function() { var inline = false; var imageObj = new PDFImage(xref, resources, image, inline); + + if (imageObj.imageMask) { + throw "Can't handle this in the web worker :/"; + } + + var imgData = { + width: w, + height: h, + data: new Uint8Array(w * h * 4) + }; + var pixels = imgData.data; + imageObj.fillRgbaBuffer(pixels, imageObj.decode); + + fn = "paintReadyImageXObject"; + args = [ imgData ]; console.log("xobj subtype image", w, h, imageObj.imageMask); } @@ -4306,13 +4321,13 @@ var PartialEvaluator = (function() { } } - var skips = ["paintXObject"]; - - if (skips.indexOf(fn) != -1) { - // console.log("skipping", fn); - args = []; - continue; - } + // var skips = ["paintXObject"]; + // + // if (skips.indexOf(fn) != -1) { + // // console.log("skipping", fn); + // args = []; + // continue; + // } fnArray.push(fn); argsArray.push(args); @@ -5403,6 +5418,31 @@ var CanvasGraphics = (function() { ctx.drawImage(tmpCanvas, 0, -h); this.restore(); }, + + paintReadyImageXObject: function(imgData) { + this.save(); + + var ctx = this.ctx; + var w = imgData.width; + var h = imgData.height; + // scale the image to the unit square + ctx.scale(1 / w, -1 / h); + + + var tmpCanvas = new this.ScratchCanvas(w, h); + var tmpCtx = tmpCanvas.getContext('2d'); + var tmpImgData = tmpCtx.getImageData(0, 0, w, h); + + // Copy over the imageData. + var tmpImgDataPixels = tmpImgData.data; + var len = tmpImgDataPixels.length; + while (len--) + tmpImgDataPixels[len] = imgData.data[len]; + + tmpCtx.putImageData(tmpImgData, 0, 0); + ctx.drawImage(tmpCanvas, 0, -h); + this.restore(); + }, // Marked content From 2fcc93c7516640e05f09164e5795dda0a033bb2d Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 5 Sep 2011 19:47:16 -0700 Subject: [PATCH 012/123] Make xObjForm use raw format --- pdf.js | 7 ++++--- web/viewer.js | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pdf.js b/pdf.js index db4d23179062a..2a494bf41e57d 100644 --- a/pdf.js +++ b/pdf.js @@ -4252,8 +4252,8 @@ var PartialEvaluator = (function() { if ('Form' == type.name) { // console.log("got xobj that is a Form"); - args[0].code = this.eval(xobj, xref, xobj.dict.get('Resources'), - fonts, images); + args[0].raw = this.evalRaw(xobj, xref, xobj.dict.get('Resources'), + fonts, images, uniquePrefix); } if (xobj instanceof JpegStream) { images.bind(xobj); // monitoring image load @@ -5369,7 +5369,8 @@ var CanvasGraphics = (function() { this.endPath(); } - this.execute(ref.code, this.xref, stream.dict.get('Resources')); + var code = this.pe.evalFromRaw(ref.raw) + this.execute(code, this.xref, stream.dict.get('Resources')); this.restore(); }, diff --git a/web/viewer.js b/web/viewer.js index 082dfaa87815e..a65eb2e77ea64 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -152,7 +152,8 @@ var PDFView = { while (container.hasChildNodes()) container.removeChild(container.lastChild); - var pdf = new WorkerPDFDoc(data); + // var pdf = new WorkerPDFDoc(data); + var pdf = new PDFDoc(new Stream(data)); var pagesCount = pdf.numPages; document.getElementById('numPages').innerHTML = pagesCount; From aa8699a33416a8f5e127988ff744f5fe0254db1f Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Tue, 6 Sep 2011 10:44:26 -0700 Subject: [PATCH 013/123] Don't display cmds used on the page by default + turn on worker support for now again --- web/viewer.js | 9 +++++++-- worker/boot.js | 23 ++++++++++++----------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/web/viewer.js b/web/viewer.js index a65eb2e77ea64..0273f7170aa58 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -152,8 +152,13 @@ var PDFView = { while (container.hasChildNodes()) container.removeChild(container.lastChild); - // var pdf = new WorkerPDFDoc(data); - var pdf = new PDFDoc(new Stream(data)); + var pdf; + if (true /* Use Worker */) { + pdf = new WorkerPDFDoc(data); + } else { + pdf = new PDFDoc(new Stream(data)); + } + var pagesCount = pdf.numPages; document.getElementById('numPages').innerHTML = pagesCount; diff --git a/worker/boot.js b/worker/boot.js index 57314c13f1530..89d9acbbb911a 100644 --- a/worker/boot.js +++ b/worker/boot.js @@ -51,18 +51,19 @@ var handler = new MessageHandler("worker", { // TODO: Handle images here. console.log("about to send page", pageNum); - - // Make a copy of the fnArray and show all cmds it has. - var fnArray = preCompilation.fnArray.slice(0).sort(); - for (var i = 0; i < fnArray.length; true) { - if (fnArray[i] == fnArray[i + 1]) { - fnArray.splice(i, 1); - } else { - i++; + + if (false /* show used commands */) { + // Make a copy of the fnArray and show all cmds it has. + var fnArray = preCompilation.fnArray.slice(0).sort(); + for (var i = 0; i < fnArray.length; true) { + if (fnArray[i] == fnArray[i + 1]) { + fnArray.splice(i, 1); + } else { + i++; + } } - } - console.log("cmds", fnArray); - + console.log("cmds", fnArray); + } handler.send("page", { pageNum: pageNum, From 5c1262e13bb44f28489c080081bb9b3f7fc12b1e Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Tue, 6 Sep 2011 10:45:27 -0700 Subject: [PATCH 014/123] Add limited support for setFill/StrokeColorSpace --- pdf.js | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 163 insertions(+), 14 deletions(-) diff --git a/pdf.js b/pdf.js index 2a494bf41e57d..fe844b68b6443 100644 --- a/pdf.js +++ b/pdf.js @@ -4252,8 +4252,12 @@ var PartialEvaluator = (function() { if ('Form' == type.name) { // console.log("got xobj that is a Form"); - args[0].raw = this.evalRaw(xobj, xref, xobj.dict.get('Resources'), + var raw = this.evalRaw(xobj, xref, xobj.dict.get('Resources'), fonts, images, uniquePrefix); + var matrix = xobj.dict.get('Matrix'); + var bbox = xobj.dict.get('BBox'); + args = [raw, matrix, bbox]; + fn = "paintReadyFormXObject"; } if (xobj instanceof JpegStream) { images.bind(xobj); // monitoring image load @@ -4288,10 +4292,10 @@ var PartialEvaluator = (function() { fn = "paintReadyImageXObject"; args = [ imgData ]; - console.log("xobj subtype image", w, h, imageObj.imageMask); + // console.log("xobj subtype image", w, h, imageObj.imageMask); } } - console.log("xobj subtype", xobj.dict.get('Subtype').name); + // console.log("xobj subtype", xobj.dict.get('Subtype').name); } } else if (cmd == 'Tf') { // eagerly collect all fonts @@ -4321,13 +4325,23 @@ var PartialEvaluator = (function() { } } - // var skips = ["paintXObject"]; - // - // if (skips.indexOf(fn) != -1) { - // // console.log("skipping", fn); - // args = []; - // continue; - // } + // Transform some cmds. + switch (fn) { + // Parse the ColorSpace data to a raw format. + case "setFillColorSpace": + case "setStrokeColorSpace": + args = [ ColorSpace.parseRaw(args[0], xref, resources) ]; + break; + } + + var skips = []; + //var skips = ["setFillColorSpace", "setFillColor", "setStrokeColorSpace", "setStrokeColor"]; + + if (skips.indexOf(fn) != -1) { + // console.log("skipping", fn); + args = []; + continue; + } fnArray.push(fn); argsArray.push(args); @@ -5193,13 +5207,15 @@ var CanvasGraphics = (function() { }, // Color - setStrokeColorSpace: function(space) { + setStrokeColorSpace: function(raw) { this.current.strokeColorSpace = - ColorSpace.parse(space, this.xref, this.res); + ColorSpace.fromRaw(raw); + // ColorSpace.parse(space, this.xref, this.res); }, - setFillColorSpace: function(space) { + setFillColorSpace: function(raw) { this.current.fillColorSpace = - ColorSpace.parse(space, this.xref, this.res); + ColorSpace.fromRaw(raw); + // ColorSpace.parse(space, this.xref, this.res); }, setStrokeColor: function(/*...*/) { var cs = this.current.strokeColorSpace; @@ -5354,6 +5370,25 @@ var CanvasGraphics = (function() { malformed('Unknown XObject subtype ' + type.name); } }, + + paintReadyFormXObject: function(raw, matrix, bbox) { + this.save(); + + if (matrix && IsArray(matrix) && 6 == matrix.length) + this.transform.apply(this, matrix); + + if (bbox && IsArray(bbox) && 4 == bbox.length) { + this.rectangle.apply(this, bbox); + this.clip(); + this.endPath(); + } + + var code = this.pe.evalFromRaw(raw) + // this.execute(code, this.xref, stream.dict.get('Resources')); + this.execute(code, this.xref, null); + + this.restore(); + }, paintFormXObject: function(ref, stream) { this.save(); @@ -5627,6 +5662,120 @@ var ColorSpace = (function() { } return null; }; + + constructor.fromRaw = function(raw) { + var name; + if (IsArray(raw)) { + name = raw[0]; + } else { + name = raw; + } + + switch (name) { + case "DeviceGrayCS": + return new DeviceGrayCS(); + case "DeviceRgbCS": + return new DeviceRgbCS(); + case "DeviceCmykCS": + return new DeviceCmykCS(); + case "Pattern": + return new PatternCS(raw[1]); + default: + error("Unkown name " + name); + } + } + + constructor.parseRaw = function colorspace_parse(cs, xref, res) { + if (IsName(cs)) { + var colorSpaces = res.get('ColorSpace'); + if (IsDict(colorSpaces)) { + var refcs = colorSpaces.get(cs.name); + if (refcs) + cs = refcs; + } + } + + cs = xref.fetchIfRef(cs); + + if (IsName(cs)) { + var mode = cs.name; + this.mode = mode; + + switch (mode) { + case 'DeviceGray': + case 'G': + return "DeviceGrayCS"; + case 'DeviceRGB': + case 'RGB': + return "DeviceRgbCS"; + case 'DeviceCMYK': + case 'CMYK': + return "DeviceCmykCS"; + case 'Pattern': + return ["PatternCS", null]; + default: + error('unrecognized colorspace ' + mode); + } + } else if (IsArray(cs)) { + var mode = cs[0].name; + this.mode = mode; + + switch (mode) { + case 'DeviceGray': + case 'G': + return "DeviceGrayCS"; + case 'DeviceRGB': + case 'RGB': + return "DeviceRgbCS"; + case 'DeviceCMYK': + case 'CMYK': + return "DeviceCmykCS"; + case 'CalGray': + return "DeviceGrayCS"; + case 'CalRGB': + return "DeviceRgbCS"; + case 'ICCBased': + var stream = xref.fetchIfRef(cs[1]); + var dict = stream.dict; + var numComps = dict.get('N'); + if (numComps == 1) + return "DeviceGrayCS"; + if (numComps == 3) + return "DeviceRgbCS"; + if (numComps == 4) + return "DeviceCmykCS"; + break; + case 'Pattern': + // TODO: IMPLEMENT ME + + // var baseCS = cs[1]; + // if (baseCS) + // baseCS = ColorSpace.parse(baseCS, xref, res); + // return new PatternCS(baseCS); + case 'Indexed': + // TODO: IMPLEMENT ME + + // var base = ColorSpace.parse(cs[1], xref, res); + // var hiVal = cs[2] + 1; + // var lookup = xref.fetchIfRef(cs[3]); + // return new IndexedCS(base, hiVal, lookup); + case 'Separation': + // TODO: IMPLEMENT ME + + // var name = cs[1]; + // var alt = ColorSpace.parse(cs[2], xref, res); + // var tintFn = new PDFFunction(xref, xref.fetchIfRef(cs[3])); + // return new SeparationCS(alt, tintFn); + case 'Lab': + case 'DeviceN': + default: + error('unimplemented color space object "' + mode + '"'); + } + } else { + error('unrecognized color space object: "' + cs + '"'); + } + return null; + }; return constructor; })(); From 89167c0b406cb90465ae59f7a8459b1cf8980b72 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Tue, 6 Sep 2011 15:02:15 -0700 Subject: [PATCH 015/123] Saving last work --- pdf.js | 6 ++++++ worker/boot.js | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index fe844b68b6443..0c448c4d495f0 100644 --- a/pdf.js +++ b/pdf.js @@ -4212,6 +4212,7 @@ var PartialEvaluator = (function() { var patterns = xref.fetchIfRef(resources.get('Pattern')) || new Dict(); var parser = new Parser(new Lexer(stream), false); var args = [], argsArray = [], fnArray = [], obj; + var res = resources; while (!IsEOF(obj = parser.getObj())) { if (IsCmd(obj)) { @@ -4331,9 +4332,14 @@ var PartialEvaluator = (function() { case "setFillColorSpace": case "setStrokeColorSpace": args = [ ColorSpace.parseRaw(args[0], xref, resources) ]; + break; + case "shadingFill": + break; } + + var skips = []; //var skips = ["setFillColorSpace", "setFillColor", "setStrokeColorSpace", "setStrokeColor"]; diff --git a/worker/boot.js b/worker/boot.js index 89d9acbbb911a..5a30f71925fb5 100644 --- a/worker/boot.js +++ b/worker/boot.js @@ -52,7 +52,7 @@ var handler = new MessageHandler("worker", { console.log("about to send page", pageNum); - if (false /* show used commands */) { + if (true /* show used commands */) { // Make a copy of the fnArray and show all cmds it has. var fnArray = preCompilation.fnArray.slice(0).sort(); for (var i = 0; i < fnArray.length; true) { From a3baea5fcb63aa46c13e62445fea0d29ce46c94c Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 08:50:13 -0700 Subject: [PATCH 016/123] Make shadingFill IR form --- pdf.js | 118 ++++++++++++++++++++++++++++++++++++++++++++++--- worker/boot.js | 29 +++++++++--- 2 files changed, 135 insertions(+), 12 deletions(-) diff --git a/pdf.js b/pdf.js index 0c448c4d495f0..dbd255e6cf4b2 100644 --- a/pdf.js +++ b/pdf.js @@ -4334,13 +4334,26 @@ var PartialEvaluator = (function() { args = [ ColorSpace.parseRaw(args[0], xref, resources) ]; break; case "shadingFill": + var shadingRes = xref.fetchIfRef(res.get('Shading')); + if (!shadingRes) + error('No shading resource found'); + + var shading = xref.fetchIfRef(shadingRes.get(args[0].name)); + if (!shading) + error('No shading object found'); + + var shadingFill = Pattern.parseShading(shading, null, xref, res, /* ctx */ null); + var patternRaw = shadingFill.getPatternRaw(); + + args = [ patternRaw ]; + fn = "shadingFillRaw"; break; } - var skips = []; + var skips = [ "setFillColorN" ];//[ "paintReadyFormXObject" ]; //var skips = ["setFillColorSpace", "setFillColor", "setStrokeColorSpace", "setStrokeColor"]; if (skips.indexOf(fn) != -1) { @@ -5335,6 +5348,43 @@ var CanvasGraphics = (function() { this.restore(); }, + shadingFillRaw: function(patternRaw) { + var ctx = this.ctx; + + this.save(); + ctx.fillStyle = Pattern.shadingFromRaw(ctx, patternRaw); + + var inv = ctx.mozCurrentTransformInverse; + if (inv) { + var canvas = ctx.canvas; + var width = canvas.width; + var height = canvas.height; + + var bl = Util.applyTransform([0, 0], inv); + var br = Util.applyTransform([0, width], inv); + var ul = Util.applyTransform([height, 0], inv); + var ur = Util.applyTransform([height, width], inv); + + var x0 = Math.min(bl[0], br[0], ul[0], ur[0]); + var y0 = Math.min(bl[1], br[1], ul[1], ur[1]); + var x1 = Math.max(bl[0], br[0], ul[0], ur[0]); + var y1 = Math.max(bl[1], br[1], ul[1], ur[1]); + + this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0); + } else { + // HACK to draw the gradient onto an infinite rectangle. + // PDF gradients are drawn across the entire image while + // Canvas only allows gradients to be drawn in a rectangle + // The following bug should allow us to remove this. + // https://bugzilla.mozilla.org/show_bug.cgi?id=664884 + + this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); + } + + this.restore(); + + }, + // Images beginInlineImage: function() { error('Should not call beginInlineImage'); @@ -5684,7 +5734,7 @@ var ColorSpace = (function() { return new DeviceRgbCS(); case "DeviceCmykCS": return new DeviceCmykCS(); - case "Pattern": + case "PatternCS": return new PatternCS(raw[1]); default: error("Unkown name " + name); @@ -6042,6 +6092,12 @@ var Pattern = (function() { } }; + constructor.shadingFromRaw = function(ctx, raw) { + var obj = window[raw[0]]; + + return obj.fromRaw(ctx, raw); + } + constructor.parse = function pattern_parse(args, cs, xref, res, ctx) { var length = args.length; @@ -6122,7 +6178,6 @@ var RadialAxialShading = (function() { this.type = 'Pattern'; this.ctx = ctx; - this.curMatrix = ctx.mozCurrentTransform; var cs = dict.get('ColorSpace', 'CS'); cs = ColorSpace.parse(cs, xref, res); @@ -6170,6 +6225,35 @@ var RadialAxialShading = (function() { this.colorStops = colorStops; } + constructor.fromRaw = function(ctx, raw) { + var type = raw[1]; + var colorStops = raw[2]; + var p0 = raw[3]; + var p1 = raw[4]; + var r0 = raw[5]; + + if (ctx.mozCurrentTransform) { + var userMatrix = ctx.mozCurrentTransformInverse; + + p0 = Util.applyTransform(p0, curMatrix); + p0 = Util.applyTransform(p0, userMatrix); + + p1 = Util.applyTransform(p1, curMatrix); + p1 = Util.applyTransform(p1, userMatrix); + } + + if (type == 2) + var grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); + else if (type == 3) + var grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); + + for (var i = 0, ii = colorStops.length; i < ii; ++i) { + var c = colorStops[i]; + grad.addColorStop(c[0], c[1]); + } + return grad; + } + constructor.prototype = { getPattern: function() { var coordsArr = this.coordsArr; @@ -6195,9 +6279,8 @@ var RadialAxialShading = (function() { // if the browser supports getting the tranform matrix, convert // gradient coordinates from pattern space to current user space - var curMatrix = this.curMatrix; var ctx = this.ctx; - if (curMatrix) { + if (ctx.mozCurrentTransform) { var userMatrix = ctx.mozCurrentTransformInverse; p0 = Util.applyTransform(p0, curMatrix); @@ -6218,8 +6301,33 @@ var RadialAxialShading = (function() { grad.addColorStop(c[0], c[1]); } return grad; + }, + + getPatternRaw: function() { + var coordsArr = this.coordsArr; + var type = this.shadingType; + if (type == 2) { + var p0 = [coordsArr[0], coordsArr[1]]; + var p1 = [coordsArr[2], coordsArr[3]]; + var r0 = null; + } else if (type == 3) { + var p0 = [coordsArr[0], coordsArr[1]]; + var p1 = [coordsArr[3], coordsArr[4]]; + var r0 = coordsArr[2], r1 = coordsArr[5]; + } else { + error(); + } + + var matrix = this.matrix; + if (matrix) { + p0 = Util.applyTransform(p0, matrix); + p1 = Util.applyTransform(p1, matrix); + } + + return [ "RadialAxialShading", type, this.colorStops, p0, p1, r0 ]; } }; + return constructor; })(); diff --git a/worker/boot.js b/worker/boot.js index 5a30f71925fb5..be8ae40d5b9eb 100644 --- a/worker/boot.js +++ b/worker/boot.js @@ -53,16 +53,31 @@ var handler = new MessageHandler("worker", { console.log("about to send page", pageNum); if (true /* show used commands */) { - // Make a copy of the fnArray and show all cmds it has. - var fnArray = preCompilation.fnArray.slice(0).sort(); - for (var i = 0; i < fnArray.length; true) { - if (fnArray[i] == fnArray[i + 1]) { - fnArray.splice(i, 1); + var cmdMap = {}; + + var fnArray = preCompilation.fnArray; + for (var i = 0; i < fnArray.length; i++) { + var entry = fnArray[i]; + if (entry == "paintReadyFormXObject") { + //console.log(preCompilation.argsArray[i]); + } + if (cmdMap[entry] == null) { + cmdMap[entry] = 1; } else { - i++; + cmdMap[entry] += 1; } } - console.log("cmds", fnArray); + + // // Make a copy of the fnArray and show all cmds it has. + // var fnArray = preCompilation.fnArray.slice(0).sort(); + // for (var i = 0; i < fnArray.length; true) { + // if (fnArray[i] == fnArray[i + 1]) { + // fnArray.splice(i, 1); + // } else { + // i++; + // } + // } + console.log("cmds", JSON.stringify(cmdMap)); } handler.send("page", { From ac4a57e858869c78f0936115c36d84f80c6f0622 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 10:16:02 -0700 Subject: [PATCH 017/123] Refactor to execute IR on main thead by posting messages to itself --- pdf.js | 1 + web/viewer.html | 1 + worker.js | 61 ++++++++++++++++++---------- worker/boot.js | 78 ++---------------------------------- worker/handler.js | 84 +++++++++++++++++++++++++++++++++++++++ worker/message_handler.js | 38 ++++++++++++------ 6 files changed, 154 insertions(+), 109 deletions(-) create mode 100644 worker/handler.js diff --git a/pdf.js b/pdf.js index dbd255e6cf4b2..e2aece9b74705 100644 --- a/pdf.js +++ b/pdf.js @@ -5802,6 +5802,7 @@ var ColorSpace = (function() { return "DeviceCmykCS"; break; case 'Pattern': + console.log("ColorSpace Pattern"); // TODO: IMPLEMENT ME // var baseCS = cs[1]; diff --git a/web/viewer.html b/web/viewer.html index 871db8ce6c204..00076aa80cd7d 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -13,6 +13,7 @@ + diff --git a/worker.js b/worker.js index 1eb65b7b9c82e..cbe62e3d7e82e 100644 --- a/worker.js +++ b/worker.js @@ -60,31 +60,48 @@ var WorkerPDFDoc = (function() { this.pageCache = []; - this.worker = new Worker("../worker/boot.js"); - this.handler = new MessageHandler("main", { - "page": function(data) { - var pageNum = data.pageNum; - var page = this.pageCache[pageNum]; - - // Add necessary shape back to fonts. - var fonts = data.fonts; - for (var i = 0; i < fonts.length; i++) { - var font = fonts[i]; - - var fontFileDict = new Dict(); - fontFileDict.map = font.file.dict.map; - - var fontFile = new Stream(font.file.bytes, font.file.start, - font.file.end - font.file.start, fontFileDict); - font.file = new FlateStream(fontFile); + var useWorker = false; + + if (useWorker) { + var worker = new Worker("../worker/boot.js"); + } else { + // If we don't use a worker, just post/sendMessage to the main thread. + var worker = { + postMessage: function(obj) { + worker.onmessage({data: obj}); } + } + } + + var handler = this.handler = new MessageHandler("main", worker); + handler.on("page", function(data) { + var pageNum = data.pageNum; + var page = this.pageCache[pageNum]; + + // Add necessary shape back to fonts. + var fonts = data.fonts; + for (var i = 0; i < fonts.length; i++) { + var font = fonts[i]; - console.log("startRenderingFromPreCompilation:", "numberOfFonts", fonts.length); - page.startRenderingFromPreCompilation(data.preCompilation, data.fonts, data.images); + var fontFileDict = new Dict(); + fontFileDict.map = font.file.dict.map; + + var fontFile = new Stream(font.file.bytes, font.file.start, + font.file.end - font.file.start, fontFileDict); + font.file = new FlateStream(fontFile); } - }, this.worker, this); + + console.log("startRenderingFromPreCompilation:", "numberOfFonts", fonts.length); + page.startRenderingFromPreCompilation(data.preCompilation, data.fonts, data.images); + }, this); + + if (!useWorker) { + // If the main thread is our worker, setup the handling for the messages + // the main thread sends to it self. + WorkerHandler.setup(handler); + } - this.handler.send("doc", data); + handler.send("doc", data); } constructor.prototype = { @@ -93,7 +110,7 @@ var WorkerPDFDoc = (function() { }, startRendering: function(page) { - this.handler.send("page", page.page.pageNumber + 1); + this.handler.send("page_request", page.page.pageNumber + 1); }, getPage: function(n) { diff --git a/worker/boot.js b/worker/boot.js index be8ae40d5b9eb..241870c5c4dc4 100644 --- a/worker/boot.js +++ b/worker/boot.js @@ -9,82 +9,10 @@ importScripts('../pdf.js'); importScripts('../fonts.js'); importScripts('../crypto.js'); importScripts('../glyphlist.js'); +importScripts('handler.js'); // Listen for messages from the main thread. var pdfDoc = null; -var handler = new MessageHandler("worker", { - "doc": function(data) { - pdfDoc = new PDFDoc(new Stream(data)); - console.log("setup pdfDoc"); - }, - - "page": function(pageNum) { - pageNum = parseInt(pageNum); - console.log("about to process page", pageNum); - - var page = pdfDoc.getPage(pageNum); - - // The following code does quite the same as Page.prototype.startRendering, - // but stops at one point and sends the result back to the main thread. - var gfx = new CanvasGraphics(null); - var fonts = []; - // TODO: Figure out how image loading is handled inside the worker. - var images = new ImagesLoader(); - - // Pre compile the pdf page and fetch the fonts/images. - var preCompilation = page.preCompile(gfx, fonts, images); - - // Extract the minimum of font data that is required to build all required - // font stuff on the main thread. - var fontsMin = []; - for (var i = 0; i < fonts.length; i++) { - var font = fonts[i]; - - fontsMin.push({ - name: font.name, - file: font.file, - properties: font.properties - }); - } - - // TODO: Handle images here. - - console.log("about to send page", pageNum); - - if (true /* show used commands */) { - var cmdMap = {}; - - var fnArray = preCompilation.fnArray; - for (var i = 0; i < fnArray.length; i++) { - var entry = fnArray[i]; - if (entry == "paintReadyFormXObject") { - //console.log(preCompilation.argsArray[i]); - } - if (cmdMap[entry] == null) { - cmdMap[entry] = 1; - } else { - cmdMap[entry] += 1; - } - } - - // // Make a copy of the fnArray and show all cmds it has. - // var fnArray = preCompilation.fnArray.slice(0).sort(); - // for (var i = 0; i < fnArray.length; true) { - // if (fnArray[i] == fnArray[i + 1]) { - // fnArray.splice(i, 1); - // } else { - // i++; - // } - // } - console.log("cmds", JSON.stringify(cmdMap)); - } - - handler.send("page", { - pageNum: pageNum, - fonts: fontsMin, - images: [], - preCompilation: preCompilation, - }); - } -}, this); +var handler = new MessageHandler("worker", this); +WorkerHandler.setup(handler); diff --git a/worker/handler.js b/worker/handler.js new file mode 100644 index 0000000000000..ca8413ede357a --- /dev/null +++ b/worker/handler.js @@ -0,0 +1,84 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +var WorkerHandler = { + setup: function(handler) { + var pdfDoc = null; + + handler.on("doc", function(data) { + pdfDoc = new PDFDoc(new Stream(data)); + console.log("setup pdfDoc"); + }); + + handler.on("page_request", function(pageNum) { + pageNum = parseInt(pageNum); + console.log("about to process page", pageNum); + + var page = pdfDoc.getPage(pageNum); + + // The following code does quite the same as Page.prototype.startRendering, + // but stops at one point and sends the result back to the main thread. + var gfx = new CanvasGraphics(null); + var fonts = []; + // TODO: Figure out how image loading is handled inside the worker. + var images = new ImagesLoader(); + + // Pre compile the pdf page and fetch the fonts/images. + var preCompilation = page.preCompile(gfx, fonts, images); + + // Extract the minimum of font data that is required to build all required + // font stuff on the main thread. + var fontsMin = []; + for (var i = 0; i < fonts.length; i++) { + var font = fonts[i]; + + fontsMin.push({ + name: font.name, + file: font.file, + properties: font.properties + }); + } + + // TODO: Handle images here. + + console.log("about to send page", pageNum); + + if (true /* show used commands */) { + var cmdMap = {}; + + var fnArray = preCompilation.fnArray; + for (var i = 0; i < fnArray.length; i++) { + var entry = fnArray[i]; + if (entry == "paintReadyFormXObject") { + //console.log(preCompilation.argsArray[i]); + } + if (cmdMap[entry] == null) { + cmdMap[entry] = 1; + } else { + cmdMap[entry] += 1; + } + } + + // // Make a copy of the fnArray and show all cmds it has. + // var fnArray = preCompilation.fnArray.slice(0).sort(); + // for (var i = 0; i < fnArray.length; true) { + // if (fnArray[i] == fnArray[i + 1]) { + // fnArray.splice(i, 1); + // } else { + // i++; + // } + // } + console.log("cmds", JSON.stringify(cmdMap)); + } + + handler.send("page", { + pageNum: pageNum, + fonts: fontsMin, + images: [], + preCompilation: preCompilation, + }); + }, this); + } +} \ No newline at end of file diff --git a/worker/message_handler.js b/worker/message_handler.js index 17c6e4aea18a5..63e8277e24d87 100644 --- a/worker/message_handler.js +++ b/worker/message_handler.js @@ -4,29 +4,41 @@ 'use strict'; -function MessageHandler(name, actionHandler, comObj, scope) { +function MessageHandler(name, comObj) { this.name = name; + this.comObj = comObj; + var ah = this.actionHandler = {}; - actionHandler["console_log"] = function(data) { + ah["console_log"] = [function(data) { console.log.apply(console, data); - } - actionHandler["console_error"] = function(data) { + }] + ah["console_error"] = [function(data) { console.error.apply(console, data); - } - + }] comObj.onmessage = function(event) { var data = event.data; - if (data.action in actionHandler) { - actionHandler[data.action].call(scope, data.data); + if (data.action in ah) { + var action = ah[data.action]; + action[0].call(action[1], data.data); } else { throw 'Unkown action from worker: ' + data.action; } }; - - this.send = function(actionName, data) { +} + +MessageHandler.prototype = { + on: function(actionName, handler, scope) { + var ah = this.actionHandler; + if (ah[actionName]) { + throw "There is already an actionName called '" + actionName + "'"; + } + ah[actionName] = [handler, scope]; + }, + + send: function(actionName, data) { try { - comObj.postMessage({ + this.comObj.postMessage({ action: actionName, data: data }); @@ -35,4 +47,6 @@ function MessageHandler(name, actionHandler, comObj, scope) { throw e; } } -} \ No newline at end of file + +} + From c77c6d524c4445c5dc8d155327074c599547e335 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 11:30:35 -0700 Subject: [PATCH 018/123] Add setFillColorN_IR by implementing new TilingPatternIR --- pdf.js | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 157 insertions(+), 13 deletions(-) diff --git a/pdf.js b/pdf.js index e2aece9b74705..2ea59079f426f 100644 --- a/pdf.js +++ b/pdf.js @@ -4230,10 +4230,18 @@ var PartialEvaluator = (function() { if (pattern) { var dict = IsStream(pattern) ? pattern.dict : pattern; var typeNum = dict.get('PatternType'); + + // Type1 is TilingPattern, Type2 is ShadingPattern. if (typeNum == 1) { - patternName.code = this.evaluate(pattern, xref, - dict.get('Resources'), - fonts); + // Create an IR of the pattern code. + var codeIR = this.evalRaw(pattern, xref, + dict.get('Resources'), fonts); + + fn = "setFillColorN_IR"; + args = TilingPattern.getIR(codeIR, dict); + + //patternName.code = this.evalRaw(pattern, xref, + // dict.get('Resources'), fonts); } } } @@ -4884,6 +4892,13 @@ var CanvasGraphics = (function() { this.xref = savedXref; }, + executeIR: function(codeIR) { + var argsArray = codeIR.argsArray; + var fnArray = codeIR.fnArray; + for (var i = 0, length = argsArray.length; i < length; i++) + this[fnArray[i]].apply(this, argsArray[i]); + }, + endDrawing: function() { this.ctx.restore(); }, @@ -5272,6 +5287,36 @@ var CanvasGraphics = (function() { this.setFillColor.apply(this, arguments); } }, + setFillColorN_IR: function(/*...*/) { + var cs = this.current.fillColorSpace; + + if (cs.name == 'Pattern') { + var IR = arguments; + if (IR[0] == "TilingPatternIR") { + // First, build the `color` var like it's done in the + // Pattern.prototype.parse function. + var base = cs.base; + var color; + if (base) { + var baseComps = base.numComps; + + color = []; + for (var i = 0; i < baseComps; ++i) + color.push(args[i]); + + color = base.getRgb(color); + } + + // Build the pattern based on the IR data. + var pattern = new TilingPatternIR(IR, color, this.ctx); + } else { + throw "Unkown IR type"; + } + this.current.fillColor = pattern; + } else { + this.setFillColor.apply(this, arguments); + } + }, setStrokeGray: function(gray) { this.setStrokeRGBColor(gray, gray, gray); }, @@ -6332,20 +6377,119 @@ var RadialAxialShading = (function() { return constructor; })(); +var TilingPatternIR = (function() { + var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; + + function TilingPatternIR(IR, color, ctx) { + // "Unfolding" the IR. + var codeIR = IR[1]; + this.matrix = IR[2]; + var bbox = IR[3]; + var xstep = IR[4]; + var ystep = IR[5]; + var paintType = IR[6]; + + // + TODO('TilingType'); + + this.curMatrix = ctx.mozCurrentTransform; + this.invMatrix = ctx.mozCurrentTransformInverse; + this.ctx = ctx; + this.type = 'Pattern'; + + var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; + var topLeft = [x0, y0]; + // we want the canvas to be as large as the step size + var botRight = [x0 + xstep, y0 + ystep]; + + var width = botRight[0] - topLeft[0]; + var height = botRight[1] - topLeft[1]; + + // TODO: hack to avoid OOM, we would idealy compute the tiling + // pattern to be only as large as the acual size in device space + // This could be computed with .mozCurrentTransform, but still + // needs to be implemented + while (Math.abs(width) > 512 || Math.abs(height) > 512) { + width = 512; + height = 512; + } + + var tmpCanvas = new ScratchCanvas(width, height); + + // set the new canvas element context as the graphics context + var tmpCtx = tmpCanvas.getContext('2d'); + var graphics = new CanvasGraphics(tmpCtx); + + switch (paintType) { + case PAINT_TYPE_COLORED: + tmpCtx.fillStyle = ctx.fillStyle; + tmpCtx.strokeStyle = ctx.strokeStyle; + break; + case PAINT_TYPE_UNCOLORED: + color = Util.makeCssRgb.apply(this, color); + tmpCtx.fillStyle = color; + tmpCtx.strokeStyle = color; + break; + default: + error('Unsupported paint type: ' + paintType); + } + + var scale = [width / xstep, height / ystep]; + this.scale = scale; + + // transform coordinates to pattern space + var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]]; + var tmpScale = [scale[0], 0, 0, scale[1], 0, 0]; + graphics.transform.apply(graphics, tmpScale); + graphics.transform.apply(graphics, tmpTranslate); + + if (bbox && IsArray(bbox) && 4 == bbox.length) { + graphics.rectangle.apply(graphics, bbox); + graphics.clip(); + graphics.endPath(); + } + + graphics.executeIR(codeIR); + + this.canvas = tmpCanvas; + } + + TilingPatternIR.prototype = { + getPattern: function tiling_getPattern() { + var matrix = this.matrix; + var curMatrix = this.curMatrix; + var ctx = this.ctx; + + if (curMatrix) + ctx.setTransform.apply(ctx, curMatrix); + + if (matrix) + ctx.transform.apply(ctx, matrix); + + var scale = this.scale; + ctx.scale(1 / scale[0], 1 / scale[1]); + + return ctx.createPattern(this.canvas, 'repeat'); + } + } + + return TilingPatternIR; +})(); + var TilingPattern = (function() { var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; - function constructor(pattern, code, dict, color, xref, ctx) { - function multiply(m, tm) { - var a = m[0] * tm[0] + m[1] * tm[2]; - var b = m[0] * tm[1] + m[1] * tm[3]; - var c = m[2] * tm[0] + m[3] * tm[2]; - var d = m[2] * tm[1] + m[3] * tm[3]; - var e = m[4] * tm[0] + m[5] * tm[2] + tm[4]; - var f = m[4] * tm[1] + m[5] * tm[3] + tm[5]; - return [a, b, c, d, e, f]; - } + constructor.getIR = function(codeIR, dict) { + var matrix = dict.get('Matrix'); + var bbox = dict.get('BBox'); + var xstep = dict.get('XStep'); + var ystep = dict.get('YStep'); + var paintType = dict.get('PaintType'); + + return ["TilingPatternIR", codeIR, matrix, bbox, xstep, ystep, paintType]; + } + function constructor(pattern, code, dict, color, xref, ctx) { TODO('TilingType'); this.matrix = dict.get('Matrix'); From cdc937a1a4da6afac283fef75499765f5b0c1e34 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 11:40:26 -0700 Subject: [PATCH 019/123] Fix missing curMatrix variable bug to get TM paper work in Nightly --- pdf.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index 2ea59079f426f..49ce746c99236 100644 --- a/pdf.js +++ b/pdf.js @@ -6278,7 +6278,8 @@ var RadialAxialShading = (function() { var p1 = raw[4]; var r0 = raw[5]; - if (ctx.mozCurrentTransform) { + var curMatrix = ctx.mozCurrentTransform; + if (curMatrix) { var userMatrix = ctx.mozCurrentTransformInverse; p0 = Util.applyTransform(p0, curMatrix); From c466450aae05bd29e72d2356b647a6e595a377c8 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 11:48:44 -0700 Subject: [PATCH 020/123] Use the worker --- worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker.js b/worker.js index cbe62e3d7e82e..dae87106cd6ec 100644 --- a/worker.js +++ b/worker.js @@ -60,7 +60,7 @@ var WorkerPDFDoc = (function() { this.pageCache = []; - var useWorker = false; + var useWorker = true; if (useWorker) { var worker = new Worker("../worker/boot.js"); From 9e3de8b339f4afc21d8b25a77ed6ba92f1e63e0a Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 13:30:12 -0700 Subject: [PATCH 021/123] Add shading support for setFillColorN_IR - is that used at all? --- pdf.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index 49ce746c99236..a7fe1e29c1e37 100644 --- a/pdf.js +++ b/pdf.js @@ -4222,6 +4222,8 @@ var PartialEvaluator = (function() { // TODO figure out how to type-check vararg functions if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) { + fn = "setFillColorN_IR"; + // compile tiling patterns var patternName = args[args.length - 1]; // SCN/scn applies patterns along with normal colors @@ -4237,11 +4239,15 @@ var PartialEvaluator = (function() { var codeIR = this.evalRaw(pattern, xref, dict.get('Resources'), fonts); - fn = "setFillColorN_IR"; args = TilingPattern.getIR(codeIR, dict); //patternName.code = this.evalRaw(pattern, xref, // dict.get('Resources'), fonts); + } else { + var shading = xref.fetchIfRef(dict.get('Shading')); + var matrix = dict.get('Matrix'); + var pattern = Pattern.parseShading(shading, matrix, xref, res, null /*ctx*/); + args = pattern.getPatternRaw(); } } } @@ -5309,6 +5315,8 @@ var CanvasGraphics = (function() { // Build the pattern based on the IR data. var pattern = new TilingPatternIR(IR, color, this.ctx); + } else if (IR[0] == "RadialAxialShading") { + var pattern = Pattern.shadingFromRaw(this.ctx, IR); } else { throw "Unkown IR type"; } From 5543d6f44844686ebcc6748187019a130f48d8a2 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 13:38:52 -0700 Subject: [PATCH 022/123] Move getting the Pattern from IR form out and make setStrokeColorN_IR work --- pdf.js | 63 +++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/pdf.js b/pdf.js index a7fe1e29c1e37..1407cd9b41d90 100644 --- a/pdf.js +++ b/pdf.js @@ -4222,7 +4222,8 @@ var PartialEvaluator = (function() { // TODO figure out how to type-check vararg functions if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) { - fn = "setFillColorN_IR"; + // Use the IR version for setStroke/FillColorN. + fn += '_IR'; // compile tiling patterns var patternName = args[args.length - 1]; @@ -5276,6 +5277,41 @@ var CanvasGraphics = (function() { this.setStrokeColor.apply(this, arguments); } }, + getColorN_IR_Pattern: function(IR) { + if (IR[0] == "TilingPatternIR") { + // First, build the `color` var like it's done in the + // Pattern.prototype.parse function. + var base = cs.base; + var color; + if (base) { + var baseComps = base.numComps; + + color = []; + for (var i = 0; i < baseComps; ++i) + color.push(args[i]); + + color = base.getRgb(color); + } + + // Build the pattern based on the IR data. + var pattern = new TilingPatternIR(IR, color, this.ctx); + } else if (IR[0] == "RadialAxialShading") { + var pattern = Pattern.shadingFromRaw(this.ctx, IR); + } else { + throw "Unkown IR type"; + } + return pattern; + }, + setStrokeColorN_IR: function(/*...*/) { + var cs = this.current.strokeColorSpace; + + if (cs.name == 'Pattern') { + this.current.strokeColor = this.getColorN_IR_Pattern(arguments); + } else { + this.setStrokeColor.apply(this, arguments); + } + + }, setFillColor: function(/*...*/) { var cs = this.current.fillColorSpace; var color = cs.getRgb(arguments); @@ -5297,30 +5333,7 @@ var CanvasGraphics = (function() { var cs = this.current.fillColorSpace; if (cs.name == 'Pattern') { - var IR = arguments; - if (IR[0] == "TilingPatternIR") { - // First, build the `color` var like it's done in the - // Pattern.prototype.parse function. - var base = cs.base; - var color; - if (base) { - var baseComps = base.numComps; - - color = []; - for (var i = 0; i < baseComps; ++i) - color.push(args[i]); - - color = base.getRgb(color); - } - - // Build the pattern based on the IR data. - var pattern = new TilingPatternIR(IR, color, this.ctx); - } else if (IR[0] == "RadialAxialShading") { - var pattern = Pattern.shadingFromRaw(this.ctx, IR); - } else { - throw "Unkown IR type"; - } - this.current.fillColor = pattern; + this.current.fillColor = this.getColorN_IR_Pattern(arguments); } else { this.setFillColor.apply(this, arguments); } From 5bfa9e4f3b9f34369def38d0aa9aa5140f654f97 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 13:42:38 -0700 Subject: [PATCH 023/123] Add some comments + fix getColorN_IR_Pattern --- pdf.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pdf.js b/pdf.js index 1407cd9b41d90..7af3e6493197f 100644 --- a/pdf.js +++ b/pdf.js @@ -4234,21 +4234,22 @@ var PartialEvaluator = (function() { var dict = IsStream(pattern) ? pattern.dict : pattern; var typeNum = dict.get('PatternType'); - // Type1 is TilingPattern, Type2 is ShadingPattern. + // Type1 is TilingPattern if (typeNum == 1) { // Create an IR of the pattern code. var codeIR = this.evalRaw(pattern, xref, dict.get('Resources'), fonts); args = TilingPattern.getIR(codeIR, dict); - - //patternName.code = this.evalRaw(pattern, xref, - // dict.get('Resources'), fonts); - } else { + } + // Type2 is ShadingPattern. + else if (typeNum == 2) { var shading = xref.fetchIfRef(dict.get('Shading')); var matrix = dict.get('Matrix'); var pattern = Pattern.parseShading(shading, matrix, xref, res, null /*ctx*/); args = pattern.getPatternRaw(); + } else { + error("Unkown PatternType " + typeNum); } } } @@ -5277,7 +5278,7 @@ var CanvasGraphics = (function() { this.setStrokeColor.apply(this, arguments); } }, - getColorN_IR_Pattern: function(IR) { + getColorN_IR_Pattern: function(IR, cs) { if (IR[0] == "TilingPatternIR") { // First, build the `color` var like it's done in the // Pattern.prototype.parse function. @@ -5306,7 +5307,7 @@ var CanvasGraphics = (function() { var cs = this.current.strokeColorSpace; if (cs.name == 'Pattern') { - this.current.strokeColor = this.getColorN_IR_Pattern(arguments); + this.current.strokeColor = this.getColorN_IR_Pattern(arguments, cs); } else { this.setStrokeColor.apply(this, arguments); } @@ -5333,7 +5334,7 @@ var CanvasGraphics = (function() { var cs = this.current.fillColorSpace; if (cs.name == 'Pattern') { - this.current.fillColor = this.getColorN_IR_Pattern(arguments); + this.current.fillColor = this.getColorN_IR_Pattern(arguments, cs); } else { this.setFillColor.apply(this, arguments); } From d887d2bd29ecc2d89e104b3a814c8b5997151b8c Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 14:45:38 -0700 Subject: [PATCH 024/123] Implement paintReadyJpegXObject + add infrastructure to handle JpegStreams --- pdf.js | 106 +++++++++++++++++++++++++++++++--------------- worker.js | 29 +++++++++++-- worker/handler.js | 7 ++- 3 files changed, 100 insertions(+), 42 deletions(-) diff --git a/pdf.js b/pdf.js index 7af3e6493197f..805122f9e695e 100644 --- a/pdf.js +++ b/pdf.js @@ -859,6 +859,31 @@ var PredictorStream = (function() { return constructor; })(); +var JpegStreamIR = (function() { + function JpegStreamIR(objId, IR) { + var src = IR; + + // create DOM image + var img = new Image(); + img.onload = (function() { + this.loaded = true; + Objects[objId] = this; + if (this.onLoad) + this.onLoad(); + }).bind(this); + img.src = src; + this.domImage = img; + } + + JpegStreamIR.prototype = { + getImage: function() { + return this.domImage; + } + } + + return JpegStreamIR; +})() + // A JpegStream can't be read directly. We use the platform to render // the underlying JPEG data for us. var JpegStream = (function() { @@ -891,30 +916,23 @@ var JpegStream = (function() { newBytes.set(embedMarker, 2); return newBytes; } - + function constructor(bytes, dict) { // TODO: per poppler, some images may have "junk" before that // need to be removed this.dict = dict; - + if (isYcckImage(bytes)) bytes = fixYcckImage(bytes); - // create DOM image - var img = new Image(); - img.onload = (function() { - this.loaded = true; - if (this.onLoad) - this.onLoad(); - }).bind(this); - img.src = 'data:image/jpeg;base64,' + window.btoa(bytesToString(bytes)); - this.domImage = img; + this.src = 'data:image/jpeg;base64,' + window.btoa(bytesToString(bytes)); } constructor.prototype = { - getImage: function() { - return this.domImage; + getIR: function() { + return this.src; }, + getChar: function() { error('internal error: getChar is not valid on JpegStream'); } @@ -4100,6 +4118,8 @@ var EvalState = (function() { var FontsMap = {}; var FontLoadedCounter = 0; +var objIdCounter = 0; + var PartialEvaluator = (function() { function constructor() { this.state = new EvalState(); @@ -4273,25 +4293,27 @@ var PartialEvaluator = (function() { fonts, images, uniquePrefix); var matrix = xobj.dict.get('Matrix'); var bbox = xobj.dict.get('BBox'); - args = [raw, matrix, bbox]; + args = [ raw, matrix, bbox ]; fn = "paintReadyFormXObject"; - } - if (xobj instanceof JpegStream) { - images.bind(xobj); // monitoring image load - // console.log("got xobj that is a JpegStream"); - } - - if (xobj.dict.get('Subtype').name == "Image") { - // Check if we have an image that is not rendered by the platform. - // Needs to be rendered ourself. - if (!(xobj instanceof JpegStream)) { - var image = xobj; - var dict = image.dict; - var w = dict.get('Width', 'W'); - var h = dict.get('Height', 'H'); - + } else if ('Image' == type.name) { + var image = xobj; + var dict = image.dict; + var w = dict.get('Width', 'W'); + var h = dict.get('Height', 'H'); + + if (image instanceof JpegStream) { + var objId = ++objIdCounter; + images.push({ + id: objId, + IR: image.getIR() + }); + + // TODO: Place dependency note in IR queue. + fn = 'paintReadyJpegXObject'; + args = [ objId, w, h ]; + } else { + // Needs to be rendered ourself. var inline = false; - var imageObj = new PDFImage(xref, resources, image, inline); if (imageObj.imageMask) { @@ -4308,12 +4330,10 @@ var PartialEvaluator = (function() { fn = "paintReadyImageXObject"; args = [ imgData ]; - - // console.log("xobj subtype image", w, h, imageObj.imageMask); } + } else { + error('Unhandled XObject subtype ' + type.name); } - // console.log("xobj subtype", xobj.dict.get('Subtype').name); - } } else if (cmd == 'Tf') { // eagerly collect all fonts var fontName = args[0].name; @@ -5533,6 +5553,24 @@ var CanvasGraphics = (function() { this.restore(); }, + paintReadyJpegXObject: function(objId, w, h) { + var image = Objects[objId]; + if (!image) { + error("Dependent image isn't ready yet"); + } + + this.save(); + + var ctx = this.ctx; + ctx.scale(1 / w, -1 / h); + + var domImage = image.getImage(); + ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, + 0, -h, w, h); + + this.restore(); + }, + paintImageXObject: function(ref, image, inline) { this.save(); diff --git a/worker.js b/worker.js index dae87106cd6ec..55c18c8fc1783 100644 --- a/worker.js +++ b/worker.js @@ -50,6 +50,9 @@ var WorkerPage = (function() { return constructor; })(); +// This holds a list of objects the IR queue depends on. +var Objects = {}; + var WorkerPDFDoc = (function() { function constructor(data) { this.data = data; @@ -60,7 +63,7 @@ var WorkerPDFDoc = (function() { this.pageCache = []; - var useWorker = true; + var useWorker = false; if (useWorker) { var worker = new Worker("../worker/boot.js"); @@ -90,9 +93,27 @@ var WorkerPDFDoc = (function() { font.file.end - font.file.start, fontFileDict); font.file = new FlateStream(fontFile); } - - console.log("startRenderingFromPreCompilation:", "numberOfFonts", fonts.length); - page.startRenderingFromPreCompilation(data.preCompilation, data.fonts, data.images); + + var imageLoadingDone = function() { + console.log("startRenderingFromPreCompilation:", "numberOfFonts", fonts.length); + page.startRenderingFromPreCompilation(data.preCompilation, data.fonts, data.images); + } + + var images = data.images; + if (images.length != 0) { + // Generate JpegStreams based on IR information and start rendering + // once the compilation is done. + var loader = new ImagesLoader(); + loader.onLoad = imageLoadingDone; + + for (var i = 0; i < images.length; i++) { + var image = images[i]; + var stream = new JpegStreamIR(image.id, image.IR); + loader.bind(stream); + } + } else { + imageLoadingDone(); + } }, this); if (!useWorker) { diff --git a/worker/handler.js b/worker/handler.js index ca8413ede357a..4fcb8c1f7b307 100644 --- a/worker/handler.js +++ b/worker/handler.js @@ -22,8 +22,7 @@ var WorkerHandler = { // but stops at one point and sends the result back to the main thread. var gfx = new CanvasGraphics(null); var fonts = []; - // TODO: Figure out how image loading is handled inside the worker. - var images = new ImagesLoader(); + var images = []; // Pre compile the pdf page and fetch the fonts/images. var preCompilation = page.preCompile(gfx, fonts, images); @@ -76,9 +75,9 @@ var WorkerHandler = { handler.send("page", { pageNum: pageNum, fonts: fontsMin, - images: [], + images: images, preCompilation: preCompilation, }); }, this); } -} \ No newline at end of file +} From 62afa95fe10cde938412911374c33afbf3d3a816 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 15:15:36 -0700 Subject: [PATCH 025/123] Add support for image xObjs with imageMask --- pdf.js | 128 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 90 insertions(+), 38 deletions(-) diff --git a/pdf.js b/pdf.js index 805122f9e695e..73a7e2e88b675 100644 --- a/pdf.js +++ b/pdf.js @@ -4313,23 +4313,47 @@ var PartialEvaluator = (function() { args = [ objId, w, h ]; } else { // Needs to be rendered ourself. - var inline = false; - var imageObj = new PDFImage(xref, resources, image, inline); + + // Figure out if the image has an imageMask. + var imageMask = dict.get('ImageMask', 'IM') || false; + + // If there is no imageMask, create the PDFImage and a lot + // of image processing can be done here. + if (!imageMask) { + var inline = false; + var imageObj = new PDFImage(xref, resources, image, inline); - if (imageObj.imageMask) { - throw "Can't handle this in the web worker :/"; + if (imageObj.imageMask) { + throw "Can't handle this in the web worker :/"; + } + + var imgData = { + width: w, + height: h, + data: new Uint8Array(w * h * 4) + }; + var pixels = imgData.data; + imageObj.fillRgbaBuffer(pixels, imageObj.decode); + + fn = "paintReadyImageXObject"; + args = [ imgData ]; + } else /* imageMask == true */ { + // This depends on a tmpCanvas beeing filled with the + // current fillStyle, such that processing the pixel + // data can't be done here. Instead of creating a + // complete PDFImage, only read the information needed + // for later. + fn = "paintReadyImageMaskXObject"; + + var width = dict.get('Width', 'W'); + var height = dict.get('Height', 'H'); + var bitStrideLength = (width + 7) >> 3; + var imgArray = image.getBytes(bitStrideLength * height); + var decode = dict.get('Decode', 'D'); + var inverseDecode = !!imageObj.decode && imageObj.decode[0] > 0; + + args = [ imgArray, inverseDecode, width, height ]; } - - var imgData = { - width: w, - height: h, - data: new Uint8Array(w * h * 4) - }; - var pixels = imgData.data; - imageObj.fillRgbaBuffer(pixels, imageObj.decode); - - fn = "paintReadyImageXObject"; - args = [ imgData ]; } } else { error('Unhandled XObject subtype ' + type.name); @@ -5616,6 +5640,54 @@ var CanvasGraphics = (function() { this.restore(); }, + paintReadyImageMaskXObject: function(imgArray, inverseDecode, width, height) { + function applyStencilMask(buffer, inverseDecode) { + var imgArrayPos = 0; + var i, j, mask, buf; + // removing making non-masked pixels transparent + var bufferPos = 3; // alpha component offset + for (i = 0; i < height; i++) { + mask = 0; + for (j = 0; j < width; j++) { + if (!mask) { + buf = imgArray[imgArrayPos++]; + mask = 128; + } + if (!(buf & mask) == inverseDecode) { + buffer[bufferPos] = 0; + } + bufferPos += 4; + mask >>= 1; + } + } + } + + this.save(); + + var ctx = this.ctx; + var w = width, h = height; + + // scale the image to the unit square + ctx.scale(1 / w, -1 / h); + + var tmpCanvas = new this.ScratchCanvas(w, h); + var tmpCtx = tmpCanvas.getContext('2d'); + var fillColor = this.current.fillColor; + + tmpCtx.fillStyle = (fillColor && fillColor.type === 'Pattern') ? + fillColor.getPattern(tmpCtx) : fillColor; + tmpCtx.fillRect(0, 0, w, h); + + var imgData = tmpCtx.getImageData(0, 0, w, h); + var pixels = imgData.data; + + applyStencilMask(pixels, inverseDecode); + + tmpCtx.putImageData(imgData, 0, 0); + ctx.drawImage(tmpCanvas, 0, -h); + this.restore(); + }, + paintReadyImageXObject: function(imgData) { this.save(); @@ -5633,6 +5705,9 @@ var CanvasGraphics = (function() { // Copy over the imageData. var tmpImgDataPixels = tmpImgData.data; var len = tmpImgDataPixels.length; + + // TODO: There got to be a better way to copy an ImageData array + // then coping over all the bytes one by one :/ while (len--) tmpImgDataPixels[len] = imgData.data[len]; @@ -6786,29 +6861,6 @@ var PDFImage = (function() { } return buf; }, - applyStencilMask: function applyStencilMask(buffer, inverseDecode) { - var width = this.width, height = this.height; - var bitStrideLength = (width + 7) >> 3; - var imgArray = this.image.getBytes(bitStrideLength * height); - var imgArrayPos = 0; - var i, j, mask, buf; - // removing making non-masked pixels transparent - var bufferPos = 3; // alpha component offset - for (i = 0; i < height; i++) { - mask = 0; - for (j = 0; j < width; j++) { - if (!mask) { - buf = imgArray[imgArrayPos++]; - mask = 128; - } - if (!(buf & mask) == inverseDecode) { - buffer[bufferPos] = 0; - } - bufferPos += 4; - mask >>= 1; - } - } - }, fillRgbaBuffer: function fillRgbaBuffer(buffer, decodeMap) { var numComps = this.numComps; var width = this.width; From a4cfc4440993581be1053dddd5f4de71502bc690 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 15:50:45 -0700 Subject: [PATCH 026/123] Add rendering timer --- worker.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/worker.js b/worker.js index 55c18c8fc1783..131201f07c781 100644 --- a/worker.js +++ b/worker.js @@ -95,8 +95,10 @@ var WorkerPDFDoc = (function() { } var imageLoadingDone = function() { + var timeStart = new Date(); console.log("startRenderingFromPreCompilation:", "numberOfFonts", fonts.length); page.startRenderingFromPreCompilation(data.preCompilation, data.fonts, data.images); + console.log("RenderingTime", (new Date()) - timeStart); } var images = data.images; From e7fa637bf4444515c011871d1736e316abbd84bf Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 16:02:26 -0700 Subject: [PATCH 027/123] Remove no longer needed code and rename IR forms to the standard ones --- pdf.js | 212 ++++----------------------------------------------------- 1 file changed, 14 insertions(+), 198 deletions(-) diff --git a/pdf.js b/pdf.js index 73a7e2e88b675..3b3fa33231e61 100644 --- a/pdf.js +++ b/pdf.js @@ -4294,7 +4294,7 @@ var PartialEvaluator = (function() { var matrix = xobj.dict.get('Matrix'); var bbox = xobj.dict.get('BBox'); args = [ raw, matrix, bbox ]; - fn = "paintReadyFormXObject"; + fn = "paintFormXObject"; } else if ('Image' == type.name) { var image = xobj; var dict = image.dict; @@ -4309,7 +4309,7 @@ var PartialEvaluator = (function() { }); // TODO: Place dependency note in IR queue. - fn = 'paintReadyJpegXObject'; + fn = 'paintJpegXObject'; args = [ objId, w, h ]; } else { // Needs to be rendered ourself. @@ -4335,7 +4335,7 @@ var PartialEvaluator = (function() { var pixels = imgData.data; imageObj.fillRgbaBuffer(pixels, imageObj.decode); - fn = "paintReadyImageXObject"; + fn = "paintImageXObject"; args = [ imgData ]; } else /* imageMask == true */ { // This depends on a tmpCanvas beeing filled with the @@ -4343,7 +4343,7 @@ var PartialEvaluator = (function() { // data can't be done here. Instead of creating a // complete PDFImage, only read the information needed // for later. - fn = "paintReadyImageMaskXObject"; + fn = "paintImageMaskXObject"; var width = dict.get('Width', 'W'); var height = dict.get('Height', 'H'); @@ -4403,25 +4403,14 @@ var PartialEvaluator = (function() { error('No shading object found'); var shadingFill = Pattern.parseShading(shading, null, xref, res, /* ctx */ null); - var patternRaw = shadingFill.getPatternRaw(); + var patternIR = shadingFill.getPatternRaw(); - args = [ patternRaw ]; - fn = "shadingFillRaw"; + args = [ patternIR ]; + fn = "shadingFill"; break; } - - - var skips = [ "setFillColorN" ];//[ "paintReadyFormXObject" ]; - //var skips = ["setFillColorSpace", "setFillColor", "setStrokeColorSpace", "setStrokeColor"]; - - if (skips.indexOf(fn) != -1) { - // console.log("skipping", fn); - args = []; - continue; - } - fnArray.push(fn); argsArray.push(args); args = []; @@ -5308,20 +5297,6 @@ var CanvasGraphics = (function() { var color = cs.getRgb(arguments); this.setStrokeRGBColor.apply(this, color); }, - setStrokeColorN: function(/*...*/) { - var cs = this.current.strokeColorSpace; - - if (cs.name == 'Pattern') { - // wait until fill to actually get the pattern, since Canvas - // calcualtes the pattern according to the current coordinate space, - // not the space when the pattern is set. - var pattern = Pattern.parse(arguments, cs, this.xref, this.res, - this.ctx); - this.current.strokeColor = pattern; - } else { - this.setStrokeColor.apply(this, arguments); - } - }, getColorN_IR_Pattern: function(IR, cs) { if (IR[0] == "TilingPatternIR") { // First, build the `color` var like it's done in the @@ -5355,25 +5330,12 @@ var CanvasGraphics = (function() { } else { this.setStrokeColor.apply(this, arguments); } - }, setFillColor: function(/*...*/) { var cs = this.current.fillColorSpace; var color = cs.getRgb(arguments); this.setFillRGBColor.apply(this, color); }, - setFillColorN: function(/*...*/) { - var cs = this.current.fillColorSpace; - - if (cs.name == 'Pattern') { - // wait until fill to actually get the pattern - var pattern = Pattern.parse(arguments, cs, this.xref, this.res, - this.ctx); - this.current.fillColor = pattern; - } else { - this.setFillColor.apply(this, arguments); - } - }, setFillColorN_IR: function(/*...*/) { var cs = this.current.fillColorSpace; @@ -5410,60 +5372,11 @@ var CanvasGraphics = (function() { this.current.fillColor = color; }, - // Shading - shadingFill: function(shadingName) { - var xref = this.xref; - var res = this.res; - var ctx = this.ctx; - - var shadingRes = xref.fetchIfRef(res.get('Shading')); - if (!shadingRes) - error('No shading resource found'); - - var shading = xref.fetchIfRef(shadingRes.get(shadingName.name)); - if (!shading) - error('No shading object found'); - - var shadingFill = Pattern.parseShading(shading, null, xref, res, ctx); - - this.save(); - ctx.fillStyle = shadingFill.getPattern(); - - var inv = ctx.mozCurrentTransformInverse; - if (inv) { - var canvas = ctx.canvas; - var width = canvas.width; - var height = canvas.height; - - var bl = Util.applyTransform([0, 0], inv); - var br = Util.applyTransform([0, width], inv); - var ul = Util.applyTransform([height, 0], inv); - var ur = Util.applyTransform([height, width], inv); - - var x0 = Math.min(bl[0], br[0], ul[0], ur[0]); - var y0 = Math.min(bl[1], br[1], ul[1], ur[1]); - var x1 = Math.max(bl[0], br[0], ul[0], ur[0]); - var y1 = Math.max(bl[1], br[1], ul[1], ur[1]); - - this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0); - } else { - // HACK to draw the gradient onto an infinite rectangle. - // PDF gradients are drawn across the entire image while - // Canvas only allows gradients to be drawn in a rectangle - // The following bug should allow us to remove this. - // https://bugzilla.mozilla.org/show_bug.cgi?id=664884 - - this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); - } - - this.restore(); - }, - - shadingFillRaw: function(patternRaw) { + shadingFill: function(patternIR) { var ctx = this.ctx; this.save(); - ctx.fillStyle = Pattern.shadingFromRaw(ctx, patternRaw); + ctx.fillStyle = Pattern.shadingFromRaw(ctx, patternIR); var inv = ctx.mozCurrentTransformInverse; if (inv) { @@ -5493,7 +5406,6 @@ var CanvasGraphics = (function() { } this.restore(); - }, // Images @@ -5506,39 +5418,8 @@ var CanvasGraphics = (function() { endInlineImage: function(image) { this.paintImageXObject(null, image, true); }, - - // XObjects - paintXObject: function(obj) { - var xobj = this.xobjs.get(obj.name); - if (!xobj) - return; - xobj = this.xref.fetchIfRef(xobj); - assertWellFormed(IsStream(xobj), 'XObject should be a stream'); - - var oc = xobj.dict.get('OC'); - if (oc) { - TODO('oc for xobject'); - } - - var opi = xobj.dict.get('OPI'); - if (opi) { - TODO('opi for xobject'); - } - - var type = xobj.dict.get('Subtype'); - assertWellFormed(IsName(type), 'XObject should have a Name subtype'); - if ('Image' == type.name) { - this.paintImageXObject(obj, xobj, false); - } else if ('Form' == type.name) { - this.paintFormXObject(obj, xobj); - } else if ('PS' == type.name) { - warn('(deprecated) PostScript XObjects are not supported'); - } else { - malformed('Unknown XObject subtype ' + type.name); - } - }, - - paintReadyFormXObject: function(raw, matrix, bbox) { + + paintFormXObject: function(raw, matrix, bbox) { this.save(); if (matrix && IsArray(matrix) && 6 == matrix.length) @@ -5557,27 +5438,7 @@ var CanvasGraphics = (function() { this.restore(); }, - paintFormXObject: function(ref, stream) { - this.save(); - - var matrix = stream.dict.get('Matrix'); - if (matrix && IsArray(matrix) && 6 == matrix.length) - this.transform.apply(this, matrix); - - var bbox = stream.dict.get('BBox'); - if (bbox && IsArray(bbox) && 4 == bbox.length) { - this.rectangle.apply(this, bbox); - this.clip(); - this.endPath(); - } - - var code = this.pe.evalFromRaw(ref.raw) - this.execute(code, this.xref, stream.dict.get('Resources')); - - this.restore(); - }, - - paintReadyJpegXObject: function(objId, w, h) { + paintJpegXObject: function(objId, w, h) { var image = Objects[objId]; if (!image) { error("Dependent image isn't ready yet"); @@ -5594,53 +5455,8 @@ var CanvasGraphics = (function() { this.restore(); }, - - paintImageXObject: function(ref, image, inline) { - this.save(); - - var ctx = this.ctx; - var dict = image.dict; - var w = dict.get('Width', 'W'); - var h = dict.get('Height', 'H'); - // scale the image to the unit square - ctx.scale(1 / w, -1 / h); - - // If the platform can render the image format directly, the - // stream has a getImage property which directly returns a - // suitable DOM Image object. - if (image.getImage) { - var domImage = image.getImage(); - ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, - 0, -h, w, h); - this.restore(); - return; - } - - var imageObj = new PDFImage(this.xref, this.res, image, inline); - - var tmpCanvas = new this.ScratchCanvas(w, h); - var tmpCtx = tmpCanvas.getContext('2d'); - if (imageObj.imageMask) { - var fillColor = this.current.fillColor; - tmpCtx.fillStyle = (fillColor && fillColor.type === 'Pattern') ? - fillColor.getPattern(tmpCtx) : fillColor; - tmpCtx.fillRect(0, 0, w, h); - } - var imgData = tmpCtx.getImageData(0, 0, w, h); - var pixels = imgData.data; - - if (imageObj.imageMask) { - var inverseDecode = !!imageObj.decode && imageObj.decode[0] > 0; - imageObj.applyStencilMask(pixels, inverseDecode); - } else - imageObj.fillRgbaBuffer(pixels, imageObj.decode); - - tmpCtx.putImageData(imgData, 0, 0); - ctx.drawImage(tmpCanvas, 0, -h); - this.restore(); - }, - paintReadyImageMaskXObject: function(imgArray, inverseDecode, width, height) { + paintImageMaskXObject: function(imgArray, inverseDecode, width, height) { function applyStencilMask(buffer, inverseDecode) { var imgArrayPos = 0; var i, j, mask, buf; @@ -5688,7 +5504,7 @@ var CanvasGraphics = (function() { this.restore(); }, - paintReadyImageXObject: function(imgData) { + paintImageXObject: function(imgData) { this.save(); var ctx = this.ctx; From f369bd3ed0577a79b399cbd07a3a4355a2743383 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 16:09:49 -0700 Subject: [PATCH 028/123] [Cleanup] Rename Raw to IR --- pdf.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/pdf.js b/pdf.js index 3b3fa33231e61..d8e37a7b2074c 100644 --- a/pdf.js +++ b/pdf.js @@ -4267,7 +4267,7 @@ var PartialEvaluator = (function() { var shading = xref.fetchIfRef(dict.get('Shading')); var matrix = dict.get('Matrix'); var pattern = Pattern.parseShading(shading, matrix, xref, res, null /*ctx*/); - args = pattern.getPatternRaw(); + args = pattern.getIR(); } else { error("Unkown PatternType " + typeNum); } @@ -4391,7 +4391,7 @@ var PartialEvaluator = (function() { // Parse the ColorSpace data to a raw format. case "setFillColorSpace": case "setStrokeColorSpace": - args = [ ColorSpace.parseRaw(args[0], xref, resources) ]; + args = [ ColorSpace.parseToIR(args[0], xref, resources) ]; break; case "shadingFill": var shadingRes = xref.fetchIfRef(res.get('Shading')); @@ -4403,7 +4403,7 @@ var PartialEvaluator = (function() { error('No shading object found'); var shadingFill = Pattern.parseShading(shading, null, xref, res, /* ctx */ null); - var patternIR = shadingFill.getPatternRaw(); + var patternIR = shadingFill.getIR(); args = [ patternIR ]; fn = "shadingFill"; @@ -5284,13 +5284,11 @@ var CanvasGraphics = (function() { // Color setStrokeColorSpace: function(raw) { this.current.strokeColorSpace = - ColorSpace.fromRaw(raw); - // ColorSpace.parse(space, this.xref, this.res); + ColorSpace.fromIR(raw); }, setFillColorSpace: function(raw) { this.current.fillColorSpace = - ColorSpace.fromRaw(raw); - // ColorSpace.parse(space, this.xref, this.res); + ColorSpace.fromIR(raw); }, setStrokeColor: function(/*...*/) { var cs = this.current.strokeColorSpace; @@ -5316,7 +5314,7 @@ var CanvasGraphics = (function() { // Build the pattern based on the IR data. var pattern = new TilingPatternIR(IR, color, this.ctx); } else if (IR[0] == "RadialAxialShading") { - var pattern = Pattern.shadingFromRaw(this.ctx, IR); + var pattern = Pattern.shadingFromIR(this.ctx, IR); } else { throw "Unkown IR type"; } @@ -5376,7 +5374,7 @@ var CanvasGraphics = (function() { var ctx = this.ctx; this.save(); - ctx.fillStyle = Pattern.shadingFromRaw(ctx, patternIR); + ctx.fillStyle = Pattern.shadingFromIR(ctx, patternIR); var inv = ctx.mozCurrentTransformInverse; if (inv) { @@ -5715,7 +5713,7 @@ var ColorSpace = (function() { return null; }; - constructor.fromRaw = function(raw) { + constructor.fromIR = function(raw) { var name; if (IsArray(raw)) { name = raw[0]; @@ -5737,7 +5735,7 @@ var ColorSpace = (function() { } } - constructor.parseRaw = function colorspace_parse(cs, xref, res) { + constructor.parseToIR = function colorspace_parse(cs, xref, res) { if (IsName(cs)) { var colorSpaces = res.get('ColorSpace'); if (IsDict(colorSpaces)) { @@ -6089,10 +6087,10 @@ var Pattern = (function() { } }; - constructor.shadingFromRaw = function(ctx, raw) { + constructor.shadingFromIR = function(ctx, raw) { var obj = window[raw[0]]; - return obj.fromRaw(ctx, raw); + return obj.fromIR(ctx, raw); } constructor.parse = function pattern_parse(args, cs, xref, res, ctx) { @@ -6222,7 +6220,7 @@ var RadialAxialShading = (function() { this.colorStops = colorStops; } - constructor.fromRaw = function(ctx, raw) { + constructor.fromIR = function(ctx, raw) { var type = raw[1]; var colorStops = raw[2]; var p0 = raw[3]; @@ -6301,7 +6299,7 @@ var RadialAxialShading = (function() { return grad; }, - getPatternRaw: function() { + getIR: function() { var coordsArr = this.coordsArr; var type = this.shadingType; if (type == 2) { From a83f49133e2199b1f1704e12dae2f49279d131b2 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 16:13:41 -0700 Subject: [PATCH 029/123] [Cleanup] Renomve no longer needed RadialAxialShading.prototype.getPattern as toIR is called all the time --- pdf.js | 50 +------------------------------------------------- 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/pdf.js b/pdf.js index d8e37a7b2074c..eb7b19c3fd502 100644 --- a/pdf.js +++ b/pdf.js @@ -6251,54 +6251,6 @@ var RadialAxialShading = (function() { } constructor.prototype = { - getPattern: function() { - var coordsArr = this.coordsArr; - var type = this.shadingType; - var p0, p1, r0, r1; - if (type == 2) { - p0 = [coordsArr[0], coordsArr[1]]; - p1 = [coordsArr[2], coordsArr[3]]; - } else if (type == 3) { - p0 = [coordsArr[0], coordsArr[1]]; - p1 = [coordsArr[3], coordsArr[4]]; - r0 = coordsArr[2]; - r1 = coordsArr[5]; - } else { - error('getPattern type unknown: ' + type); - } - - var matrix = this.matrix; - if (matrix) { - p0 = Util.applyTransform(p0, matrix); - p1 = Util.applyTransform(p1, matrix); - } - - // if the browser supports getting the tranform matrix, convert - // gradient coordinates from pattern space to current user space - var ctx = this.ctx; - if (ctx.mozCurrentTransform) { - var userMatrix = ctx.mozCurrentTransformInverse; - - p0 = Util.applyTransform(p0, curMatrix); - p0 = Util.applyTransform(p0, userMatrix); - - p1 = Util.applyTransform(p1, curMatrix); - p1 = Util.applyTransform(p1, userMatrix); - } - - var colorStops = this.colorStops, grad; - if (type == 2) - grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); - else if (type == 3) - grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); - - for (var i = 0, ii = colorStops.length; i < ii; ++i) { - var c = colorStops[i]; - grad.addColorStop(c[0], c[1]); - } - return grad; - }, - getIR: function() { var coordsArr = this.coordsArr; var type = this.shadingType; @@ -6311,7 +6263,7 @@ var RadialAxialShading = (function() { var p1 = [coordsArr[3], coordsArr[4]]; var r0 = coordsArr[2], r1 = coordsArr[5]; } else { - error(); + error('getPattern type unknown: ' + type); } var matrix = this.matrix; From 1c30fda577cea356170c093c5f616ef7a24fc61f Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 16:25:51 -0700 Subject: [PATCH 030/123] Implement ColorSpace IR forms for Pattern and Indexed --- pdf.js | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/pdf.js b/pdf.js index eb7b19c3fd502..75795a0299e3b 100644 --- a/pdf.js +++ b/pdf.js @@ -5729,7 +5729,17 @@ var ColorSpace = (function() { case "DeviceCmykCS": return new DeviceCmykCS(); case "PatternCS": - return new PatternCS(raw[1]); + var baseCS = raw[1]; + if (baseCS == null) { + return new PatternCS(null); + } else { + return new PatternCS(ColorSpace.fromIR(baseCS)); + } + case "Indexed": + var baseCS = raw[1]; + var hiVal = raw[2]; + var lookup = raw[3]; + return new IndexedCS(ColorSpace.fromIR(baseCS), hiVal, lookup) default: error("Unkown name " + name); } @@ -5796,23 +5806,18 @@ var ColorSpace = (function() { return "DeviceCmykCS"; break; case 'Pattern': - console.log("ColorSpace Pattern"); - // TODO: IMPLEMENT ME - - // var baseCS = cs[1]; - // if (baseCS) - // baseCS = ColorSpace.parse(baseCS, xref, res); - // return new PatternCS(baseCS); + var baseCS = cs[1]; + if (baseCS) + baseCS = ColorSpace.parseToIR(baseCS, xref, res); + return ["PatternCS", baseCS]; case 'Indexed': - // TODO: IMPLEMENT ME + var baseCS = ColorSpace.parseToIR(cs[1], xref, res); + var hiVal = cs[2] + 1; + var lookup = xref.fetchIfRef(cs[3]); + return ["IndexedCS", baseCS, hiVal, lookup]; + case 'Separation': + error("Need to implement IR form for SeparationCS"); - // var base = ColorSpace.parse(cs[1], xref, res); - // var hiVal = cs[2] + 1; - // var lookup = xref.fetchIfRef(cs[3]); - // return new IndexedCS(base, hiVal, lookup); - case 'Separation': - // TODO: IMPLEMENT ME - // var name = cs[1]; // var alt = ColorSpace.parse(cs[2], xref, res); // var tintFn = new PDFFunction(xref, xref.fetchIfRef(cs[3])); From a7d1c84c92dacde86187fd9bdd573cfef91b79d3 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 16:29:03 -0700 Subject: [PATCH 031/123] Make ColorSpace.parse use ColorSpace.toIR and .fromIR to make use of only one codepath --- pdf.js | 85 +++------------------------------------------------------- 1 file changed, 3 insertions(+), 82 deletions(-) diff --git a/pdf.js b/pdf.js index 75795a0299e3b..4f0420f7195bf 100644 --- a/pdf.js +++ b/pdf.js @@ -5629,88 +5629,8 @@ var ColorSpace = (function() { }; constructor.parse = function colorspace_parse(cs, xref, res) { - if (IsName(cs)) { - var colorSpaces = res.get('ColorSpace'); - if (IsDict(colorSpaces)) { - var refcs = colorSpaces.get(cs.name); - if (refcs) - cs = refcs; - } - } - - cs = xref.fetchIfRef(cs); - - if (IsName(cs)) { - var mode = cs.name; - this.mode = mode; - - switch (mode) { - case 'DeviceGray': - case 'G': - return new DeviceGrayCS(); - case 'DeviceRGB': - case 'RGB': - return new DeviceRgbCS(); - case 'DeviceCMYK': - case 'CMYK': - return new DeviceCmykCS(); - case 'Pattern': - return new PatternCS(null); - default: - error('unrecognized colorspace ' + mode); - } - } else if (IsArray(cs)) { - var mode = cs[0].name; - this.mode = mode; - - switch (mode) { - case 'DeviceGray': - case 'G': - return new DeviceGrayCS(); - case 'DeviceRGB': - case 'RGB': - return new DeviceRgbCS(); - case 'DeviceCMYK': - case 'CMYK': - return new DeviceCmykCS(); - case 'CalGray': - return new DeviceGrayCS(); - case 'CalRGB': - return new DeviceRgbCS(); - case 'ICCBased': - var stream = xref.fetchIfRef(cs[1]); - var dict = stream.dict; - var numComps = dict.get('N'); - if (numComps == 1) - return new DeviceGrayCS(); - if (numComps == 3) - return new DeviceRgbCS(); - if (numComps == 4) - return new DeviceCmykCS(); - break; - case 'Pattern': - var baseCS = cs[1]; - if (baseCS) - baseCS = ColorSpace.parse(baseCS, xref, res); - return new PatternCS(baseCS); - case 'Indexed': - var base = ColorSpace.parse(cs[1], xref, res); - var hiVal = cs[2] + 1; - var lookup = xref.fetchIfRef(cs[3]); - return new IndexedCS(base, hiVal, lookup); - case 'Separation': - var alt = ColorSpace.parse(cs[2], xref, res); - var tintFn = new PDFFunction(xref, xref.fetchIfRef(cs[3])); - return new SeparationCS(alt, tintFn); - case 'Lab': - case 'DeviceN': - default: - error('unimplemented color space object "' + mode + '"'); - } - } else { - error('unrecognized color space object: "' + cs + '"'); - } - return null; + var IR = constructor.parseToIR(cs, xref, res); + return constructor.fromIR(IR); }; constructor.fromIR = function(raw) { @@ -5743,6 +5663,7 @@ var ColorSpace = (function() { default: error("Unkown name " + name); } + return null; } constructor.parseToIR = function colorspace_parse(cs, xref, res) { From 790816bbdd44e288012d2131e87c904550ae30f6 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 17:02:01 -0700 Subject: [PATCH 032/123] Use all over the place and cleanup/renomve not longer needed code --- pdf.js | 129 ++++++++-------------------------------------- worker.js | 8 +-- worker/handler.js | 12 ++--- 3 files changed, 32 insertions(+), 117 deletions(-) diff --git a/pdf.js b/pdf.js index 4f0420f7195bf..2266faa009a06 100644 --- a/pdf.js +++ b/pdf.js @@ -3374,50 +3374,12 @@ var Page = (function() { } return shadow(this, 'rotate', rotate); }, - - startRendering: function(canvasCtx, continuation) { - var gfx = new CanvasGraphics(canvasCtx); - - // If there is already some code to render, then use it directly. - if (this.code) { - this.display(gfx); - return; - } - - var self = this; - var stats = self.stats; - stats.compile = stats.fonts = stats.render = 0; - - var fonts = []; - var images = new ImagesLoader(); - - var preCompilation = this.preCompile(gfx, fonts, images); - stats.compile = Date.now(); - - // Make a copy of the necessary datat to build a font later. The `font` - // object will be sent to the main thread later on. - var fontsBackup = fonts; - fonts = []; - for (var i = 0; i < fontsBackup.length; i++) { - var orgFont = fontsBackup[i]; - var font = { - name: orgFont.name, - file: orgFont.file, - properties: orgFont.properties - } - fonts.push(font); - } - - this.startRenderingFromPreCompilation(gfx, preCompilation, fonts, images, continuation); - }, - - startRenderingFromPreCompilation: function(gfx, preCompilation, fonts, images, continuation) { + startRenderingFromIRQueue: function(gfx, IRQueue, fonts, images, continuation) { var self = this; + this.IRQueue = IRQueue; - var displayContinuation = function() { - self.code = gfx.postCompile(preCompilation); - + var displayContinuation = function() { // Always defer call to display() to work around bug in // Firefox error reporting from XHR callbacks. setTimeout(function() { @@ -3440,10 +3402,10 @@ var Page = (function() { }) }, - preCompile: function(gfx, fonts, images) { - if (this.code) { + getIRQueue: function(fonts, images) { + if (this.IRQueue) { // content was compiled - return; + return this.IRQueue; } var xref = this.xref; @@ -3456,8 +3418,9 @@ var Page = (function() { content[i] = xref.fetchIfRef(content[i]); content = new StreamsSequenceStream(content); } - return gfx.preCompile(content, xref, resources, fonts, images, - this.pageNumber + "_"); + + var pe = this.pe = new PartialEvaluator(); + return this.IRQueue = pe.getIRQueue(content, xref, resources, fonts, images, this.pageNumber + "_"); }, ensureFonts: function(fonts, callback) { @@ -3481,8 +3444,6 @@ var Page = (function() { }, display: function(gfx) { - assert(this.code instanceof Function, - 'page content must be compiled first'); var xref = this.xref; var resources = xref.fetchIfRef(this.resources); var mediaBox = xref.fetchIfRef(this.mediaBox); @@ -3491,7 +3452,7 @@ var Page = (function() { width: this.width, height: this.height, rotate: this.rotate }); - gfx.execute(this.code, xref, resources); + gfx.executeIRQueue(this.IRQueue); gfx.endDrawing(); }, rotatePoint: function(x, y) { @@ -4224,7 +4185,7 @@ var PartialEvaluator = (function() { }; constructor.prototype = { - evalRaw: function(stream, xref, resources, fonts, images, uniquePrefix) { + getIRQueue: function(stream, xref, resources, fonts, images, uniquePrefix) { uniquePrefix = uniquePrefix || ""; resources = xref.fetchIfRef(resources) || new Dict(); @@ -4257,8 +4218,8 @@ var PartialEvaluator = (function() { // Type1 is TilingPattern if (typeNum == 1) { // Create an IR of the pattern code. - var codeIR = this.evalRaw(pattern, xref, - dict.get('Resources'), fonts); + var codeIR = this.getIRQueue(pattern, xref, + dict.get('Resources'), fonts, images, uniquePrefix); args = TilingPattern.getIR(codeIR, dict); } @@ -4289,7 +4250,7 @@ var PartialEvaluator = (function() { if ('Form' == type.name) { // console.log("got xobj that is a Form"); - var raw = this.evalRaw(xobj, xref, xobj.dict.get('Resources'), + var raw = this.getIRQueue(xobj, xref, xobj.dict.get('Resources'), fonts, images, uniquePrefix); var matrix = xobj.dict.get('Matrix'); var bbox = xobj.dict.get('BBox'); @@ -4425,26 +4386,6 @@ var PartialEvaluator = (function() { argsArray: argsArray }; }, - - eval: function(stream, xref, resources, fonts, images, uniquePrefix) { - var ret = this.evalRaw(stream, xref, resources, fonts, images, uniquePrefix); - - return function(gfx) { - var argsArray = ret.argsArray; - var fnArray = ret.fnArray; - for (var i = 0, length = argsArray.length; i < length; i++) - gfx[fnArray[i]].apply(gfx, argsArray[i]); - }; - }, - - evalFromRaw: function(raw) { - return function(gfx) { - var argsArray = raw.argsArray; - var fnArray = raw.fnArray; - for (var i = 0, length = argsArray.length; i < length; i++) - gfx[fnArray[i]].apply(gfx, argsArray[i]); - }; - }, extractEncoding: function(dict, xref, properties) { var type = properties.type, encoding; @@ -4907,37 +4848,12 @@ var CanvasGraphics = (function() { this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height); }, - preCompile: function(stream, xref, resources, fonts, images) { - var pe = this.pe = new PartialEvaluator(); - return pe.evalRaw(stream, xref, resources, fonts, images); - }, - - postCompile: function(raw) { - if (!this.pe) { - this.pe = new PartialEvaluator(); - } - return this.pe.evalFromRaw(raw); - }, - - execute: function(code, xref, resources) { - resources = xref.fetchIfRef(resources) || new Dict(); - var savedXref = this.xref, savedRes = this.res, savedXobjs = this.xobjs; - this.xref = xref; - this.res = resources || new Dict(); - this.xobjs = xref.fetchIfRef(this.res.get('XObject')) || new Dict(); - - code(this); - - this.xobjs = savedXobjs; - this.res = savedRes; - this.xref = savedXref; - }, - - executeIR: function(codeIR) { + executeIRQueue: function(codeIR) { var argsArray = codeIR.argsArray; var fnArray = codeIR.fnArray; - for (var i = 0, length = argsArray.length; i < length; i++) - this[fnArray[i]].apply(this, argsArray[i]); + for (var i = 0, length = argsArray.length; i < length; i++) { + this[fnArray[i]].apply(this, argsArray[i]); + } }, endDrawing: function() { @@ -5417,7 +5333,7 @@ var CanvasGraphics = (function() { this.paintImageXObject(null, image, true); }, - paintFormXObject: function(raw, matrix, bbox) { + paintFormXObject: function(IRQueue, matrix, bbox) { this.save(); if (matrix && IsArray(matrix) && 6 == matrix.length) @@ -5429,9 +5345,8 @@ var CanvasGraphics = (function() { this.endPath(); } - var code = this.pe.evalFromRaw(raw) // this.execute(code, this.xref, stream.dict.get('Resources')); - this.execute(code, this.xref, null); + this.executeIRQueue(IRQueue); this.restore(); }, @@ -6210,7 +6125,7 @@ var TilingPatternIR = (function() { function TilingPatternIR(IR, color, ctx) { // "Unfolding" the IR. - var codeIR = IR[1]; + var IRQueue = IR[1]; this.matrix = IR[2]; var bbox = IR[3]; var xstep = IR[4]; @@ -6277,7 +6192,7 @@ var TilingPatternIR = (function() { graphics.endPath(); } - graphics.executeIR(codeIR); + graphics.executeIRQueue(IRQueue); this.canvas = tmpCanvas; } diff --git a/worker.js b/worker.js index 131201f07c781..61e4c20b9e575 100644 --- a/worker.js +++ b/worker.js @@ -33,13 +33,13 @@ var WorkerPage = (function() { this.workerPDF.startRendering(this) }, - startRenderingFromPreCompilation: function(preCompilation, fonts, images) { + startRenderingFromIRQueue: function(IRQueue, fonts, images) { var gfx = new CanvasGraphics(this.ctx); // TODO: Add proper handling for images loaded by the worker. var images = new ImagesLoader(); - this.page.startRenderingFromPreCompilation(gfx, preCompilation, fonts, images, this.callback); + this.page.startRenderingFromIRQueue(gfx, IRQueue, fonts, images, this.callback); }, getLinks: function() { @@ -63,7 +63,7 @@ var WorkerPDFDoc = (function() { this.pageCache = []; - var useWorker = false; + var useWorker = true; if (useWorker) { var worker = new Worker("../worker/boot.js"); @@ -97,7 +97,7 @@ var WorkerPDFDoc = (function() { var imageLoadingDone = function() { var timeStart = new Date(); console.log("startRenderingFromPreCompilation:", "numberOfFonts", fonts.length); - page.startRenderingFromPreCompilation(data.preCompilation, data.fonts, data.images); + page.startRenderingFromIRQueue(data.IRQueue, data.fonts, data.images); console.log("RenderingTime", (new Date()) - timeStart); } diff --git a/worker/handler.js b/worker/handler.js index 4fcb8c1f7b307..01bb76a260cb2 100644 --- a/worker/handler.js +++ b/worker/handler.js @@ -25,7 +25,7 @@ var WorkerHandler = { var images = []; // Pre compile the pdf page and fetch the fonts/images. - var preCompilation = page.preCompile(gfx, fonts, images); + var IRQueue = page.getIRQueue(fonts, images); // Extract the minimum of font data that is required to build all required // font stuff on the main thread. @@ -47,7 +47,7 @@ var WorkerHandler = { if (true /* show used commands */) { var cmdMap = {}; - var fnArray = preCompilation.fnArray; + var fnArray = IRQueue .fnArray; for (var i = 0; i < fnArray.length; i++) { var entry = fnArray[i]; if (entry == "paintReadyFormXObject") { @@ -73,10 +73,10 @@ var WorkerHandler = { } handler.send("page", { - pageNum: pageNum, - fonts: fontsMin, - images: images, - preCompilation: preCompilation, + pageNum: pageNum, + fonts: fontsMin, + images: images, + IRQueue: IRQueue, }); }, this); } From 2269bcccfa5e60d0b109da5dbc051837987c9521 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 17:06:16 -0700 Subject: [PATCH 033/123] [Cleanup] Remove no longer needed Patter.parse and TilingPattern. functions --- pdf.js | 143 ++------------------------------------------------------- 1 file changed, 3 insertions(+), 140 deletions(-) diff --git a/pdf.js b/pdf.js index 2266faa009a06..12ce35b49f82c 100644 --- a/pdf.js +++ b/pdf.js @@ -5930,50 +5930,9 @@ var Pattern = (function() { constructor.shadingFromIR = function(ctx, raw) { var obj = window[raw[0]]; - return obj.fromIR(ctx, raw); } - constructor.parse = function pattern_parse(args, cs, xref, res, ctx) { - var length = args.length; - - var patternName = args[length - 1]; - if (!IsName(patternName)) - error('Bad args to getPattern: ' + patternName); - - var patternRes = xref.fetchIfRef(res.get('Pattern')); - if (!patternRes) - error('Unable to find pattern resource'); - - var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); - var dict = IsStream(pattern) ? pattern.dict : pattern; - var typeNum = dict.get('PatternType'); - - switch (typeNum) { - case 1: - var base = cs.base; - var color; - if (base) { - var baseComps = base.numComps; - - color = []; - for (var i = 0; i < baseComps; ++i) - color.push(args[i]); - - color = base.getRgb(color); - } - var code = patternName.code; - return new TilingPattern(pattern, code, dict, color, xref, ctx); - case 2: - var shading = xref.fetchIfRef(dict.get('Shading')); - var matrix = dict.get('Matrix'); - return Pattern.parseShading(shading, matrix, xref, res, ctx); - default: - error('Unknown type of pattern: ' + typeNum); - } - return null; - }; - constructor.parseShading = function pattern_shading(shading, matrix, xref, res, ctx) { @@ -6219,10 +6178,8 @@ var TilingPatternIR = (function() { return TilingPatternIR; })(); -var TilingPattern = (function() { - var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; - - constructor.getIR = function(codeIR, dict) { +var TilingPattern = { + getIR: function(codeIR, dict) { var matrix = dict.get('Matrix'); var bbox = dict.get('BBox'); var xstep = dict.get('XStep'); @@ -6231,101 +6188,7 @@ var TilingPattern = (function() { return ["TilingPatternIR", codeIR, matrix, bbox, xstep, ystep, paintType]; } - - function constructor(pattern, code, dict, color, xref, ctx) { - TODO('TilingType'); - - this.matrix = dict.get('Matrix'); - this.curMatrix = ctx.mozCurrentTransform; - this.invMatrix = ctx.mozCurrentTransformInverse; - this.ctx = ctx; - this.type = 'Pattern'; - - var bbox = dict.get('BBox'); - var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; - - var xstep = dict.get('XStep'); - var ystep = dict.get('YStep'); - - var topLeft = [x0, y0]; - // we want the canvas to be as large as the step size - var botRight = [x0 + xstep, y0 + ystep]; - - var width = botRight[0] - topLeft[0]; - var height = botRight[1] - topLeft[1]; - - // TODO: hack to avoid OOM, we would idealy compute the tiling - // pattern to be only as large as the acual size in device space - // This could be computed with .mozCurrentTransform, but still - // needs to be implemented - while (Math.abs(width) > 512 || Math.abs(height) > 512) { - width = 512; - height = 512; - } - - var tmpCanvas = new ScratchCanvas(width, height); - - // set the new canvas element context as the graphics context - var tmpCtx = tmpCanvas.getContext('2d'); - var graphics = new CanvasGraphics(tmpCtx); - - var paintType = dict.get('PaintType'); - switch (paintType) { - case PAINT_TYPE_COLORED: - tmpCtx.fillStyle = ctx.fillStyle; - tmpCtx.strokeStyle = ctx.strokeStyle; - break; - case PAINT_TYPE_UNCOLORED: - color = Util.makeCssRgb.apply(this, color); - tmpCtx.fillStyle = color; - tmpCtx.strokeStyle = color; - break; - default: - error('Unsupported paint type: ' + paintType); - } - - var scale = [width / xstep, height / ystep]; - this.scale = scale; - - // transform coordinates to pattern space - var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]]; - var tmpScale = [scale[0], 0, 0, scale[1], 0, 0]; - graphics.transform.apply(graphics, tmpScale); - graphics.transform.apply(graphics, tmpTranslate); - - if (bbox && IsArray(bbox) && 4 == bbox.length) { - graphics.rectangle.apply(graphics, bbox); - graphics.clip(); - graphics.endPath(); - } - - var res = xref.fetchIfRef(dict.get('Resources')); - graphics.execute(code, xref, res); - - this.canvas = tmpCanvas; - } - - constructor.prototype = { - getPattern: function tiling_getPattern() { - var matrix = this.matrix; - var curMatrix = this.curMatrix; - var ctx = this.ctx; - - if (curMatrix) - ctx.setTransform.apply(ctx, curMatrix); - - if (matrix) - ctx.transform.apply(ctx, matrix); - - var scale = this.scale; - ctx.scale(1 / scale[0], 1 / scale[1]); - - return ctx.createPattern(this.canvas, 'repeat'); - } - }; - return constructor; -})(); - +}; var PDFImage = (function() { function constructor(xref, res, image, inline) { From 443bb84cd6cb99e944c4ecd4e79d5151776536e3 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 17:15:35 -0700 Subject: [PATCH 034/123] Add very simple Promise object --- worker.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/worker.js b/worker.js index 61e4c20b9e575..9db894d323987 100644 --- a/worker.js +++ b/worker.js @@ -53,6 +53,38 @@ var WorkerPage = (function() { // This holds a list of objects the IR queue depends on. var Objects = {}; +var Promise = (function() { + function Promise(name) { + this.name = name; + this.isResolved = false; + }; + + Promise.prototype = { + resolve: function(data) { + if (this.isResolved) { + throw "A Promise can be resolved only once"; + } + + this.isResolved = true; + this.data = data; + var callbacks = this.callbacks; + + for (var i = 0; i < callbacks.length; i++) { + callbacks[i].call(null, data); + } + }, + + then: function(callback) { + // If the promise is already resolved, call the callback directly. + if (this.isResolved) { + callback.call(null, this.data); + } else { + this.callbacks.push(callback); + } + } + } +})(); + var WorkerPDFDoc = (function() { function constructor(data) { this.data = data; From e7b636dba4755b789c5457ab2e03e10b1afd4759 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 17:55:44 -0700 Subject: [PATCH 035/123] Flattern the IRQueue to make it easier to reexecute and have no nested state --- pdf.js | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/pdf.js b/pdf.js index 12ce35b49f82c..40b38d9f0a1c3 100644 --- a/pdf.js +++ b/pdf.js @@ -3420,7 +3420,8 @@ var Page = (function() { } var pe = this.pe = new PartialEvaluator(); - return this.IRQueue = pe.getIRQueue(content, xref, resources, fonts, images, this.pageNumber + "_"); + var IRQueue = {}; + return this.IRQueue = pe.getIRQueue(content, xref, resources, IRQueue, fonts, images, this.pageNumber + "_"); }, ensureFonts: function(fonts, callback) { @@ -4185,14 +4186,22 @@ var PartialEvaluator = (function() { }; constructor.prototype = { - getIRQueue: function(stream, xref, resources, fonts, images, uniquePrefix) { + getIRQueue: function(stream, xref, resources, queue, fonts, images, uniquePrefix) { uniquePrefix = uniquePrefix || ""; + if (!queue.argsArray) { + queue.argsArray = [] + } + if (!queue.fnArray) { + queue.fnArray = []; + } + + var fnArray = queue.fnArray, argsArray = queue.argsArray; resources = xref.fetchIfRef(resources) || new Dict(); var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict(); var patterns = xref.fetchIfRef(resources.get('Pattern')) || new Dict(); var parser = new Parser(new Lexer(stream), false); - var args = [], argsArray = [], fnArray = [], obj; + var args = [], obj; var res = resources; while (!IsEOF(obj = parser.getObj())) { @@ -4217,9 +4226,10 @@ var PartialEvaluator = (function() { // Type1 is TilingPattern if (typeNum == 1) { + // TODO: Add dependency here. // Create an IR of the pattern code. var codeIR = this.getIRQueue(pattern, xref, - dict.get('Resources'), fonts, images, uniquePrefix); + dict.get('Resources'), {}, fonts, images, uniquePrefix); args = TilingPattern.getIR(codeIR, dict); } @@ -4249,13 +4259,19 @@ var PartialEvaluator = (function() { ); if ('Form' == type.name) { - // console.log("got xobj that is a Form"); - var raw = this.getIRQueue(xobj, xref, xobj.dict.get('Resources'), - fonts, images, uniquePrefix); var matrix = xobj.dict.get('Matrix'); var bbox = xobj.dict.get('BBox'); - args = [ raw, matrix, bbox ]; - fn = "paintFormXObject"; + + fnArray.push("paintFormXObjectBegin"); + argsArray.push([ matrix, bbox ]); + + // This adds the IRQueue of the xObj to the current queue. + this.getIRQueue(xobj, xref, xobj.dict.get('Resources'), queue, + fonts, images, uniquePrefix); + + + fn = "paintFormXObjectEnd"; + args = []; } else if ('Image' == type.name) { var image = xobj; var dict = image.dict; @@ -5333,7 +5349,7 @@ var CanvasGraphics = (function() { this.paintImageXObject(null, image, true); }, - paintFormXObject: function(IRQueue, matrix, bbox) { + paintFormXObjectBegin: function(matrix, bbox) { this.save(); if (matrix && IsArray(matrix) && 6 == matrix.length) @@ -5344,10 +5360,9 @@ var CanvasGraphics = (function() { this.clip(); this.endPath(); } + }, - // this.execute(code, this.xref, stream.dict.get('Resources')); - this.executeIRQueue(IRQueue); - + paintFormXObjectEnd: function() { this.restore(); }, From 7e3bbccaaec6dd9114eecb9b43098c2ba9942a2c Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 7 Sep 2011 19:50:05 -0700 Subject: [PATCH 036/123] Add dependency management for loading images that causes the execution to halt if the dependency isn't satisfied during executing time --- pdf.js | 57 +++++++++++++++++++++++++++++++++++++++++++++---------- worker.js | 54 ++++++++++++++++++++++++++++------------------------ 2 files changed, 76 insertions(+), 35 deletions(-) diff --git a/pdf.js b/pdf.js index 40b38d9f0a1c3..ad77479462a19 100644 --- a/pdf.js +++ b/pdf.js @@ -861,13 +861,15 @@ var PredictorStream = (function() { var JpegStreamIR = (function() { function JpegStreamIR(objId, IR) { - var src = IR; + var src = 'data:image/jpeg;base64,' + window.btoa(IR); // create DOM image var img = new Image(); img.onload = (function() { this.loaded = true; - Objects[objId] = this; + + Objects.resolve(objId, this); + if (this.onLoad) this.onLoad(); }).bind(this); @@ -925,7 +927,7 @@ var JpegStream = (function() { if (isYcckImage(bytes)) bytes = fixYcckImage(bytes); - this.src = 'data:image/jpeg;base64,' + window.btoa(bytesToString(bytes)); + this.src = bytesToString(bytes); } constructor.prototype = { @@ -3453,8 +3455,16 @@ var Page = (function() { width: this.width, height: this.height, rotate: this.rotate }); - gfx.executeIRQueue(this.IRQueue); - gfx.endDrawing(); + + var startIdx = 0; + var length = this.IRQueue.fnArray.length; + var IRQueue = this.IRQueue; + + function next() { + console.log("next executeIRQueue", startIdx, length); + startIdx = gfx.executeIRQueue(IRQueue, startIdx, next); + } + next(); }, rotatePoint: function(x, y) { var rotate = this.rotate; @@ -4285,7 +4295,11 @@ var PartialEvaluator = (function() { IR: image.getIR() }); - // TODO: Place dependency note in IR queue. + // Add the dependency on the image object. + fnArray.push("dependency"); + argsArray.push([ objId ]); + + // The normal fn. fn = 'paintJpegXObject'; args = [ objId, w, h ]; } else { @@ -4864,12 +4878,35 @@ var CanvasGraphics = (function() { this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height); }, - executeIRQueue: function(codeIR) { + executeIRQueue: function(codeIR, startIdx, continueCallback) { var argsArray = codeIR.argsArray; var fnArray = codeIR.fnArray; - for (var i = 0, length = argsArray.length; i < length; i++) { - this[fnArray[i]].apply(this, argsArray[i]); + var i = startIdx || 0; + var length = argsArray.length; + for (i; i < length; i++) { + if (fnArray[i] !== "dependency") { + this[fnArray[i]].apply(this, argsArray[i]); + } else { + var deps = argsArray[i]; + for (var n = 0; n < deps.length; n++) { + var depObjId = deps[n]; + var promise; + if (!Objects[depObjId]) { + promise = Objects[depObjId] = new Promise(); + } else { + promise = Objects[depObjId]; + } + // If the promise isn't resolved yet, add the continueCallback + // to the promise and bail out. + if (!promise.isResolved) { + console.log("Unresolved object: " + depObjId); + promise.then(continueCallback); + return i; + } + } + } } + return i; }, endDrawing: function() { @@ -5367,7 +5404,7 @@ var CanvasGraphics = (function() { }, paintJpegXObject: function(objId, w, h) { - var image = Objects[objId]; + var image = Objects[objId].data; if (!image) { error("Dependent image isn't ready yet"); } diff --git a/worker.js b/worker.js index 9db894d323987..5d0769d293987 100644 --- a/worker.js +++ b/worker.js @@ -51,12 +51,27 @@ var WorkerPage = (function() { })(); // This holds a list of objects the IR queue depends on. -var Objects = {}; +var Objects = { + resolve: function(objId, data) { + // In case there is a promise already on this object, just resolve it. + if (Objects[objId] instanceof Promise) { + Objects[objId].resolve(data); + } else { + Objects[objId] = new Promise(data); + } + } +}; var Promise = (function() { - function Promise(name) { - this.name = name; - this.isResolved = false; + function Promise(data) { + // If you build a promise and pass in some data it's already resolved. + if (data != null) { + this.isResolved = true; + this.data = data; + } else { + this.isResolved = false; + } + this.callbacks = []; }; Promise.prototype = { @@ -83,6 +98,7 @@ var Promise = (function() { } } } + return Promise; })(); var WorkerPDFDoc = (function() { @@ -95,7 +111,7 @@ var WorkerPDFDoc = (function() { this.pageCache = []; - var useWorker = true; + var useWorker = false; if (useWorker) { var worker = new Worker("../worker/boot.js"); @@ -125,29 +141,17 @@ var WorkerPDFDoc = (function() { font.file.end - font.file.start, fontFileDict); font.file = new FlateStream(fontFile); } - - var imageLoadingDone = function() { - var timeStart = new Date(); - console.log("startRenderingFromPreCompilation:", "numberOfFonts", fonts.length); - page.startRenderingFromIRQueue(data.IRQueue, data.fonts, data.images); - console.log("RenderingTime", (new Date()) - timeStart); - } var images = data.images; - if (images.length != 0) { - // Generate JpegStreams based on IR information and start rendering - // once the compilation is done. - var loader = new ImagesLoader(); - loader.onLoad = imageLoadingDone; - - for (var i = 0; i < images.length; i++) { - var image = images[i]; - var stream = new JpegStreamIR(image.id, image.IR); - loader.bind(stream); - } - } else { - imageLoadingDone(); + for (var i = 0; i < images.length; i++) { + var image = images[i]; + var stream = new JpegStreamIR(image.id, image.IR); } + + var timeStart = new Date(); + console.log("startRenderingFromPreCompilation:", "numberOfFonts", fonts.length); + page.startRenderingFromIRQueue(data.IRQueue, data.fonts, data.images); + console.log("RenderingTime", (new Date()) - timeStart); }, this); if (!useWorker) { From 89afa693950bb8a7b76f7f5363b4a8063eb2c3d9 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 8 Sep 2011 09:46:01 -0700 Subject: [PATCH 037/123] Fix rendering of fonts on Firefox + turn worker on again. Seems like Gecko requires the fontName to start with a character, not a number. --- pdf.js | 2 +- worker.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pdf.js b/pdf.js index ad77479462a19..dde6beb176abf 100644 --- a/pdf.js +++ b/pdf.js @@ -3423,7 +3423,7 @@ var Page = (function() { var pe = this.pe = new PartialEvaluator(); var IRQueue = {}; - return this.IRQueue = pe.getIRQueue(content, xref, resources, IRQueue, fonts, images, this.pageNumber + "_"); + return this.IRQueue = pe.getIRQueue(content, xref, resources, IRQueue, fonts, images, "p" + this.pageNumber + "_"); }, ensureFonts: function(fonts, callback) { diff --git a/worker.js b/worker.js index 5d0769d293987..b71f1a878b10f 100644 --- a/worker.js +++ b/worker.js @@ -111,7 +111,7 @@ var WorkerPDFDoc = (function() { this.pageCache = []; - var useWorker = false; + var useWorker = true; if (useWorker) { var worker = new Worker("../worker/boot.js"); From fea3388c4129d577a5f674c12f097cd759b7daf8 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 8 Sep 2011 10:44:50 -0700 Subject: [PATCH 038/123] Make testdriver use new worker infrastructure --- test/driver.js | 3 ++- test/test_slave.html | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/driver.js b/test/driver.js index f3e45a53de7f1..3af8d3bcb14f7 100644 --- a/test/driver.js +++ b/test/driver.js @@ -68,7 +68,8 @@ function nextTask() { r.responseArrayBuffer || r.response; try { - task.pdfDoc = new PDFDoc(new Stream(data)); + task.pdfDoc = new WorkerPDFDoc(data); + // task.pdfDoc = new PDFDoc(new Stream(data)); } catch (e) { failure = 'load PDF doc : ' + e.toString(); } diff --git a/test/test_slave.html b/test/test_slave.html index 91b8a6850f42a..06657ddd34427 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -8,6 +8,9 @@ + + + From 4e3f87b60c231061433f5cd07d38120380f97408 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 8 Sep 2011 12:44:55 -0700 Subject: [PATCH 039/123] Make ColorSpace Separation work as long as no IR form is needed for now --- pdf.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/pdf.js b/pdf.js index dde6beb176abf..4c5f4fa4ec892 100644 --- a/pdf.js +++ b/pdf.js @@ -5596,8 +5596,12 @@ var ColorSpace = (function() { }; constructor.parse = function colorspace_parse(cs, xref, res) { - var IR = constructor.parseToIR(cs, xref, res); - return constructor.fromIR(IR); + var IR = constructor.parseToIR(cs, xref, res, true); + if (!(IR instanceof SeparationCS)) { + return constructor.fromIR(IR); + } else { + return IR + } }; constructor.fromIR = function(raw) { @@ -5633,7 +5637,7 @@ var ColorSpace = (function() { return null; } - constructor.parseToIR = function colorspace_parse(cs, xref, res) { + constructor.parseToIR = function colorspace_parse(cs, xref, res, parseOnly) { if (IsName(cs)) { var colorSpaces = res.get('ColorSpace'); if (IsDict(colorSpaces)) { @@ -5703,13 +5707,15 @@ var ColorSpace = (function() { var hiVal = cs[2] + 1; var lookup = xref.fetchIfRef(cs[3]); return ["IndexedCS", baseCS, hiVal, lookup]; - case 'Separation': - error("Need to implement IR form for SeparationCS"); - - // var name = cs[1]; - // var alt = ColorSpace.parse(cs[2], xref, res); - // var tintFn = new PDFFunction(xref, xref.fetchIfRef(cs[3])); - // return new SeparationCS(alt, tintFn); + case 'Separation': + if (!parseOnly) { + error("Need to implement IR form for SeparationCS"); + } else { + var name = cs[1]; + var alt = ColorSpace.parse(cs[2], xref, res); + var tintFn = new PDFFunction(xref, xref.fetchIfRef(cs[3])); + return new SeparationCS(alt, tintFn); + } case 'Lab': case 'DeviceN': default: @@ -5756,7 +5762,6 @@ var SeparationCS = (function() { baseBuf[pos++] = 255 * tinted[j]; } return base.getRgbBuffer(baseBuf, 8); - } }; From 8572c29c55ce74c728400b1b5408e1d2bed97975 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 8 Sep 2011 13:51:13 -0700 Subject: [PATCH 040/123] Fix missing parameter for RadialAxialShading --- pdf.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pdf.js b/pdf.js index 4c5f4fa4ec892..d69caf80af8e5 100644 --- a/pdf.js +++ b/pdf.js @@ -6083,6 +6083,7 @@ var RadialAxialShading = (function() { var p0 = raw[3]; var p1 = raw[4]; var r0 = raw[5]; + var r1 = raw[6]; var curMatrix = ctx.mozCurrentTransform; if (curMatrix) { @@ -6115,10 +6116,12 @@ var RadialAxialShading = (function() { var p0 = [coordsArr[0], coordsArr[1]]; var p1 = [coordsArr[2], coordsArr[3]]; var r0 = null; + var r1 = null; } else if (type == 3) { var p0 = [coordsArr[0], coordsArr[1]]; var p1 = [coordsArr[3], coordsArr[4]]; - var r0 = coordsArr[2], r1 = coordsArr[5]; + var r0 = coordsArr[2]; + var r1 = coordsArr[5]; } else { error('getPattern type unknown: ' + type); } @@ -6129,7 +6132,7 @@ var RadialAxialShading = (function() { p1 = Util.applyTransform(p1, matrix); } - return [ "RadialAxialShading", type, this.colorStops, p0, p1, r0 ]; + return [ "RadialAxialShading", type, this.colorStops, p0, p1, r0, r1 ]; } }; From 9dcefe1efcef436f22beef72ef30176e8da619bd Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 8 Sep 2011 14:52:59 -0700 Subject: [PATCH 041/123] Add PDFFunction to IR support + complete ColorSpace SeparationCS IR support --- pdf.js | 212 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 141 insertions(+), 71 deletions(-) diff --git a/pdf.js b/pdf.js index d69caf80af8e5..9bf879fa2649d 100644 --- a/pdf.js +++ b/pdf.js @@ -5604,12 +5604,12 @@ var ColorSpace = (function() { } }; - constructor.fromIR = function(raw) { + constructor.fromIR = function(IR) { var name; - if (IsArray(raw)) { - name = raw[0]; + if (IsArray(IR)) { + name = IR[0]; } else { - name = raw; + name = IR; } switch (name) { @@ -5620,17 +5620,25 @@ var ColorSpace = (function() { case "DeviceCmykCS": return new DeviceCmykCS(); case "PatternCS": - var baseCS = raw[1]; + var baseCS = IR[1]; if (baseCS == null) { return new PatternCS(null); } else { return new PatternCS(ColorSpace.fromIR(baseCS)); } - case "Indexed": - var baseCS = raw[1]; - var hiVal = raw[2]; - var lookup = raw[3]; + case "IndexedCS": + var baseCS = IR[1]; + var hiVal = IR[2]; + var lookup = IR[3]; return new IndexedCS(ColorSpace.fromIR(baseCS), hiVal, lookup) + case "SeparationCS": + var alt = IR[1]; + var tintFnIR = IR[2]; + + return new SeparationCS( + ColorSpace.fromIR(alt), + PDFFunction.fromIR(tintFnIR) + ); default: error("Unkown name " + name); } @@ -5708,14 +5716,9 @@ var ColorSpace = (function() { var lookup = xref.fetchIfRef(cs[3]); return ["IndexedCS", baseCS, hiVal, lookup]; case 'Separation': - if (!parseOnly) { - error("Need to implement IR form for SeparationCS"); - } else { - var name = cs[1]; - var alt = ColorSpace.parse(cs[2], xref, res); - var tintFn = new PDFFunction(xref, xref.fetchIfRef(cs[3])); - return new SeparationCS(alt, tintFn); - } + var alt = ColorSpace.parseToIR(cs[2], xref, res); + var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); + return ["SeparationCS", alt, tintFnIR]; case 'Lab': case 'DeviceN': default: @@ -6059,7 +6062,7 @@ var RadialAxialShading = (function() { error('No support for array of functions'); else if (!IsPDFFunction(fnObj)) error('Invalid function'); - var fn = new PDFFunction(xref, fnObj); + var fn = PDFFunction.parse(xref, fnObj); // 10 samples seems good enough for now, but probably won't work // if there are sharp color changes. Ideally, we would implement @@ -6069,7 +6072,7 @@ var RadialAxialShading = (function() { var colorStops = []; for (var i = t0; i <= t1; i += step) { - var color = fn.func([i]); + var color = fn([i]); var rgbColor = Util.makeCssRgb.apply(this, cs.getRgb(color)); colorStops.push([(i - t0) / diff, rgbColor]); } @@ -6439,26 +6442,76 @@ var PDFImage = (function() { })(); var PDFFunction = (function() { - function constructor(xref, fn) { - var dict = fn.dict; - if (!dict) - dict = fn; - - var types = [this.constructSampled, - null, - this.constructInterpolated, - this.constructStiched, - this.constructPostScript]; - - var typeNum = dict.get('FunctionType'); - var typeFn = types[typeNum]; - if (!typeFn) - error('Unknown type of function'); - - typeFn.call(this, fn, dict, xref); - } + var CONSTRUCT_SAMPLED = 0; + var CONSTRUCT_INTERPOLATED = 2; + var CONSTRUCT_STICHED = 3; + var CONSTRUCT_POSTSCRIPT = 4; + + return { + getSampleArray: function(size, outputSize, bps, str) { + var length = 1; + for (var i = 0; i < size.length; i++) + length *= size[i]; + length *= outputSize; + + var array = []; + var codeSize = 0; + var codeBuf = 0; + + var strBytes = str.getBytes((length * bps + 7) / 8); + var strIdx = 0; + for (var i = 0; i < length; i++) { + var b; + while (codeSize < bps) { + codeBuf <<= 8; + codeBuf |= strBytes[strIdx++]; + codeSize += 8; + } + codeSize -= bps; + array.push(codeBuf >> codeSize); + codeBuf &= (1 << codeSize) - 1; + } + return array; + }, + + getIR: function(xref, fn) { + var dict = fn.dict; + if (!dict) + dict = fn; + + var types = [this.constructSampled, + null, + this.constructInterpolated, + this.constructStiched, + this.constructPostScript]; + + var typeNum = dict.get('FunctionType'); + var typeFn = types[typeNum]; + if (!typeFn) + error('Unknown type of function'); + + return typeFn.call(this, fn, dict, xref); + }, + + fromIR: function(IR) { + var type = IR[0]; + switch (type) { + case CONSTRUCT_SAMPLED: + return this.constructSampledFromIR(IR); + case CONSTRUCT_INTERPOLATED: + return this.constructInterpolatedFromIR(IR); + case CONSTRUCT_STICHED: + return this.constructStichedFromIR(IR); + case CONSTRUCT_POSTSCRIPT: + return this.constructPostScriptFromIR(IR); + } + }, + + parse: function(xref, fn) { + var IR = this.getIR(xref, fn); + return this.fromIR(IR); + }, - constructor.prototype = { constructSampled: function(str, dict) { var domain = dict.get('Domain'); var range = dict.get('Range'); @@ -6495,7 +6548,21 @@ var PDFFunction = (function() { var samples = this.getSampleArray(size, outputSize, bps, str); - this.func = function(args) { + return [ CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, outputSize, bps, range ]; + }, + + constructSampledFromIR: function(IR) { + var inputSize = IR[1]; + var domain = IR[2]; + var encode = IR[3]; + var decode = IR[4] + var samples = IR[5] + var size = IR[6] + var outputSize= IR[7]; + var bps = IR[8]; + var range = IR[9]; + + return function(args) { var clip = function(v, min, max) { if (v > max) v = max; @@ -6552,33 +6619,9 @@ var PDFFunction = (function() { } return output; - }; - }, - getSampleArray: function(size, outputSize, bps, str) { - var length = 1; - for (var i = 0; i < size.length; i++) - length *= size[i]; - length *= outputSize; - - var array = []; - var codeSize = 0; - var codeBuf = 0; - - var strBytes = str.getBytes((length * bps + 7) / 8); - var strIdx = 0; - for (var i = 0; i < length; i++) { - var b; - while (codeSize < bps) { - codeBuf <<= 8; - codeBuf |= strBytes[strIdx++]; - codeSize += 8; - } - codeSize -= bps; - array.push(codeBuf >> codeSize); - codeBuf &= (1 << codeSize) - 1; } - return array; }, + constructInterpolated: function(str, dict) { var c0 = dict.get('C0') || [0]; var c1 = dict.get('C1') || [1]; @@ -6592,7 +6635,14 @@ var PDFFunction = (function() { for (var i = 0; i < length; ++i) diff.push(c1[i] - c0[i]); - this.func = function(args) { + return [ CONSTRUCT_INTERPOLATED, c0, diff ]; + }, + + constructInterpolatedFromIR: function(IR) { + var c0 = IR[1]; + var diff = IR[2]; + + return function(args) { var x = args[0]; var out = []; @@ -6600,8 +6650,10 @@ var PDFFunction = (function() { out.push(c0[j] + (x^n * diff[i])); return out; - }; + + } }, + constructStiched: function(fn, dict, xref) { var domain = dict.get('Domain'); var range = dict.get('Range'); @@ -6616,12 +6668,26 @@ var PDFFunction = (function() { var fnRefs = dict.get('Functions'); var fns = []; for (var i = 0, ii = fnRefs.length; i < ii; ++i) - fns.push(new PDFFunction(xref, xref.fetchIfRef(fnRefs[i]))); + fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i]))); var bounds = dict.get('Bounds'); var encode = dict.get('Encode'); - this.func = function(args) { + return [ CONSTRUCT_STICHED, domain, bounds, encoding, fns ]; + }, + + constructStichedFromIR: function(IR) { + var domain = IR[1]; + var bounds = IR[2]; + var encoding = IR[3]; + var fnsIR = IR[4]; + var fns = []; + + for (var i = 0; i < fnsIR.length; i++) { + fns.push(PDFFunction.fromIR(fnsIR[i])); + } + + return function(args) { var clip = function(v, min, max) { if (v > max) v = max; @@ -6655,13 +6721,17 @@ var PDFFunction = (function() { return fns[i].func([v2]); }; }, + constructPostScript: function() { + return [ CONSTRUCT_POSTSCRIPT ]; + }, + + constructPostScriptFromIR: function(IR) { TODO('unhandled type of function'); this.func = function() { return [255, 105, 180]; }; } - }; - - return constructor; + } })(); + From 755399a755f6c603fa8f052687aa317cacd369d8 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 8 Sep 2011 15:48:52 -0700 Subject: [PATCH 042/123] Fix TilingPattern + implement DummyShading IR form --- pdf.js | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/pdf.js b/pdf.js index 9bf879fa2649d..16d60098df78e 100644 --- a/pdf.js +++ b/pdf.js @@ -4241,7 +4241,7 @@ var PartialEvaluator = (function() { var codeIR = this.getIRQueue(pattern, xref, dict.get('Resources'), {}, fonts, images, uniquePrefix); - args = TilingPattern.getIR(codeIR, dict); + args = TilingPattern.getIR(codeIR, dict, args); } // Type2 is ShadingPattern. else if (typeNum == 2) { @@ -5268,6 +5268,7 @@ var CanvasGraphics = (function() { if (IR[0] == "TilingPatternIR") { // First, build the `color` var like it's done in the // Pattern.prototype.parse function. + var args = IR[1]; var base = cs.base; var color; if (base) { @@ -5282,7 +5283,7 @@ var CanvasGraphics = (function() { // Build the pattern based on the IR data. var pattern = new TilingPatternIR(IR, color, this.ctx); - } else if (IR[0] == "RadialAxialShading") { + } else if (IR[0] == "RadialAxialShading" || IR[0] == "DummyShading") { var pattern = Pattern.shadingFromIR(this.ctx, IR); } else { throw "Unkown IR type"; @@ -5745,7 +5746,7 @@ var SeparationCS = (function() { constructor.prototype = { getRgb: function sepcs_getRgb(color) { - var tinted = this.tintFn.func(color); + var tinted = this.tintFn(color); return this.base.getRgb(tinted); }, getRgbBuffer: function sepcs_getRgbBuffer(input, bits) { @@ -5760,7 +5761,7 @@ var SeparationCS = (function() { var baseBuf = new Uint8Array(numComps * length); for (var i = 0; i < length; ++i) { var scaled = input[i] * scale; - var tinted = tintFn.func([scaled]); + var tinted = tintFn([scaled]); for (var j = 0; j < numComps; ++j) baseBuf[pos++] = 255 * tinted[j]; } @@ -6015,9 +6016,14 @@ var DummyShading = (function() { function constructor() { this.type = 'Pattern'; } + + constructor.fromIR = function() { + return 'hotpink'; + } + constructor.prototype = { - getPattern: function dummy_getpattern() { - return 'hotpink'; + getIR: function dummpy_getir() { + return [ 'DummyShading' ]; } }; return constructor; @@ -6033,7 +6039,6 @@ var RadialAxialShading = (function() { this.type = 'Pattern'; this.ctx = ctx; - var cs = dict.get('ColorSpace', 'CS'); cs = ColorSpace.parse(cs, xref, res); this.cs = cs; @@ -6147,12 +6152,12 @@ var TilingPatternIR = (function() { function TilingPatternIR(IR, color, ctx) { // "Unfolding" the IR. - var IRQueue = IR[1]; - this.matrix = IR[2]; - var bbox = IR[3]; - var xstep = IR[4]; - var ystep = IR[5]; - var paintType = IR[6]; + var IRQueue = IR[2]; + this.matrix = IR[3]; + var bbox = IR[4]; + var xstep = IR[5]; + var ystep = IR[6]; + var paintType = IR[7]; // TODO('TilingType'); @@ -6242,14 +6247,14 @@ var TilingPatternIR = (function() { })(); var TilingPattern = { - getIR: function(codeIR, dict) { + getIR: function(codeIR, dict, args) { var matrix = dict.get('Matrix'); var bbox = dict.get('BBox'); var xstep = dict.get('XStep'); var ystep = dict.get('YStep'); var paintType = dict.get('PaintType'); - return ["TilingPatternIR", codeIR, matrix, bbox, xstep, ystep, paintType]; + return ["TilingPatternIR", args, codeIR, matrix, bbox, xstep, ystep, paintType]; } }; @@ -6718,7 +6723,7 @@ var PDFFunction = (function() { var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); // call the appropropriate function - return fns[i].func([v2]); + return fns[i]([v2]); }; }, @@ -6726,9 +6731,9 @@ var PDFFunction = (function() { return [ CONSTRUCT_POSTSCRIPT ]; }, - constructPostScriptFromIR: function(IR) { + constructPostScriptFromIR: function() { TODO('unhandled type of function'); - this.func = function() { + return function() { return [255, 105, 180]; }; } From 2b7ff49d8d4a7eca62498a14447c7b5fafb859d6 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 8 Sep 2011 16:22:06 -0700 Subject: [PATCH 043/123] Fix constructInterpolatedFromIR + fix indexing i that should be j --- pdf.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pdf.js b/pdf.js index 16d60098df78e..b5e6fbf76c256 100644 --- a/pdf.js +++ b/pdf.js @@ -6640,19 +6640,20 @@ var PDFFunction = (function() { for (var i = 0; i < length; ++i) diff.push(c1[i] - c0[i]); - return [ CONSTRUCT_INTERPOLATED, c0, diff ]; + return [ CONSTRUCT_INTERPOLATED, c0, diff, n ]; }, constructInterpolatedFromIR: function(IR) { - var c0 = IR[1]; + var c0 = IR[1]; var diff = IR[2]; + var n = IR[3]; return function(args) { var x = args[0]; var out = []; for (var j = 0; j < length; ++j) - out.push(c0[j] + (x^n * diff[i])); + out.push(c0[j] + (x^n * diff[j])); return out; From 7ee894c09cf05054237724ea0557f34589e0c165 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 8 Sep 2011 17:23:38 -0700 Subject: [PATCH 044/123] fontFile doesn't have to be a FlateStream. complex_ttf_font.pdf --- worker.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/worker.js b/worker.js index b71f1a878b10f..c0cf7c29d155c 100644 --- a/worker.js +++ b/worker.js @@ -139,7 +139,15 @@ var WorkerPDFDoc = (function() { var fontFile = new Stream(font.file.bytes, font.file.start, font.file.end - font.file.start, fontFileDict); - font.file = new FlateStream(fontFile); + + // Check if this is a FlateStream. Otherwise just use the created + // Stream one. This makes complex_ttf_font.pdf work. + var cmf = font.file.bytes[0]; + if ((cmf & 0x0f) == 0x08) { + font.file = new FlateStream(fontFile); + } else { + font.file = fontFile; + } } var images = data.images; From a2bf701bfeef9ebc977eb62ca2663150885f5007 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 8 Sep 2011 17:55:38 -0700 Subject: [PATCH 045/123] Implement endInlineImage IR --- pdf.js | 133 +++++++++++++++++++------------------- worker/message_handler.js | 14 ++-- 2 files changed, 71 insertions(+), 76 deletions(-) diff --git a/pdf.js b/pdf.js index b5e6fbf76c256..941c3c38e068c 100644 --- a/pdf.js +++ b/pdf.js @@ -4197,6 +4197,70 @@ var PartialEvaluator = (function() { constructor.prototype = { getIRQueue: function(stream, xref, resources, queue, fonts, images, uniquePrefix) { + function buildPaintImageXObject(image, inline) { + var dict = image.dict; + var w = dict.get('Width', 'W'); + var h = dict.get('Height', 'H'); + + if (image instanceof JpegStream) { + var objId = ++objIdCounter; + images.push({ + id: objId, + IR: image.getIR() + }); + + // Add the dependency on the image object. + fnArray.push("dependency"); + argsArray.push([ objId ]); + + // The normal fn. + fn = 'paintJpegXObject'; + args = [ objId, w, h ]; + } else { + // Needs to be rendered ourself. + + // Figure out if the image has an imageMask. + var imageMask = dict.get('ImageMask', 'IM') || false; + + // If there is no imageMask, create the PDFImage and a lot + // of image processing can be done here. + if (!imageMask) { + var imageObj = new PDFImage(xref, resources, image, inline); + + if (imageObj.imageMask) { + throw "Can't handle this in the web worker :/"; + } + + var imgData = { + width: w, + height: h, + data: new Uint8Array(w * h * 4) + }; + var pixels = imgData.data; + imageObj.fillRgbaBuffer(pixels, imageObj.decode); + + fn = "paintImageXObject"; + args = [ imgData ]; + } else /* imageMask == true */ { + // This depends on a tmpCanvas beeing filled with the + // current fillStyle, such that processing the pixel + // data can't be done here. Instead of creating a + // complete PDFImage, only read the information needed + // for later. + fn = "paintImageMaskXObject"; + + var width = dict.get('Width', 'W'); + var height = dict.get('Height', 'H'); + var bitStrideLength = (width + 7) >> 3; + var imgArray = image.getBytes(bitStrideLength * height); + var decode = dict.get('Decode', 'D'); + var inverseDecode = !!decode && decode[0] > 0; + + args = [ imgArray, inverseDecode, width, height ]; + } + } + } + uniquePrefix = uniquePrefix || ""; if (!queue.argsArray) { queue.argsArray = [] @@ -4283,69 +4347,7 @@ var PartialEvaluator = (function() { fn = "paintFormXObjectEnd"; args = []; } else if ('Image' == type.name) { - var image = xobj; - var dict = image.dict; - var w = dict.get('Width', 'W'); - var h = dict.get('Height', 'H'); - - if (image instanceof JpegStream) { - var objId = ++objIdCounter; - images.push({ - id: objId, - IR: image.getIR() - }); - - // Add the dependency on the image object. - fnArray.push("dependency"); - argsArray.push([ objId ]); - - // The normal fn. - fn = 'paintJpegXObject'; - args = [ objId, w, h ]; - } else { - // Needs to be rendered ourself. - - // Figure out if the image has an imageMask. - var imageMask = dict.get('ImageMask', 'IM') || false; - - // If there is no imageMask, create the PDFImage and a lot - // of image processing can be done here. - if (!imageMask) { - var inline = false; - var imageObj = new PDFImage(xref, resources, image, inline); - - if (imageObj.imageMask) { - throw "Can't handle this in the web worker :/"; - } - - var imgData = { - width: w, - height: h, - data: new Uint8Array(w * h * 4) - }; - var pixels = imgData.data; - imageObj.fillRgbaBuffer(pixels, imageObj.decode); - - fn = "paintImageXObject"; - args = [ imgData ]; - } else /* imageMask == true */ { - // This depends on a tmpCanvas beeing filled with the - // current fillStyle, such that processing the pixel - // data can't be done here. Instead of creating a - // complete PDFImage, only read the information needed - // for later. - fn = "paintImageMaskXObject"; - - var width = dict.get('Width', 'W'); - var height = dict.get('Height', 'H'); - var bitStrideLength = (width + 7) >> 3; - var imgArray = image.getBytes(bitStrideLength * height); - var decode = dict.get('Decode', 'D'); - var inverseDecode = !!imageObj.decode && imageObj.decode[0] > 0; - - args = [ imgArray, inverseDecode, width, height ]; - } - } + buildPaintImageXObject(xobj, false) } else { error('Unhandled XObject subtype ' + type.name); } @@ -4375,6 +4377,8 @@ var PartialEvaluator = (function() { // TODO: TOASK: Is it possible to get here? If so, what does // args[0].name should be like??? } + } else if (cmd == 'EI') { + buildPaintImageXObject(args[0], true); } // Transform some cmds. @@ -5383,9 +5387,6 @@ var CanvasGraphics = (function() { beginImageData: function() { error('Should not call beginImageData'); }, - endInlineImage: function(image) { - this.paintImageXObject(null, image, true); - }, paintFormXObjectBegin: function(matrix, bbox) { this.save(); diff --git a/worker/message_handler.js b/worker/message_handler.js index 63e8277e24d87..1a832cef0b074 100644 --- a/worker/message_handler.js +++ b/worker/message_handler.js @@ -37,16 +37,10 @@ MessageHandler.prototype = { }, send: function(actionName, data) { - try { - this.comObj.postMessage({ - action: actionName, - data: data - }); - } catch (e) { - console.error("FAILED to send data from", this.name); - throw e; - } + this.comObj.postMessage({ + action: actionName, + data: data + }); } - } From c370b8a428be472ece58ffff78ad97698f26129b Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 8 Sep 2011 18:34:54 -0700 Subject: [PATCH 046/123] Ensure things work for default fonts like Arial --- fonts.js | 8 ++++---- pdf.js | 4 ++-- worker.js | 25 ++++++++++++++----------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/fonts.js b/fonts.js index 69bb50590ca91..0ca75e88a6510 100755 --- a/fonts.js +++ b/fonts.js @@ -173,7 +173,7 @@ var FontLoader = { document.documentElement.removeEventListener( 'pdfjsFontLoad', checkFontsLoaded, false); - callback(); + callback(objs); return true; } @@ -450,6 +450,8 @@ var Font = (function Font() { var constructor = function font_constructor(name, file, properties) { this.name = name; this.encoding = properties.encoding; + this.glyphs = properties.glyphs; + this.loadedName = properties.loadedName; this.sizes = []; var names = name.split('+'); @@ -477,7 +479,6 @@ var Font = (function Font() { // name ArialBlack for example will be replaced by Helvetica. this.black = (name.search(/Black/g) != -1); - this.loadedName = fontName.split('-')[0]; this.loading = false; return; } @@ -514,14 +515,13 @@ var Font = (function Font() { this.type = properties.type; this.textMatrix = properties.textMatrix; this.composite = properties.composite; - this.loadedName = properties.loadedName; // TODO: Remove this once we can be sure nothing got broken to du changes // in this commit. if (!this.loadedName) { throw "There has to be a `loadedName`"; } - + this.loading = true; }; diff --git a/pdf.js b/pdf.js index 941c3c38e068c..3a2aff3e0c5a3 100644 --- a/pdf.js +++ b/pdf.js @@ -3427,9 +3427,9 @@ var Page = (function() { }, ensureFonts: function(fonts, callback) { - var fontObjs = FontLoader.bind( + FontLoader.bind( fonts, - function() { + function(fontObjs) { // Rebuild the FontsMap. This is emulating the behavior of the main // thread. if (fontObjs) { diff --git a/worker.js b/worker.js index c0cf7c29d155c..23cc9ae14d410 100644 --- a/worker.js +++ b/worker.js @@ -134,19 +134,22 @@ var WorkerPDFDoc = (function() { for (var i = 0; i < fonts.length; i++) { var font = fonts[i]; - var fontFileDict = new Dict(); - fontFileDict.map = font.file.dict.map; + // Some fonts don't have a file, e.g. the build in ones like Arial. + if (font.file) { + var fontFileDict = new Dict(); + fontFileDict.map = font.file.dict.map; - var fontFile = new Stream(font.file.bytes, font.file.start, - font.file.end - font.file.start, fontFileDict); + var fontFile = new Stream(font.file.bytes, font.file.start, + font.file.end - font.file.start, fontFileDict); - // Check if this is a FlateStream. Otherwise just use the created - // Stream one. This makes complex_ttf_font.pdf work. - var cmf = font.file.bytes[0]; - if ((cmf & 0x0f) == 0x08) { - font.file = new FlateStream(fontFile); - } else { - font.file = fontFile; + // Check if this is a FlateStream. Otherwise just use the created + // Stream one. This makes complex_ttf_font.pdf work. + var cmf = font.file.bytes[0]; + if ((cmf & 0x0f) == 0x08) { + font.file = new FlateStream(fontFile); + } else { + font.file = fontFile; + } } } From cda4c04312327ec13a545536f60e20367440f7dc Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 8 Sep 2011 19:28:15 -0700 Subject: [PATCH 047/123] Removing some console.log statements. --- pdf.js | 2 -- worker.js | 1 - worker/handler.js | 18 +----------------- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/pdf.js b/pdf.js index 3a2aff3e0c5a3..c0bf1be7b58eb 100644 --- a/pdf.js +++ b/pdf.js @@ -3461,7 +3461,6 @@ var Page = (function() { var IRQueue = this.IRQueue; function next() { - console.log("next executeIRQueue", startIdx, length); startIdx = gfx.executeIRQueue(IRQueue, startIdx, next); } next(); @@ -4903,7 +4902,6 @@ var CanvasGraphics = (function() { // If the promise isn't resolved yet, add the continueCallback // to the promise and bail out. if (!promise.isResolved) { - console.log("Unresolved object: " + depObjId); promise.then(continueCallback); return i; } diff --git a/worker.js b/worker.js index 23cc9ae14d410..e164de11b44f0 100644 --- a/worker.js +++ b/worker.js @@ -160,7 +160,6 @@ var WorkerPDFDoc = (function() { } var timeStart = new Date(); - console.log("startRenderingFromPreCompilation:", "numberOfFonts", fonts.length); page.startRenderingFromIRQueue(data.IRQueue, data.fonts, data.images); console.log("RenderingTime", (new Date()) - timeStart); }, this); diff --git a/worker/handler.js b/worker/handler.js index 01bb76a260cb2..c6e3e12978c48 100644 --- a/worker/handler.js +++ b/worker/handler.js @@ -9,12 +9,10 @@ var WorkerHandler = { handler.on("doc", function(data) { pdfDoc = new PDFDoc(new Stream(data)); - console.log("setup pdfDoc"); }); handler.on("page_request", function(pageNum) { pageNum = parseInt(pageNum); - console.log("about to process page", pageNum); var page = pdfDoc.getPage(pageNum); @@ -40,11 +38,7 @@ var WorkerHandler = { }); } - // TODO: Handle images here. - - console.log("about to send page", pageNum); - - if (true /* show used commands */) { + if (false /* show used commands */) { var cmdMap = {}; var fnArray = IRQueue .fnArray; @@ -59,16 +53,6 @@ var WorkerHandler = { cmdMap[entry] += 1; } } - - // // Make a copy of the fnArray and show all cmds it has. - // var fnArray = preCompilation.fnArray.slice(0).sort(); - // for (var i = 0; i < fnArray.length; true) { - // if (fnArray[i] == fnArray[i + 1]) { - // fnArray.splice(i, 1); - // } else { - // i++; - // } - // } console.log("cmds", JSON.stringify(cmdMap)); } From 342547831ddcd60dd5329ae61f06f7f8bef36144 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Fri, 9 Sep 2011 08:47:12 -0700 Subject: [PATCH 048/123] Adding back bug in constructInterpolatedFromIR to get refTest passing in openweb.pdf --- pdf.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pdf.js b/pdf.js index c0bf1be7b58eb..f41160ebbeaf1 100644 --- a/pdf.js +++ b/pdf.js @@ -6639,20 +6639,21 @@ var PDFFunction = (function() { for (var i = 0; i < length; ++i) diff.push(c1[i] - c0[i]); - return [ CONSTRUCT_INTERPOLATED, c0, diff, n ]; + return [ CONSTRUCT_INTERPOLATED, c0, diff, n, i ]; }, constructInterpolatedFromIR: function(IR) { var c0 = IR[1]; var diff = IR[2]; var n = IR[3]; + var i = IR[4]; return function(args) { var x = args[0]; var out = []; for (var j = 0; j < length; ++j) - out.push(c0[j] + (x^n * diff[j])); + out.push(c0[j] + (x^n * diff[i])); return out; From 90da4fc83183eb29ad1e3116eb513c62a7829356 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Fri, 9 Sep 2011 09:24:31 -0700 Subject: [PATCH 049/123] Ensure to call the pageDone callback after it's really done --- pdf.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pdf.js b/pdf.js index f41160ebbeaf1..44873a7448447 100644 --- a/pdf.js +++ b/pdf.js @@ -3386,13 +3386,12 @@ var Page = (function() { // Firefox error reporting from XHR callbacks. setTimeout(function() { var exc = null; - // try { - self.display(gfx); - self.stats.render = Date.now(); - // } catch (e) { - // exc = e.toString(); - // } - continuation(exc); + try { + self.display(gfx, continuation); + } catch (e) { + exc = e.toString(); + continuation(exc); + } }); }; @@ -3446,7 +3445,7 @@ var Page = (function() { ); }, - display: function(gfx) { + display: function(gfx, callback) { var xref = this.xref; var resources = xref.fetchIfRef(this.resources); var mediaBox = xref.fetchIfRef(this.mediaBox); @@ -3460,8 +3459,13 @@ var Page = (function() { var length = this.IRQueue.fnArray.length; var IRQueue = this.IRQueue; + var self = this; function next() { startIdx = gfx.executeIRQueue(IRQueue, startIdx, next); + if (startIdx == length) { + self.stats.render = Date.now(); + callback(); + } } next(); }, From a2287534143081c94b27295403c9919363a624ad Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 15 Sep 2011 13:13:00 -0700 Subject: [PATCH 050/123] Add missing metrics.js for worker --- worker/boot.js | 1 + 1 file changed, 1 insertion(+) diff --git a/worker/boot.js b/worker/boot.js index 241870c5c4dc4..cc3896c2a7f64 100644 --- a/worker/boot.js +++ b/worker/boot.js @@ -9,6 +9,7 @@ importScripts('../pdf.js'); importScripts('../fonts.js'); importScripts('../crypto.js'); importScripts('../glyphlist.js'); +importScripts('../metrics.js'); importScripts('handler.js'); // Listen for messages from the main thread. From 32ae879219294ff49c7da149f8ccd87a4375bb46 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Fri, 9 Sep 2011 09:58:44 -0700 Subject: [PATCH 051/123] Stop the execution if it takes longer then a certain amount of time and reshedule it --- pdf.js | 71 +++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/pdf.js b/pdf.js index 44873a7448447..9c8ea2fcc0031 100644 --- a/pdf.js +++ b/pdf.js @@ -4849,6 +4849,11 @@ function ScratchCanvas(width, height) { } var CanvasGraphics = (function() { + var kScalePrecision = 50; + var kRasterizerMin = 14; + var kExecutionTime = 50; + var kExecutionTimeCheck = 500; + function constructor(canvasCtx, imageCanvas) { this.ctx = canvasCtx; this.current = new CanvasExtraState(); @@ -4885,34 +4890,54 @@ var CanvasGraphics = (function() { this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height); }, - executeIRQueue: function(codeIR, startIdx, continueCallback) { + executeIRQueue: function(codeIR, executionStartIdx, continueCallback) { var argsArray = codeIR.argsArray; var fnArray = codeIR.fnArray; - var i = startIdx || 0; - var length = argsArray.length; - for (i; i < length; i++) { - if (fnArray[i] !== "dependency") { - this[fnArray[i]].apply(this, argsArray[i]); - } else { - var deps = argsArray[i]; - for (var n = 0; n < deps.length; n++) { - var depObjId = deps[n]; - var promise; - if (!Objects[depObjId]) { - promise = Objects[depObjId] = new Promise(); - } else { - promise = Objects[depObjId]; - } - // If the promise isn't resolved yet, add the continueCallback - // to the promise and bail out. - if (!promise.isResolved) { - promise.then(continueCallback); - return i; + var i = executionStartIdx || 0; + var argsArrayLen = argsArray.length; + + var executionEndIdx; + var startTime = Date.now(); + + do { + executionEndIdx = Math.min(argsArrayLen, i + kExecutionTimeCheck); + + for (i; i < executionEndIdx; i++) { + if (fnArray[i] !== "dependency") { + this[fnArray[i]].apply(this, argsArray[i]); + } else { + var deps = argsArray[i]; + for (var n = 0; n < deps.length; n++) { + var depObjId = deps[n]; + var promise; + if (!Objects[depObjId]) { + promise = Objects[depObjId] = new Promise(); + } else { + promise = Objects[depObjId]; + } + // If the promise isn't resolved yet, add the continueCallback + // to the promise and bail out. + if (!promise.isResolved) { + promise.then(continueCallback); + return i; + } } } } - } - return i; + + // If the entire IRQueue was executed, stop as were done. + if (i == argsArrayLen) { + return i; + } + // If the execution took longer then a certain amount of time, shedule + // to continue exeution after a short delay. + else if ((Date.now() - startTime) > kExecutionTime) { + setTimeout(continueCallback, 0); + return i; + } + // If the IRQueue isn't executed completly yet OR the execution time + // was short enough, do another execution round. + } while (true); }, endDrawing: function() { From ac11f30ae9fbb08f5383f4c6f1ebca83c520e223 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Fri, 9 Sep 2011 11:20:55 -0700 Subject: [PATCH 052/123] Send JpegStreams to the main thread ASAP. No need for ImagesLoader anymore --- pdf.js | 67 +++++++++-------------------------------------- worker.js | 40 +++++++++++++++++++--------- worker/handler.js | 6 ++--- 3 files changed, 43 insertions(+), 70 deletions(-) diff --git a/pdf.js b/pdf.js index 9c8ea2fcc0031..3f0594939a5b7 100644 --- a/pdf.js +++ b/pdf.js @@ -943,44 +943,6 @@ var JpegStream = (function() { return constructor; })(); -// Simple object to track the loading images -// Initialy for every that is in loading call imageLoading() -// and, when images onload is fired, call imageLoaded() -// When all images are loaded, the onLoad event is fired. -var ImagesLoader = (function() { - function constructor() { - this.loading = 0; - } - - constructor.prototype = { - imageLoading: function() { - ++this.loading; - }, - - imageLoaded: function() { - if (--this.loading == 0 && this.onLoad) { - this.onLoad(); - delete this.onLoad; - } - }, - - bind: function(jpegStream) { - if (jpegStream.loaded) - return; - this.imageLoading(); - jpegStream.onLoad = this.imageLoaded.bind(this); - }, - - notifyOnLoad: function(callback) { - if (this.loading == 0) - callback(); - this.onLoad = callback; - } - }; - - return constructor; -})(); - var DecryptStream = (function() { function constructor(str, decrypt) { this.str = str; @@ -3377,7 +3339,7 @@ var Page = (function() { return shadow(this, 'rotate', rotate); }, - startRenderingFromIRQueue: function(gfx, IRQueue, fonts, images, continuation) { + startRenderingFromIRQueue: function(gfx, IRQueue, fonts, continuation) { var self = this; this.IRQueue = IRQueue; @@ -3396,14 +3358,11 @@ var Page = (function() { }; this.ensureFonts(fonts, function() { - images.notifyOnLoad(function() { - self.stats.images = Date.now(); - displayContinuation(); - }); - }) + displayContinuation(); + }); }, - getIRQueue: function(fonts, images) { + getIRQueue: function(handler, fonts) { if (this.IRQueue) { // content was compiled return this.IRQueue; @@ -3422,7 +3381,7 @@ var Page = (function() { var pe = this.pe = new PartialEvaluator(); var IRQueue = {}; - return this.IRQueue = pe.getIRQueue(content, xref, resources, IRQueue, fonts, images, "p" + this.pageNumber + "_"); + return this.IRQueue = pe.getIRQueue(content, xref, resources, IRQueue, handler, fonts, "p" + this.pageNumber + "_"); }, ensureFonts: function(fonts, callback) { @@ -3460,10 +3419,13 @@ var Page = (function() { var IRQueue = this.IRQueue; var self = this; + var startTime = Date.now(); function next() { startIdx = gfx.executeIRQueue(IRQueue, startIdx, next); if (startIdx == length) { self.stats.render = Date.now(); + console.log("page=%d - executeIRQueue: time=%dms", + self.pageNumber + 1, self.stats.render - startTime); callback(); } } @@ -4199,7 +4161,7 @@ var PartialEvaluator = (function() { }; constructor.prototype = { - getIRQueue: function(stream, xref, resources, queue, fonts, images, uniquePrefix) { + getIRQueue: function(stream, xref, resources, queue, handler, fonts, uniquePrefix) { function buildPaintImageXObject(image, inline) { var dict = image.dict; var w = dict.get('Width', 'W'); @@ -4207,10 +4169,7 @@ var PartialEvaluator = (function() { if (image instanceof JpegStream) { var objId = ++objIdCounter; - images.push({ - id: objId, - IR: image.getIR() - }); + handler.send("obj", [objId, "JpegStream", image.getIR()]); // Add the dependency on the image object. fnArray.push("dependency"); @@ -4291,7 +4250,7 @@ var PartialEvaluator = (function() { if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) { // Use the IR version for setStroke/FillColorN. fn += '_IR'; - + // compile tiling patterns var patternName = args[args.length - 1]; // SCN/scn applies patterns along with normal colors @@ -4306,7 +4265,7 @@ var PartialEvaluator = (function() { // TODO: Add dependency here. // Create an IR of the pattern code. var codeIR = this.getIRQueue(pattern, xref, - dict.get('Resources'), {}, fonts, images, uniquePrefix); + dict.get('Resources'), {}, handler, fonts, uniquePrefix); args = TilingPattern.getIR(codeIR, dict, args); } @@ -4344,7 +4303,7 @@ var PartialEvaluator = (function() { // This adds the IRQueue of the xObj to the current queue. this.getIRQueue(xobj, xref, xobj.dict.get('Resources'), queue, - fonts, images, uniquePrefix); + handler, fonts, uniquePrefix); fn = "paintFormXObjectEnd"; diff --git a/worker.js b/worker.js index e164de11b44f0..558bba90bcefb 100644 --- a/worker.js +++ b/worker.js @@ -30,16 +30,24 @@ var WorkerPage = (function() { // TODO: Place the worker magic HERE. // this.page.startRendering(ctx, callback, errback); + this.startRenderingTime = Date.now(); this.workerPDF.startRendering(this) }, - startRenderingFromIRQueue: function(IRQueue, fonts, images) { + startRenderingFromIRQueue: function(IRQueue, fonts) { var gfx = new CanvasGraphics(this.ctx); - // TODO: Add proper handling for images loaded by the worker. - var images = new ImagesLoader(); - - this.page.startRenderingFromIRQueue(gfx, IRQueue, fonts, images, this.callback); + var startTime = Date.now(); + var callback = function(err) { + var pageNum = this.page.pageNumber + 1; + console.log("page=%d - rendering time: time=%dms", + pageNum, Date.now() - startTime); + console.log("page=%d - total time: time=%dms", + pageNum, Date.now() - this.startRenderingTime); + + this.callback(err); + }.bind(this); + this.page.startRenderingFromIRQueue(gfx, IRQueue, fonts, callback); }, getLinks: function() { @@ -153,15 +161,21 @@ var WorkerPDFDoc = (function() { } } - var images = data.images; - for (var i = 0; i < images.length; i++) { - var image = images[i]; - var stream = new JpegStreamIR(image.id, image.IR); + page.startRenderingFromIRQueue(data.IRQueue, data.fonts); + }, this); + + handler.on("obj", function(data) { + var objId = data[0]; + var objType = data[1]; + + switch (objType) { + case "JpegStream": + var IR = data[2]; + new JpegStreamIR(objId, IR); + break; + default: + throw "Got unkown object type " + objType; } - - var timeStart = new Date(); - page.startRenderingFromIRQueue(data.IRQueue, data.fonts, data.images); - console.log("RenderingTime", (new Date()) - timeStart); }, this); if (!useWorker) { diff --git a/worker/handler.js b/worker/handler.js index c6e3e12978c48..ab8079564aa77 100644 --- a/worker/handler.js +++ b/worker/handler.js @@ -20,11 +20,12 @@ var WorkerHandler = { // but stops at one point and sends the result back to the main thread. var gfx = new CanvasGraphics(null); var fonts = []; - var images = []; + var start = Date.now(); // Pre compile the pdf page and fetch the fonts/images. - var IRQueue = page.getIRQueue(fonts, images); + var IRQueue = page.getIRQueue(handler, fonts); + console.log("page=%d - getIRQueue: time=%dms, len=%d", pageNum, Date.now() - start, IRQueue.fnArray.length); // Extract the minimum of font data that is required to build all required // font stuff on the main thread. var fontsMin = []; @@ -59,7 +60,6 @@ var WorkerHandler = { handler.send("page", { pageNum: pageNum, fonts: fontsMin, - images: images, IRQueue: IRQueue, }); }, this); From dd9aea21e9c306c3899679eac851b20e68b1c574 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Fri, 9 Sep 2011 16:15:51 -0700 Subject: [PATCH 053/123] Trying to implement progressive font rendering. Works on FF, but Chrome doesn't catchup the fonts --- fonts.js | 70 +++++++++++++++++------------------------ pdf.js | 48 +++++++++++++++++++--------- worker.js | 79 +++++++++++++++++++++++++++++++---------------- worker/handler.js | 16 +--------- 4 files changed, 115 insertions(+), 98 deletions(-) diff --git a/fonts.js b/fonts.js index 0ca75e88a6510..f9af8d06861bf 100755 --- a/fonts.js +++ b/fonts.js @@ -159,24 +159,12 @@ if (!isWorker) { } var FontLoader = { - listeningForFontLoad: false, + fontLoadData: {}, + fonts: {}, bind: function(fonts, callback) { - function checkFontsLoaded() { - for (var i = 0; i < objs.length; i++) { - var fontObj = objs[i]; - if (fontObj.loading) { - return false; - } - } - - document.documentElement.removeEventListener( - 'pdfjsFontLoad', checkFontsLoaded, false); - - callback(objs); - return true; - } - + console.log("requesting fonts", fonts[0].properties.loadedName, fonts[0].name); + var rules = [], names = [], objs = []; for (var i = 0; i < fonts.length; i++) { @@ -196,28 +184,33 @@ var FontLoader = { if (rule) { rules.push(rule); names.push(obj.loadedName); + this.fonts[obj.loadedName] = obj; + this.fontLoadData[obj.loadedName] = obj; } } } - this.listeningForFontLoad = false; - if (!isWorker && rules.length) { - FontLoader.prepareFontLoadEvent(rules, names, objs); - } - - if (!checkFontsLoaded()) { - document.documentElement.addEventListener( - 'pdfjsFontLoad', checkFontsLoaded, false); + if (rules.length) { + this.fontsLoading += rules.length; + FontLoader.prepareFontLoadEvent(rules, names); } return objs; }, + + postFontLoadEvent: function(names) { + for (var i = 0; i < names.length; i++) { + var name = names[i]; + Objects.resolve(name, this.fontLoadData[name]); + } + }, + // Set things up so that at least one pdfjsFontLoad event is // dispatched when all the @font-face |rules| for |names| have been // loaded in a subdocument. It's expected that the load of |rules| // has already started in this (outer) document, so that they should // be ordered before the load in the subdocument. - prepareFontLoadEvent: function(rules, names, objs) { + prepareFontLoadEvent: function(rules, names, callback) { /** Hack begin */ // There's no event when a font has finished downloading so the // following code is a dirty hack to 'guess' when a font is @@ -253,23 +246,6 @@ var FontLoader = { div.innerHTML = html; document.body.appendChild(div); - if (!this.listeningForFontLoad) { - window.addEventListener( - 'message', - function(e) { - var fontNames = JSON.parse(e.data); - for (var i = 0; i < objs.length; ++i) { - var font = objs[i]; - font.loading = false; - } - var evt = document.createEvent('Events'); - evt.initEvent('pdfjsFontLoad', true, false); - document.documentElement.dispatchEvent(evt); - }, - false); - this.listeningForFontLoad = true; - } - // XXX we should have a time-out here too, and maybe fire // pdfjsFontLoadFailed? var src = ''; @@ -303,6 +279,16 @@ var FontLoader = { } }; +if (!isWorker) { + window.addEventListener( + 'message', + function(e) { + FontLoader.postFontLoadEvent(JSON.parse(e.data)); + }.bind(this), + false); +} + + var UnicodeRanges = [ { 'begin': 0x0000, 'end': 0x007F }, // Basic Latin { 'begin': 0x0080, 'end': 0x00FF }, // Latin-1 Supplement diff --git a/pdf.js b/pdf.js index 3f0594939a5b7..eb80030721bfe 100644 --- a/pdf.js +++ b/pdf.js @@ -3353,16 +3353,17 @@ var Page = (function() { } catch (e) { exc = e.toString(); continuation(exc); + throw e; } }); }; - this.ensureFonts(fonts, function() { + // this.ensureFonts(fonts, function() { displayContinuation(); - }); + // }); }, - getIRQueue: function(handler, fonts) { + getIRQueue: function(handler) { if (this.IRQueue) { // content was compiled return this.IRQueue; @@ -3381,7 +3382,7 @@ var Page = (function() { var pe = this.pe = new PartialEvaluator(); var IRQueue = {}; - return this.IRQueue = pe.getIRQueue(content, xref, resources, IRQueue, handler, fonts, "p" + this.pageNumber + "_"); + return this.IRQueue = pe.getIRQueue(content, xref, resources, IRQueue, handler, "p" + this.pageNumber + "_"); }, ensureFonts: function(fonts, callback) { @@ -4161,7 +4162,13 @@ var PartialEvaluator = (function() { }; constructor.prototype = { - getIRQueue: function(stream, xref, resources, queue, handler, fonts, uniquePrefix) { + getIRQueue: function(stream, xref, resources, queue, handler, uniquePrefix) { + + function insertDependency(depList) { + fnArray.push("dependency"); + argsArray.push(depList); + } + function buildPaintImageXObject(image, inline) { var dict = image.dict; var w = dict.get('Width', 'W'); @@ -4172,9 +4179,8 @@ var PartialEvaluator = (function() { handler.send("obj", [objId, "JpegStream", image.getIR()]); // Add the dependency on the image object. - fnArray.push("dependency"); - argsArray.push([ objId ]); - + insertDependency([objId]); + // The normal fn. fn = 'paintJpegXObject'; args = [ objId, w, h ]; @@ -4265,7 +4271,7 @@ var PartialEvaluator = (function() { // TODO: Add dependency here. // Create an IR of the pattern code. var codeIR = this.getIRQueue(pattern, xref, - dict.get('Resources'), {}, handler, fonts, uniquePrefix); + dict.get('Resources'), {}, handler, uniquePrefix); args = TilingPattern.getIR(codeIR, dict, args); } @@ -4303,7 +4309,7 @@ var PartialEvaluator = (function() { // This adds the IRQueue of the xObj to the current queue. this.getIRQueue(xobj, xref, xobj.dict.get('Resources'), queue, - handler, fonts, uniquePrefix); + handler, uniquePrefix); fn = "paintFormXObjectEnd"; @@ -4324,14 +4330,24 @@ var PartialEvaluator = (function() { assertWellFormed(IsDict(font)); if (!font.translated) { font.translated = this.translateFont(font, xref, resources); - if (fonts && font.translated) { + if (font.translated) { // keep track of each font we translated so the caller can // load them asynchronously before calling display on a page - fonts.push(font.translated); - var loadedName = uniquePrefix + "font_" + (FontLoadedCounter++); font.translated.properties.loadedName = loadedName; FontsMap[loadedName] = font; + + handler.send("obj", [ + loadedName, + "Font", + font.translated.name, + font.translated.file, + font.translated.properties + ]); + + // Ensure the font is ready before the font is set + // and later on used for drawing. + insertDependency([loadedName]); } } args[0].name = font.translated.properties.loadedName; @@ -4870,13 +4886,14 @@ var CanvasGraphics = (function() { var depObjId = deps[n]; var promise; if (!Objects[depObjId]) { - promise = Objects[depObjId] = new Promise(); + promise = Objects[depObjId] = new Promise(depObjId); } else { promise = Objects[depObjId]; } // If the promise isn't resolved yet, add the continueCallback // to the promise and bail out. if (!promise.isResolved) { + console.log("depending on obj", depObjId); promise.then(continueCallback); return i; } @@ -5096,13 +5113,14 @@ var CanvasGraphics = (function() { setFont: function(fontRef, size) { // Lookup the fontObj using fontRef only. var fontRefName = fontRef.name; - var fontObj = FontsMap[fontRefName].fontObj; + var fontObj = Objects.get(fontRefName); if (!fontObj) { throw "Can't find font for " + fontRefName; } var name = fontObj.loadedName; + console.log("setFont", name); if (!name) { // TODO: fontDescriptor is not available, fallback to default font name = 'sans-serif'; diff --git a/worker.js b/worker.js index 558bba90bcefb..440b948175fb2 100644 --- a/worker.js +++ b/worker.js @@ -62,16 +62,25 @@ var WorkerPage = (function() { var Objects = { resolve: function(objId, data) { // In case there is a promise already on this object, just resolve it. - if (Objects[objId] instanceof Promise) { + if (Objects[objId]) { Objects[objId].resolve(data); } else { - Objects[objId] = new Promise(data); + Objects[objId] = new Promise(objId, data); } + }, + + get: function(objId) { + var obj = Objects[objId]; + if (!obj || !obj.isResolved) { + throw "Requesting object that isn't resolved yet"; + } + return obj.data; } }; var Promise = (function() { - function Promise(data) { + function Promise(name, data) { + this.name = name; // If you build a promise and pass in some data it's already resolved. if (data != null) { this.isResolved = true; @@ -84,6 +93,8 @@ var Promise = (function() { Promise.prototype = { resolve: function(data) { + console.log("resolve", this.name); + if (this.isResolved) { throw "A Promise can be resolved only once"; } @@ -137,29 +148,6 @@ var WorkerPDFDoc = (function() { var pageNum = data.pageNum; var page = this.pageCache[pageNum]; - // Add necessary shape back to fonts. - var fonts = data.fonts; - for (var i = 0; i < fonts.length; i++) { - var font = fonts[i]; - - // Some fonts don't have a file, e.g. the build in ones like Arial. - if (font.file) { - var fontFileDict = new Dict(); - fontFileDict.map = font.file.dict.map; - - var fontFile = new Stream(font.file.bytes, font.file.start, - font.file.end - font.file.start, fontFileDict); - - // Check if this is a FlateStream. Otherwise just use the created - // Stream one. This makes complex_ttf_font.pdf work. - var cmf = font.file.bytes[0]; - if ((cmf & 0x0f) == 0x08) { - font.file = new FlateStream(fontFile); - } else { - font.file = fontFile; - } - } - } page.startRenderingFromIRQueue(data.IRQueue, data.fonts); }, this); @@ -173,6 +161,45 @@ var WorkerPDFDoc = (function() { var IR = data[2]; new JpegStreamIR(objId, IR); break; + case "Font": + var name = data[2]; + var file = data[3]; + var properties = data[4]; + + console.log("got new font", name); + + var font = { + name: name, + file: file, + properties: properties + }; + + // Some fonts don't have a file, e.g. the build in ones like Arial. + if (file) { + var fontFileDict = new Dict(); + fontFileDict.map = file.dict.map; + + var fontFile = new Stream(file.bytes, file.start, + file.end - file.start, fontFileDict); + + // Check if this is a FlateStream. Otherwise just use the created + // Stream one. This makes complex_ttf_font.pdf work. + var cmf = file.bytes[0]; + if ((cmf & 0x0f) == 0x08) { + font.file = new FlateStream(fontFile); + } else { + font.file = fontFile; + } + } + + FontLoader.bind( + [ font ], + function(fontObjs) { + var fontObj = fontObjs[0]; + Objects.resolve(objId, fontObj); + } + ); + break; default: throw "Got unkown object type " + objType; } diff --git a/worker/handler.js b/worker/handler.js index ab8079564aa77..fe6c6d8ef5410 100644 --- a/worker/handler.js +++ b/worker/handler.js @@ -19,25 +19,12 @@ var WorkerHandler = { // The following code does quite the same as Page.prototype.startRendering, // but stops at one point and sends the result back to the main thread. var gfx = new CanvasGraphics(null); - var fonts = []; var start = Date.now(); // Pre compile the pdf page and fetch the fonts/images. - var IRQueue = page.getIRQueue(handler, fonts); + var IRQueue = page.getIRQueue(handler); console.log("page=%d - getIRQueue: time=%dms, len=%d", pageNum, Date.now() - start, IRQueue.fnArray.length); - // Extract the minimum of font data that is required to build all required - // font stuff on the main thread. - var fontsMin = []; - for (var i = 0; i < fonts.length; i++) { - var font = fonts[i]; - - fontsMin.push({ - name: font.name, - file: font.file, - properties: font.properties - }); - } if (false /* show used commands */) { var cmdMap = {}; @@ -59,7 +46,6 @@ var WorkerHandler = { handler.send("page", { pageNum: pageNum, - fonts: fontsMin, IRQueue: IRQueue, }); }, this); From e15bfc00a0b4c6e182878b9acbd826e9d2d0c00d Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Fri, 9 Sep 2011 19:35:45 -0700 Subject: [PATCH 054/123] Fixing font loading issues --- fonts.js | 63 +++++++++++++++++++++++++++++++++++++++----------------- pdf.js | 1 - 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/fonts.js b/fonts.js index f9af8d06861bf..e8ab0c56da984 100755 --- a/fonts.js +++ b/fonts.js @@ -159,49 +159,73 @@ if (!isWorker) { } var FontLoader = { - fontLoadData: {}, fonts: {}, + fontsLoading: false, + waitingNames: [], + waitingStr: [], bind: function(fonts, callback) { console.log("requesting fonts", fonts[0].properties.loadedName, fonts[0].name); - var rules = [], names = [], objs = []; + var rules = [], names = []; for (var i = 0; i < fonts.length; i++) { var font = fonts[i]; var obj = new Font(font.name, font.file, font.properties); - objs.push(obj); var str = ''; var data = obj.data; + var name = obj.loadedName; if (data) { var length = data.length; for (var j = 0; j < length; j++) str += String.fromCharCode(data[j]); - var rule = isWorker ? obj.bindWorker(str) : obj.bindDOM(str); - if (rule) { - rules.push(rule); - names.push(obj.loadedName); - this.fonts[obj.loadedName] = obj; - this.fontLoadData[obj.loadedName] = obj; - } + + this.fonts[obj.loadedName] = obj; + + this.waitingNames.push(name); + this.waitingStr.push(str); + } else { + // If there is no data, then there is nothing to load and we can + // resolve the object right away. + Objects.resolve(name, obj); } } - if (rules.length) { - this.fontsLoading += rules.length; - FontLoader.prepareFontLoadEvent(rules, names); + if (!this.fontsLoading) { + this.executeWaiting(); + } else { + console.log('There are currently some fonts getting loaded - waiting'); } + }, + + executeWaiting: function() { + var names = this.waitingNames; + console.log('executing fonts', names.join(', ')); - return objs; + var rules = []; + for (var i = 0; i < names.length; i++) { + var obj = this.fonts[names[i]]; + var rule = obj.bindDOM(this.waitingStr[i]); + rules.push(rule); + } + this.prepareFontLoadEvent(rules, names); + this.waitingNames = []; + this.waitingStr = []; }, - postFontLoadEvent: function(names) { + fontLoadEvent: function(names) { + this.fontsLoading = false; + for (var i = 0; i < names.length; i++) { var name = names[i]; - Objects.resolve(name, this.fontLoadData[name]); + Objects.resolve(name, this.fonts[name]); + } + + if (this.waitingNames.length != 0) { + this.executeWaiting(); } }, @@ -210,7 +234,8 @@ var FontLoader = { // loaded in a subdocument. It's expected that the load of |rules| // has already started in this (outer) document, so that they should // be ordered before the load in the subdocument. - prepareFontLoadEvent: function(rules, names, callback) { + prepareFontLoadEvent: function(rules, names) { + this.fontsLoading = true; /** Hack begin */ // There's no event when a font has finished downloading so the // following code is a dirty hack to 'guess' when a font is @@ -261,7 +286,7 @@ var FontLoader = { } src += ' var fontNames=[' + fontNamesArray + '];\n'; src += ' window.onload = function () {\n'; - src += ' parent.postMessage(JSON.stringify(fontNames), "*");\n'; + src += ' setTimeout(function(){parent.postMessage(JSON.stringify(fontNames), "*")},0);\n'; src += ' }'; src += ''; for (var i = 0; i < names.length; ++i) { @@ -283,7 +308,7 @@ if (!isWorker) { window.addEventListener( 'message', function(e) { - FontLoader.postFontLoadEvent(JSON.parse(e.data)); + FontLoader.fontLoadEvent(JSON.parse(e.data)); }.bind(this), false); } diff --git a/pdf.js b/pdf.js index eb80030721bfe..c8068dd3b8866 100644 --- a/pdf.js +++ b/pdf.js @@ -5120,7 +5120,6 @@ var CanvasGraphics = (function() { } var name = fontObj.loadedName; - console.log("setFont", name); if (!name) { // TODO: fontDescriptor is not available, fallback to default font name = 'sans-serif'; From 6e9306afd2bedec0cb9f4a12c23ff4cb072653b8 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Fri, 9 Sep 2011 16:29:50 -0700 Subject: [PATCH 055/123] Remove console.logs from previous commit again --- fonts.js | 2 -- pdf.js | 1 - worker.js | 4 ---- 3 files changed, 7 deletions(-) diff --git a/fonts.js b/fonts.js index e8ab0c56da984..fd467eecab655 100755 --- a/fonts.js +++ b/fonts.js @@ -165,8 +165,6 @@ var FontLoader = { waitingStr: [], bind: function(fonts, callback) { - console.log("requesting fonts", fonts[0].properties.loadedName, fonts[0].name); - var rules = [], names = []; for (var i = 0; i < fonts.length; i++) { diff --git a/pdf.js b/pdf.js index c8068dd3b8866..e7c3d6efa1a77 100644 --- a/pdf.js +++ b/pdf.js @@ -4893,7 +4893,6 @@ var CanvasGraphics = (function() { // If the promise isn't resolved yet, add the continueCallback // to the promise and bail out. if (!promise.isResolved) { - console.log("depending on obj", depObjId); promise.then(continueCallback); return i; } diff --git a/worker.js b/worker.js index 440b948175fb2..78e88835c0ff1 100644 --- a/worker.js +++ b/worker.js @@ -93,8 +93,6 @@ var Promise = (function() { Promise.prototype = { resolve: function(data) { - console.log("resolve", this.name); - if (this.isResolved) { throw "A Promise can be resolved only once"; } @@ -166,8 +164,6 @@ var WorkerPDFDoc = (function() { var file = data[3]; var properties = data[4]; - console.log("got new font", name); - var font = { name: name, file: file, From d639a9a94abdfd2e2949dd1c5efb3f6ad4681166 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Fri, 9 Sep 2011 16:45:41 -0700 Subject: [PATCH 056/123] Remove no longer needed worker files, rename boot to boot_processor --- worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker.js b/worker.js index 78e88835c0ff1..6f4a38da8ad37 100644 --- a/worker.js +++ b/worker.js @@ -131,7 +131,7 @@ var WorkerPDFDoc = (function() { var useWorker = true; if (useWorker) { - var worker = new Worker("../worker/boot.js"); + var worker = new Worker("../worker/boot_processor.js"); } else { // If we don't use a worker, just post/sendMessage to the main thread. var worker = { From 9eb785858602add567a652ac463cb993677c5086 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Fri, 9 Sep 2011 18:14:42 -0700 Subject: [PATCH 057/123] Fix --- worker/boot_processor.js | 18 ++ worker/canvas.js | 252 ----------------------- worker/client.js | 417 --------------------------------------- worker/font.js | 66 ------- 4 files changed, 18 insertions(+), 735 deletions(-) create mode 100644 worker/boot_processor.js delete mode 100644 worker/canvas.js delete mode 100644 worker/client.js delete mode 100644 worker/font.js diff --git a/worker/boot_processor.js b/worker/boot_processor.js new file mode 100644 index 0000000000000..241870c5c4dc4 --- /dev/null +++ b/worker/boot_processor.js @@ -0,0 +1,18 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +importScripts('console.js'); +importScripts('message_handler.js'); +importScripts('../pdf.js'); +importScripts('../fonts.js'); +importScripts('../crypto.js'); +importScripts('../glyphlist.js'); +importScripts('handler.js'); + +// Listen for messages from the main thread. +var pdfDoc = null; + +var handler = new MessageHandler("worker", this); +WorkerHandler.setup(handler); diff --git a/worker/canvas.js b/worker/canvas.js deleted file mode 100644 index 5a9237d9af937..0000000000000 --- a/worker/canvas.js +++ /dev/null @@ -1,252 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ - -'use strict'; - -var JpegStreamProxyCounter = 0; -// WebWorker Proxy for JpegStream. -var JpegStreamProxy = (function() { - function constructor(bytes, dict) { - this.id = JpegStreamProxyCounter++; - this.dict = dict; - - // Tell the main thread to create an image. - postMessage({ - action: 'jpeg_stream', - data: { - id: this.id, - raw: bytesToString(bytes) - } - }); - } - - constructor.prototype = { - getImage: function() { - return this; - }, - getChar: function() { - error('internal error: getChar is not valid on JpegStream'); - } - }; - - return constructor; -})(); - -// Really simple GradientProxy. There is currently only one active gradient at -// the time, meaning you can't create a gradient, create a second one and then -// use the first one again. As this isn't used in pdf.js right now, it's okay. -function GradientProxy(cmdQueue, x0, y0, x1, y1) { - cmdQueue.push(['$createLinearGradient', [x0, y0, x1, y1]]); - this.addColorStop = function(i, rgba) { - cmdQueue.push(['$addColorStop', [i, rgba]]); - } -} - -// Really simple PatternProxy. -var patternProxyCounter = 0; -function PatternProxy(cmdQueue, object, kind) { - this.id = patternProxyCounter++; - - if (!(object instanceof CanvasProxy)) { - throw 'unkown type to createPattern'; - } - - // Flush the object here to ensure it's available on the main thread. - // TODO: Make some kind of dependency management, such that the object - // gets flushed only if needed. - object.flush(); - cmdQueue.push(['$createPatternFromCanvas', [this.id, object.id, kind]]); -} - -var canvasProxyCounter = 0; -function CanvasProxy(width, height) { - this.id = canvasProxyCounter++; - - // The `stack` holds the rendering calls and gets flushed to the main thead. - var cmdQueue = this.cmdQueue = []; - - // Dummy context that gets exposed. - var ctx = {}; - this.getContext = function(type) { - if (type != '2d') { - throw 'CanvasProxy can only provide a 2d context.'; - } - return ctx; - } - - // Expose only the minimum of the canvas object - there is no dom to do - // more here. - this.width = width; - this.height = height; - ctx.canvas = this; - - // Setup function calls to `ctx`. - var ctxFunc = [ - 'createRadialGradient', - 'arcTo', - 'arc', - 'fillText', - 'strokeText', - 'createImageData', - 'drawWindow', - 'save', - 'restore', - 'scale', - 'rotate', - 'translate', - 'transform', - 'setTransform', - 'clearRect', - 'fillRect', - 'strokeRect', - 'beginPath', - 'closePath', - 'moveTo', - 'lineTo', - 'quadraticCurveTo', - 'bezierCurveTo', - 'rect', - 'fill', - 'stroke', - 'clip', - 'measureText', - 'isPointInPath', - - // These functions are necessary to track the rendering currentX state. - // The exact values can be computed on the main thread only, as the - // worker has no idea about text width. - '$setCurrentX', - '$addCurrentX', - '$saveCurrentX', - '$restoreCurrentX', - '$showText', - '$setFont' - ]; - - function buildFuncCall(name) { - return function() { - // console.log("funcCall", name) - cmdQueue.push([name, Array.prototype.slice.call(arguments)]); - } - } - var name; - for (var i = 0; i < ctxFunc.length; i++) { - name = ctxFunc[i]; - ctx[name] = buildFuncCall(name); - } - - // Some function calls that need more work. - - ctx.createPattern = function(object, kind) { - return new PatternProxy(cmdQueue, object, kind); - } - - ctx.createLinearGradient = function(x0, y0, x1, y1) { - return new GradientProxy(cmdQueue, x0, y0, x1, y1); - } - - ctx.getImageData = function(x, y, w, h) { - return { - width: w, - height: h, - data: Uint8ClampedArray(w * h * 4) - }; - } - - ctx.putImageData = function(data, x, y, width, height) { - cmdQueue.push(['$putImageData', [data, x, y, width, height]]); - } - - ctx.drawImage = function(image, x, y, width, height, - sx, sy, swidth, sheight) { - if (image instanceof CanvasProxy) { - // Send the image/CanvasProxy to the main thread. - image.flush(); - cmdQueue.push(['$drawCanvas', [image.id, x, y, sx, sy, swidth, sheight]]); - } else if (image instanceof JpegStreamProxy) { - cmdQueue.push(['$drawImage', [image.id, x, y, sx, sy, swidth, sheight]]); - } else { - throw 'unkown type to drawImage'; - } - } - - // Setup property access to `ctx`. - var ctxProp = { - // "canvas" - 'globalAlpha': '1', - 'globalCompositeOperation': 'source-over', - 'strokeStyle': '#000000', - 'fillStyle': '#000000', - 'lineWidth': '1', - 'lineCap': 'butt', - 'lineJoin': 'miter', - 'miterLimit': '10', - 'shadowOffsetX': '0', - 'shadowOffsetY': '0', - 'shadowBlur': '0', - 'shadowColor': 'rgba(0, 0, 0, 0)', - 'font': '10px sans-serif', - 'textAlign': 'start', - 'textBaseline': 'alphabetic', - 'mozTextStyle': '10px sans-serif', - 'mozImageSmoothingEnabled': 'true' - }; - - function buildGetter(name) { - return function() { - return ctx['$' + name]; - } - } - - function buildSetter(name) { - return function(value) { - cmdQueue.push(['$', name, value]); - return ctx['$' + name] = value; - } - } - - // Setting the value to `stroke|fillStyle` needs special handling, as it - // might gets an gradient/pattern. - function buildSetterStyle(name) { - return function(value) { - if (value instanceof GradientProxy) { - cmdQueue.push(['$' + name + 'Gradient']); - } else if (value instanceof PatternProxy) { - cmdQueue.push(['$' + name + 'Pattern', [value.id]]); - } else { - cmdQueue.push(['$', name, value]); - return ctx['$' + name] = value; - } - } - } - - for (var name in ctxProp) { - ctx['$' + name] = ctxProp[name]; - ctx.__defineGetter__(name, buildGetter(name)); - - // Special treatment for `fillStyle` and `strokeStyle`: The passed style - // might be a gradient. Need to check for that. - if (name == 'fillStyle' || name == 'strokeStyle') { - ctx.__defineSetter__(name, buildSetterStyle(name)); - } else { - ctx.__defineSetter__(name, buildSetter(name)); - } - } -} - -/** -* Sends the current cmdQueue of the CanvasProxy over to the main thread and -* resets the cmdQueue. -*/ -CanvasProxy.prototype.flush = function() { - postMessage({ - action: 'canvas_proxy_cmd_queue', - data: { - id: this.id, - cmdQueue: this.cmdQueue, - width: this.width, - height: this.height - } - }); - this.cmdQueue.length = 0; -}; diff --git a/worker/client.js b/worker/client.js deleted file mode 100644 index a20a4179ff28d..0000000000000 --- a/worker/client.js +++ /dev/null @@ -1,417 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ - -'use strict'; - -if (typeof console.time == 'undefined') { - var consoleTimer = {}; - console.time = function(name) { - consoleTimer[name] = Date.now(); - }; - - console.timeEnd = function(name) { - var time = consoleTimer[name]; - if (time == null) { - throw 'Unkown timer name ' + name; - } - this.log('Timer:', name, Date.now() - time); - }; -} - -function FontWorker() { - this.worker = new Worker('worker/font.js'); - this.fontsWaiting = 0; - this.fontsWaitingCallbacks = []; - - // Listen to the WebWorker for data and call actionHandler on it. - this.worker.onmessage = function(event) { - var data = event.data; - var actionHandler = this.actionHandler; - if (data.action in actionHandler) { - actionHandler[data.action].call(this, data.data); - } else { - throw 'Unkown action from worker: ' + data.action; - } - }.bind(this); - - this.$handleFontLoadedCallback = this.handleFontLoadedCallback.bind(this); -} - -FontWorker.prototype = { - handleFontLoadedCallback: function() { - // Decrease the number of fonts wainting to be loaded. - this.fontsWaiting--; - // If all fonts are available now, then call all the callbacks. - if (this.fontsWaiting == 0) { - var callbacks = this.fontsWaitingCallbacks; - for (var i = 0; i < callbacks.length; i++) { - callbacks[i](); - } - this.fontsWaitingCallbacks.length = 0; - } - }, - - actionHandler: { - 'log': function(data) { - console.log.apply(console, data); - }, - - 'fonts': function(data) { - // console.log("got processed fonts from worker", Object.keys(data)); - for (name in data) { - // Update the encoding property. - var font = Fonts.lookup(name); - font.properties = { - encoding: data[name].encoding - }; - - // Call `Font.prototype.bindDOM` to make the font get loaded - // on the page. - Font.prototype.bindDOM.call( - font, - data[name].str, - // IsLoadedCallback. - this.$handleFontLoadedCallback - ); - } - } - }, - - ensureFonts: function(data, callback) { - var font; - var notLoaded = []; - for (var i = 0; i < data.length; i++) { - font = data[i]; - if (Fonts[font.name]) { - continue; - } - - // Register the font but don't pass in any real data. The idea is to - // store as less data as possible to reduce memory usage. - Fonts.registerFont(font.name, Object.create(null), Object.create(null)); - - // Mark this font to be handled later. - notLoaded.push(font); - // Increate the number of fonts to wait for. - this.fontsWaiting++; - } - - console.time('ensureFonts'); - // If there are fonts, that need to get loaded, tell the FontWorker to get - // started and push the callback on the waiting-callback-stack. - if (notLoaded.length != 0) { - console.log('fonts -> FontWorker'); - // Send the worker the fonts to work on. - this.worker.postMessage({ - action: 'fonts', - data: notLoaded - }); - if (callback) { - this.fontsWaitingCallbacks.push(callback); - } - } - // All fonts are present? Well, then just call the callback if there is one. - else { - if (callback) { - callback(); - } - } - } -}; - -function WorkerPDFDoc(canvas) { - var timer = null; - - this.ctx = canvas.getContext('2d'); - this.canvas = canvas; - this.worker = new Worker('worker/pdf.js'); - this.fontWorker = new FontWorker(); - this.waitingForFonts = false; - this.waitingForFontsCallback = []; - - this.numPage = 1; - this.numPages = null; - - var imagesList = {}; - var canvasList = { - 0: canvas - }; - var patternList = {}; - var gradient; - - var currentX = 0; - var currentXStack = []; - - var ctxSpecial = { - '$setCurrentX': function(value) { - currentX = value; - }, - - '$addCurrentX': function(value) { - currentX += value; - }, - - '$saveCurrentX': function() { - currentXStack.push(currentX); - }, - - '$restoreCurrentX': function() { - currentX = currentXStack.pop(); - }, - - '$showText': function(y, text) { - text = Fonts.charsToUnicode(text); - this.translate(currentX, -1 * y); - this.fillText(text, 0, 0); - currentX += this.measureText(text).width; - }, - - '$putImageData': function(imageData, x, y) { - var imgData = this.getImageData(0, 0, imageData.width, imageData.height); - - // Store the .data property to avaid property lookups. - var imageRealData = imageData.data; - var imgRealData = imgData.data; - - // Copy over the imageData. - var len = imageRealData.length; - while (len--) - imgRealData[len] = imageRealData[len]; - - this.putImageData(imgData, x, y); - }, - - '$drawImage': function(id, x, y, sx, sy, swidth, sheight) { - var image = imagesList[id]; - if (!image) { - throw 'Image not found: ' + id; - } - this.drawImage(image, x, y, image.width, image.height, - sx, sy, swidth, sheight); - }, - - '$drawCanvas': function(id, x, y, sx, sy, swidth, sheight) { - var canvas = canvasList[id]; - if (!canvas) { - throw 'Canvas not found'; - } - if (sheight != null) { - this.drawImage(canvas, x, y, canvas.width, canvas.height, - sx, sy, swidth, sheight); - } else { - this.drawImage(canvas, x, y, canvas.width, canvas.height); - } - }, - - '$createLinearGradient': function(x0, y0, x1, y1) { - gradient = this.createLinearGradient(x0, y0, x1, y1); - }, - - '$createPatternFromCanvas': function(patternId, canvasId, kind) { - var canvas = canvasList[canvasId]; - if (!canvas) { - throw 'Canvas not found'; - } - patternList[patternId] = this.createPattern(canvas, kind); - }, - - '$addColorStop': function(i, rgba) { - gradient.addColorStop(i, rgba); - }, - - '$fillStyleGradient': function() { - this.fillStyle = gradient; - }, - - '$fillStylePattern': function(id) { - var pattern = patternList[id]; - if (!pattern) { - throw 'Pattern not found'; - } - this.fillStyle = pattern; - }, - - '$strokeStyleGradient': function() { - this.strokeStyle = gradient; - }, - - '$strokeStylePattern': function(id) { - var pattern = patternList[id]; - if (!pattern) { - throw 'Pattern not found'; - } - this.strokeStyle = pattern; - }, - - '$setFont': function(name, size) { - this.font = size + 'px "' + name + '"'; - Fonts.setActive(name, size); - } - }; - - function renderProxyCanvas(canvas, cmdQueue) { - var ctx = canvas.getContext('2d'); - var cmdQueueLength = cmdQueue.length; - for (var i = 0; i < cmdQueueLength; i++) { - var opp = cmdQueue[i]; - if (opp[0] == '$') { - ctx[opp[1]] = opp[2]; - } else if (opp[0] in ctxSpecial) { - ctxSpecial[opp[0]].apply(ctx, opp[1]); - } else { - ctx[opp[0]].apply(ctx, opp[1]); - } - } - } - - /** - * Functions to handle data sent by the WebWorker. - */ - var actionHandler = { - 'log': function(data) { - console.log.apply(console, data); - }, - - 'pdf_num_pages': function(data) { - this.numPages = parseInt(data); - if (this.loadCallback) { - this.loadCallback(); - } - }, - - 'font': function(data) { - var base64 = window.btoa(data.raw); - - // Add the @font-face rule to the document - var url = 'url(data:' + data.mimetype + ';base64,' + base64 + ');'; - var rule = ("@font-face { font-family:'" + data.fontName + - "';src:" + url + '}'); - var styleSheet = document.styleSheets[0]; - styleSheet.insertRule(rule, styleSheet.cssRules.length); - - // Just adding the font-face to the DOM doesn't make it load. It - // seems it's loaded once Gecko notices it's used. Therefore, - // add a div on the page using the loaded font. - var div = document.createElement('div'); - var style = 'font-family:"' + data.fontName + - '";position: absolute;top:-99999;left:-99999;z-index:-99999'; - div.setAttribute('style', style); - document.body.appendChild(div); - }, - - 'setup_page': function(data) { - var size = data.split(','); - var canvas = this.canvas, ctx = this.ctx; - canvas.width = parseInt(size[0]); - canvas.height = parseInt(size[1]); - }, - - 'fonts': function(data) { - this.waitingForFonts = true; - this.fontWorker.ensureFonts(data, function() { - this.waitingForFonts = false; - var callbacks = this.waitingForFontsCallback; - for (var i = 0; i < callbacks.length; i++) { - callbacks[i](); - } - this.waitingForFontsCallback.length = 0; - }.bind(this)); - }, - - 'jpeg_stream': function(data) { - var img = new Image(); - img.src = 'data:image/jpeg;base64,' + window.btoa(data.raw); - imagesList[data.id] = img; - }, - - 'canvas_proxy_cmd_queue': function(data) { - var id = data.id; - var cmdQueue = data.cmdQueue; - - // Check if there is already a canvas with the given id. If not, - // create a new canvas. - if (!canvasList[id]) { - var newCanvas = document.createElement('canvas'); - newCanvas.width = data.width; - newCanvas.height = data.height; - canvasList[id] = newCanvas; - } - - var renderData = function() { - if (id == 0) { - console.time('main canvas rendering'); - var ctx = this.ctx; - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - } - renderProxyCanvas(canvasList[id], cmdQueue); - if (id == 0) { - console.timeEnd('main canvas rendering'); - console.timeEnd('>>> total page display time:'); - } - }.bind(this); - - if (this.waitingForFonts) { - if (id == 0) { - console.log('want to render, but not all fonts are there', id); - this.waitingForFontsCallback.push(renderData); - } else { - // console.log("assume canvas doesn't have fonts", id); - renderData(); - } - } else { - renderData(); - } - } - }; - - // Listen to the WebWorker for data and call actionHandler on it. - this.worker.onmessage = function(event) { - var data = event.data; - if (data.action in actionHandler) { - actionHandler[data.action].call(this, data.data); - } else { - throw 'Unkown action from worker: ' + data.action; - } - }.bind(this); -} - -WorkerPDFDoc.prototype.open = function(url, callback) { - var req = new XMLHttpRequest(); - req.open('GET', url); - req.mozResponseType = req.responseType = 'arraybuffer'; - req.expected = (document.URL.indexOf('file:') == 0) ? 0 : 200; - req.onreadystatechange = function() { - if (req.readyState == 4 && req.status == req.expected) { - var data = req.mozResponseArrayBuffer || req.mozResponse || - req.responseArrayBuffer || req.response; - - this.loadCallback = callback; - this.worker.postMessage(data); - this.showPage(this.numPage); - } - }.bind(this); - req.send(null); -}; - -WorkerPDFDoc.prototype.showPage = function(numPage) { - this.numPage = parseInt(numPage); - console.log('=== start rendering page ' + numPage + ' ==='); - console.time('>>> total page display time:'); - this.worker.postMessage(numPage); - if (this.onChangePage) { - this.onChangePage(numPage); - } -}; - -WorkerPDFDoc.prototype.nextPage = function() { - if (this.numPage == this.numPages) return; - this.showPage(++this.numPage); -}; - -WorkerPDFDoc.prototype.prevPage = function() { - if (this.numPage == 1) return; - this.showPage(--this.numPage); -}; diff --git a/worker/font.js b/worker/font.js deleted file mode 100644 index 549b73101dd33..0000000000000 --- a/worker/font.js +++ /dev/null @@ -1,66 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ - -'use strict'; - -importScripts('console.js'); - -importScripts('../pdf.js'); -importScripts('../fonts.js'); -importScripts('../glyphlist.js'); - -function fontDataToString(font) { - // Doing postMessage on objects make them lose their "shape". This adds the - // "shape" for all required objects agains, such that the encoding works as - // expected. - var fontFileDict = new Dict(); - fontFileDict.map = font.file.dict.map; - - var fontFile = new Stream(font.file.bytes, font.file.start, - font.file.end - font.file.start, fontFileDict); - font.file = new FlateStream(fontFile); - - // This will encode the font. - var fontObj = new Font(font.name, font.file, font.properties); - - // Create string that is used for css later. - var str = ''; - var data = fontObj.data; - var length = data.length; - for (var j = 0; j < length; j++) - str += String.fromCharCode(data[j]); - - return { - str: str, - encoding: font.properties.encoding - }; -} - -/** -* Functions to handle data sent by the MainThread. -*/ -var actionHandler = { - 'fonts': function(data) { - var fontData; - var result = {}; - for (var i = 0; i < data.length; i++) { - fontData = data[i]; - result[fontData.name] = fontDataToString(fontData); - } - - postMessage({ - action: 'fonts', - data: result - }); - } -}; - -// Listen to the MainThread for data and call actionHandler on it. -this.onmessage = function(event) { - var data = event.data; - if (data.action in actionHandler) { - actionHandler[data.action].call(this, data.data); - } else { - throw 'Unkown action from worker: ' + data.action; - } -}; From e9fff5968e7cb1372de0986458aeb83d149d3503 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Fri, 9 Sep 2011 18:16:22 -0700 Subject: [PATCH 058/123] Remove worker/boot.js --- worker/boot.js | 19 ------------------- worker/boot_processor.js | 1 + 2 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 worker/boot.js diff --git a/worker/boot.js b/worker/boot.js deleted file mode 100644 index cc3896c2a7f64..0000000000000 --- a/worker/boot.js +++ /dev/null @@ -1,19 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ - -'use strict'; - -importScripts('console.js'); -importScripts('message_handler.js'); -importScripts('../pdf.js'); -importScripts('../fonts.js'); -importScripts('../crypto.js'); -importScripts('../glyphlist.js'); -importScripts('../metrics.js'); -importScripts('handler.js'); - -// Listen for messages from the main thread. -var pdfDoc = null; - -var handler = new MessageHandler("worker", this); -WorkerHandler.setup(handler); diff --git a/worker/boot_processor.js b/worker/boot_processor.js index 241870c5c4dc4..cc3896c2a7f64 100644 --- a/worker/boot_processor.js +++ b/worker/boot_processor.js @@ -9,6 +9,7 @@ importScripts('../pdf.js'); importScripts('../fonts.js'); importScripts('../crypto.js'); importScripts('../glyphlist.js'); +importScripts('../metrics.js'); importScripts('handler.js'); // Listen for messages from the main thread. From 6dcf9f42a5da22d21dae274132926be8f27d19eb Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Fri, 9 Sep 2011 18:17:56 -0700 Subject: [PATCH 059/123] Make font processing happen in a worker --- fonts.js | 277 +++++++++++++++++++++++--------------------------- web/viewer.js | 2 +- worker.js | 46 +++------ 3 files changed, 144 insertions(+), 181 deletions(-) diff --git a/fonts.js b/fonts.js index fd467eecab655..6fb99a4d21d48 100755 --- a/fonts.js +++ b/fonts.js @@ -161,68 +161,57 @@ if (!isWorker) { var FontLoader = { fonts: {}, fontsLoading: false, - waitingNames: [], - waitingStr: [], - - bind: function(fonts, callback) { - var rules = [], names = []; - - for (var i = 0; i < fonts.length; i++) { - var font = fonts[i]; - - var obj = new Font(font.name, font.file, font.properties); - - var str = ''; - var data = obj.data; - var name = obj.loadedName; - if (data) { - var length = data.length; - for (var j = 0; j < length; j++) - str += String.fromCharCode(data[j]); - - - this.fonts[obj.loadedName] = obj; - - this.waitingNames.push(name); - this.waitingStr.push(str); - } else { - // If there is no data, then there is nothing to load and we can - // resolve the object right away. - Objects.resolve(name, obj); - } - } + waitingFontObjs: [], + waitingFontIds: [], + bind: function(objId, fontObj) { + this.waitingFontObjs.push(fontObj); + this.waitingFontIds.push(objId); + if (!this.fontsLoading) { this.executeWaiting(); - } else { - console.log('There are currently some fonts getting loaded - waiting'); } }, + + bindDOM: function font_bindDom(fontObj) { + var fontName = fontObj.loadedName; + // Add the font-face rule to the document + var url = ('url(data:' + fontObj.mimetype + ';base64,' + + window.btoa(fontObj.str) + ');'); + var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}'; + var styleSheet = document.styleSheets[0]; + styleSheet.insertRule(rule, styleSheet.cssRules.length); + return rule; + }, executeWaiting: function() { - var names = this.waitingNames; - console.log('executing fonts', names.join(', ')); - var rules = []; - for (var i = 0; i < names.length; i++) { - var obj = this.fonts[names[i]]; - var rule = obj.bindDOM(this.waitingStr[i]); + var names = []; + var objIds = this.waitingFontIds; + + for (var i = 0; i < this.waitingFontObjs.length; i++) { + var fontObj = this.waitingFontObjs[i]; + var rule = this.bindDOM(fontObj); + this.fonts[objIds[i]] = fontObj; + names.push(fontObj.loadedName); rules.push(rule); } - this.prepareFontLoadEvent(rules, names); - this.waitingNames = []; - this.waitingStr = []; + + this.prepareFontLoadEvent(rules, names, objIds); + this.waitingFontIds = []; + this.waitingFontObjs = []; }, - fontLoadEvent: function(names) { - this.fontsLoading = false; - - for (var i = 0; i < names.length; i++) { - var name = names[i]; - Objects.resolve(name, this.fonts[name]); + fontLoadEvent: function(objIds) { + for (var i = 0; i < objIds.length; i++) { + var objId = objIds[i]; + Objects.resolve(objId, this.fonts[objId]); + delete this.fonts[objId]; } + + this.fontsLoading = false; - if (this.waitingNames.length != 0) { + if (this.waitingFontIds.length != 0) { this.executeWaiting(); } }, @@ -232,7 +221,7 @@ var FontLoader = { // loaded in a subdocument. It's expected that the load of |rules| // has already started in this (outer) document, so that they should // be ordered before the load in the subdocument. - prepareFontLoadEvent: function(rules, names) { + prepareFontLoadEvent: function(rules, names, objIds) { this.fontsLoading = true; /** Hack begin */ // There's no event when a font has finished downloading so the @@ -278,18 +267,16 @@ var FontLoader = { } src += ''; src += ''; - for (var i = 0; i < names.length; ++i) { - src += '

Hi

'; - } + src += '

Hi

'; src += ''; var frame = document.createElement('iframe'); frame.src = 'data:text/html,' + src; @@ -447,6 +434,90 @@ function getUnicodeRangeFor(value) { return -1; } +/** + * FontShape is the minimal shape a FontObject can have to be useful during + * executing the IRQueue. + */ +var FontShape = (function FontShape() { + var constructor = function FontShape_constructor(obj) { + for (var name in obj) { + this[name] = obj[name]; + } + }; + + function int16(bytes) { + return (bytes[0] << 8) + (bytes[1] & 0xff); + }; + + constructor.prototype = { + charsToUnicode: function fonts_chars2Unicode(chars) { + var charsCache = this.charsCache; + var str; + + // if we translated this string before, just grab it from the cache + if (charsCache) { + str = charsCache[chars]; + if (str) + return str; + } + + // lazily create the translation cache + if (!charsCache) + charsCache = this.charsCache = Object.create(null); + + // translate the string using the font's encoding + var encoding = this.encoding; + if (!encoding) + return chars; + str = ''; + + if (this.composite) { + // composite fonts have multi-byte strings convert the string from + // single-byte to multi-byte + // XXX assuming CIDFonts are two-byte - later need to extract the + // correct byte encoding according to the PDF spec + var length = chars.length - 1; // looping over two bytes at a time so + // loop should never end on the last byte + for (var i = 0; i < length; i++) { + var charcode = int16([chars.charCodeAt(i++), chars.charCodeAt(i)]); + var unicode = encoding[charcode]; + if ('undefined' == typeof(unicode)) { + warn('Unencoded charcode ' + charcode); + unicode = charcode; + } else { + unicode = unicode.unicode; + } + str += String.fromCharCode(unicode); + } + } + else { + for (var i = 0; i < chars.length; ++i) { + var charcode = chars.charCodeAt(i); + var unicode = encoding[charcode]; + if ('undefined' == typeof(unicode)) { + warn('Unencoded charcode ' + charcode); + unicode = charcode; + } else { + unicode = unicode.unicode; + } + + // Handle surrogate pairs + if (unicode > 0xFFFF) { + str += String.fromCharCode(unicode & 0xFFFF); + unicode >>= 16; + } + str += String.fromCharCode(unicode); + } + } + + // Enter the translated string into the cache + return (charsCache[chars] = str); + } + } + + return constructor; +})(); + /** * 'Font' is the class the outside world should use, it encapsulate all the font * decoding logics whatever type it is (assuming the font type is supported). @@ -1322,98 +1393,6 @@ var Font = (function Font() { } return stringToArray(otf.file); - }, - - bindWorker: function font_bindWorker(data) { - postMessage({ - action: 'font', - data: { - raw: data, - fontName: this.loadedName, - mimetype: this.mimetype - } - }); - }, - - bindDOM: function font_bindDom(data) { - var fontName = this.loadedName; - - // Add the font-face rule to the document - var url = ('url(data:' + this.mimetype + ';base64,' + - window.btoa(data) + ');'); - var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}'; - var styleSheet = document.styleSheets[0]; - if (!styleSheet) { - document.documentElement.firstChild.appendChild( document.createElement('style') ); - styleSheet = document.styleSheets[0]; - } - styleSheet.insertRule(rule, styleSheet.cssRules.length); - - return rule; - }, - - charsToUnicode: function fonts_chars2Unicode(chars) { - var charsCache = this.charsCache; - var str; - - // if we translated this string before, just grab it from the cache - if (charsCache) { - str = charsCache[chars]; - if (str) - return str; - } - - // lazily create the translation cache - if (!charsCache) - charsCache = this.charsCache = Object.create(null); - - // translate the string using the font's encoding - var encoding = this.encoding; - if (!encoding) - return chars; - str = ''; - - if (this.composite) { - // composite fonts have multi-byte strings convert the string from - // single-byte to multi-byte - // XXX assuming CIDFonts are two-byte - later need to extract the - // correct byte encoding according to the PDF spec - var length = chars.length - 1; // looping over two bytes at a time so - // loop should never end on the last byte - for (var i = 0; i < length; i++) { - var charcode = int16([chars.charCodeAt(i++), chars.charCodeAt(i)]); - var unicode = encoding[charcode]; - if ('undefined' == typeof(unicode)) { - warn('Unencoded charcode ' + charcode); - unicode = charcode; - } else { - unicode = unicode.unicode; - } - str += String.fromCharCode(unicode); - } - } - else { - for (var i = 0; i < chars.length; ++i) { - var charcode = chars.charCodeAt(i); - var unicode = encoding[charcode]; - if ('undefined' == typeof(unicode)) { - warn('Unencoded charcode ' + charcode); - unicode = charcode; - } else { - unicode = unicode.unicode; - } - - // Handle surrogate pairs - if (unicode > 0xFFFF) { - str += String.fromCharCode(unicode & 0xFFFF); - unicode >>= 16; - } - str += String.fromCharCode(unicode); - } - } - - // Enter the translated string into the cache - return (charsCache[chars] = str); } }; diff --git a/web/viewer.js b/web/viewer.js index 0273f7170aa58..3d994dfdd75a3 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -4,7 +4,7 @@ 'use strict'; var kDefaultURL = 'compressed.tracemonkey-pldi-09.pdf'; -var kDefaultScale = 1.5; +var kDefaultScale = 1; var kDefaultScaleDelta = 1.1; var kCacheSize = 20; var kCssUnits = 96.0 / 72.0; diff --git a/worker.js b/worker.js index 6f4a38da8ad37..9c6bf40df562e 100644 --- a/worker.js +++ b/worker.js @@ -141,6 +141,9 @@ var WorkerPDFDoc = (function() { } } + var fontWorker = new Worker('../worker/boot_font.js'); + var fontHandler = this.fontHandler = new MessageHandler('font', fontWorker); + var handler = this.handler = new MessageHandler("main", worker); handler.on("page", function(data) { var pageNum = data.pageNum; @@ -164,42 +167,23 @@ var WorkerPDFDoc = (function() { var file = data[3]; var properties = data[4]; - var font = { - name: name, - file: file, - properties: properties - }; - - // Some fonts don't have a file, e.g. the build in ones like Arial. - if (file) { - var fontFileDict = new Dict(); - fontFileDict.map = file.dict.map; - - var fontFile = new Stream(file.bytes, file.start, - file.end - file.start, fontFileDict); - - // Check if this is a FlateStream. Otherwise just use the created - // Stream one. This makes complex_ttf_font.pdf work. - var cmf = file.bytes[0]; - if ((cmf & 0x0f) == 0x08) { - font.file = new FlateStream(fontFile); - } else { - font.file = fontFile; - } - } - - FontLoader.bind( - [ font ], - function(fontObjs) { - var fontObj = fontObjs[0]; - Objects.resolve(objId, fontObj); - } - ); + fontHandler.send("font", [objId, name, file, properties]); break; default: throw "Got unkown object type " + objType; } }, this); + + fontHandler.on('font_ready', function(data) { + var objId = data[0]; + var fontObj = new FontShape(data[1]); + // If there is no string, then there is nothing to attach to the DOM. + if (!fontObj.str) { + Objects.resolve(objId, fontObj); + } else { + FontLoader.bind(objId, fontObj); + } + }); if (!useWorker) { // If the main thread is our worker, setup the handling for the messages From 86681a8d252c1c564f95ddb600280156edb85c97 Mon Sep 17 00:00:00 2001 From: Julian Vierec Date: Tue, 13 Sep 2011 07:49:35 -0700 Subject: [PATCH 060/123] Add Objects.setData and Promise.data to set the data before the object/promise is resolved --- fonts.js | 4 +--- pdf.js | 8 ++------ worker.js | 41 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/fonts.js b/fonts.js index 6fb99a4d21d48..59a654aeb17e1 100755 --- a/fonts.js +++ b/fonts.js @@ -192,7 +192,6 @@ var FontLoader = { for (var i = 0; i < this.waitingFontObjs.length; i++) { var fontObj = this.waitingFontObjs[i]; var rule = this.bindDOM(fontObj); - this.fonts[objIds[i]] = fontObj; names.push(fontObj.loadedName); rules.push(rule); } @@ -205,8 +204,7 @@ var FontLoader = { fontLoadEvent: function(objIds) { for (var i = 0; i < objIds.length; i++) { var objId = objIds[i]; - Objects.resolve(objId, this.fonts[objId]); - delete this.fonts[objId]; + Objects.resolve(objId); } this.fontsLoading = false; diff --git a/pdf.js b/pdf.js index e7c3d6efa1a77..6068785ba3fd9 100644 --- a/pdf.js +++ b/pdf.js @@ -4884,12 +4884,8 @@ var CanvasGraphics = (function() { var deps = argsArray[i]; for (var n = 0; n < deps.length; n++) { var depObjId = deps[n]; - var promise; - if (!Objects[depObjId]) { - promise = Objects[depObjId] = new Promise(depObjId); - } else { - promise = Objects[depObjId]; - } + var promise = Objects.getPromise(depObjId); + // If the promise isn't resolved yet, add the continueCallback // to the promise and bail out. if (!promise.isResolved) { diff --git a/worker.js b/worker.js index 9c6bf40df562e..a1bbf85a621de 100644 --- a/worker.js +++ b/worker.js @@ -60,6 +60,19 @@ var WorkerPage = (function() { // This holds a list of objects the IR queue depends on. var Objects = { + getPromise: function(objId) { + if (Objects[objId]) { + return this[objId]; + } else { + return this[objId] = new Promise(objId); + } + }, + + setData: function(objId, data) { + var promise = this.getPromise(objId); + promise.data = data; + }, + resolve: function(objId, data) { // In case there is a promise already on this object, just resolve it. if (Objects[objId]) { @@ -79,19 +92,39 @@ var Objects = { }; var Promise = (function() { + var EMPTY_PROMISE = {}; + function Promise(name, data) { this.name = name; // If you build a promise and pass in some data it's already resolved. if (data != null) { this.isResolved = true; - this.data = data; + this.$data = data; } else { this.isResolved = false; + this.$data = EMPTY_PROMISE; } this.callbacks = []; }; Promise.prototype = { + set data(data) { + if (data === undefined) { + return; + } + if (this.$data !== EMPTY_PROMISE) { + throw "Promise " + this.name + ": Cannot set the data of a promise twice"; + } + this.$data = data; + }, + + get data() { + if (this.$data === EMPTY_PROMISE) { + throw "Promise " + this.name + ": Cannot get data that isn't set"; + } + return this.$data; + }, + resolve: function(data) { if (this.isResolved) { throw "A Promise can be resolved only once"; @@ -109,7 +142,8 @@ var Promise = (function() { then: function(callback) { // If the promise is already resolved, call the callback directly. if (this.isResolved) { - callback.call(null, this.data); + var data = this.data; + callback.call(null, data); } else { this.callbacks.push(callback); } @@ -128,7 +162,7 @@ var WorkerPDFDoc = (function() { this.pageCache = []; - var useWorker = true; + var useWorker = false; if (useWorker) { var worker = new Worker("../worker/boot_processor.js"); @@ -181,6 +215,7 @@ var WorkerPDFDoc = (function() { if (!fontObj.str) { Objects.resolve(objId, fontObj); } else { + Objects.setData(objId, fontObj); FontLoader.bind(objId, fontObj); } }); From 966cbc21127f0c9db7c466e775e5948810fde139 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Tue, 13 Sep 2011 07:59:24 -0700 Subject: [PATCH 061/123] Add FontShape.getRule which returns the name for the ctx object --- fonts.js | 15 +++++++++++++++ pdf.js | 11 +---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/fonts.js b/fonts.js index 59a654aeb17e1..d7a66fd001d3c 100755 --- a/fonts.js +++ b/fonts.js @@ -441,6 +441,17 @@ var FontShape = (function FontShape() { for (var name in obj) { this[name] = obj[name]; } + + var name = this.loadedName; + var bold = this.black ? (this.bold ? 'bolder' : 'bold') : + (this.bold ? 'bold' : 'normal'); + + var italic = this.italic ? 'italic' : 'normal'; + var serif = this.serif ? 'serif' : 'sans-serif'; + var typeface = '"' + name + '", ' + serif; + + this.$name1 = italic + ' ' + bold + ' '; + this.$name2 = 'px ' + typeface; }; function int16(bytes) { @@ -448,6 +459,10 @@ var FontShape = (function FontShape() { }; constructor.prototype = { + getRule: function fonts_getRule(size) { + return this.$name1 + size + this.$name2; + }, + charsToUnicode: function fonts_chars2Unicode(chars) { var charsCache = this.charsCache; var str; diff --git a/pdf.js b/pdf.js index 6068785ba3fd9..23dd910aaac48 100644 --- a/pdf.js +++ b/pdf.js @@ -4824,8 +4824,6 @@ function ScratchCanvas(width, height) { } var CanvasGraphics = (function() { - var kScalePrecision = 50; - var kRasterizerMin = 14; var kExecutionTime = 50; var kExecutionTimeCheck = 500; @@ -5127,14 +5125,7 @@ var CanvasGraphics = (function() { if (this.ctx.$setFont) { this.ctx.$setFont(name, size); } else { - var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') : - (fontObj.bold ? 'bold' : 'normal'); - - var italic = fontObj.italic ? 'italic' : 'normal'; - var serif = fontObj.serif ? 'serif' : 'sans-serif'; - var typeface = '"' + name + '", ' + serif; - var rule = italic + ' ' + bold + ' ' + size + 'px ' + typeface; - this.ctx.font = rule; + this.ctx.font = fontObj.getRule(size); } }, setTextRenderingMode: function(mode) { From 9d2806ec87614680dc3534f125ac58da7fabb940 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 14 Sep 2011 07:55:15 -0700 Subject: [PATCH 062/123] Use canvas measureText to detect font loading --- fonts.js | 227 ++++++++++++++++++++++++++----------------------------- 1 file changed, 109 insertions(+), 118 deletions(-) diff --git a/fonts.js b/fonts.js index d7a66fd001d3c..6aad019d3f720 100755 --- a/fonts.js +++ b/fonts.js @@ -158,136 +158,127 @@ if (!isWorker) { })(); } +/** + * The FontLoader binds a fontObj to the DOM and checks if it is loaded. + * At the point of writing (11/9/14) there is no DOM event to detect loading + * of fonts. Therefore, we measure the font using a canvas before the + * font is attached to the DOM and later on test if the width changed. + * To ensure there is a change in the font size, two fallback fonts are used. + * The font used might look like this: + * ctx.font = "normal normmal 'p0_font_0', 'Arial' + * + * As long as the font 'p0_font_0' isn't loaded, the font 'Arial' is used. A + * second measurement is done against the font 'Courier': + * ctx.font = "normal normmal 'p0_font_0', 'Courier' + * + * The font sizes of Arial and Courier are quite different which ensures there + * gone be a change nomatter what the font looks like (e.g. you could end up + * with a font that looks like Arial which means you don't see any change in + * size, but as Courier is checked as well, there will be difference in this + * font). + * + * !!! The test string used for measurements is really important. Some fonts + * don't have definitions for characters like "a" or "b", but only for some + * unicode characters. Therefore, the test string have to be build using the + * encoding of the fontObj. + */ var FontLoader = { - fonts: {}, - fontsLoading: false, - waitingFontObjs: [], - waitingFontIds: [], - - bind: function(objId, fontObj) { - this.waitingFontObjs.push(fontObj); - this.waitingFontIds.push(objId); + scratchCtx: null, + + /** + * Create the canvas used for measuring the width of text. + */ + setup: function() { + var canvas = document.createElement("canvas"); + var ctx = canvas.getContext("2d"); + this.ctx = ctx; + }, + + /** + * Measures the width of some string using a fontObj and some different + * fallback fonts. + */ + measure: function(fontObj, str) { + var ctx = this.ctx; + + // THe fonts used as fallback. + var fallbacks = [ "Arial", "Courier" ]; - if (!this.fontsLoading) { - this.executeWaiting(); + var widths = []; + for (var n = 0; n < fallbacks.length; n++) { + // Choose a large font size as there are no sub-pixel returned from + // measureText. + var font = fontObj.getRule(420, fallbacks[n]); + ctx.font = font; + + widths.push(ctx.measureText(str).width); } + return widths; + }, + + /** + * Attaches a fontObj to the DOM and calls Objects.resolve(objId) once + * the font is loaded. + */ + bind: function(objId, fontObj) { + var encoding = fontObj.encoding; + var testStr = ""; + for (var enc in encoding) { + testStr += String.fromCharCode(encoding[enc]); + if (testStr.length == 10) { + break; + } + } + + var before = this.measure(fontObj, testStr); + this.bindDOM(fontObj); + + var check = function() { + var measure = this.measure(fontObj, testStr); + + for (var i = 0; i < measure.length; i++) { + if (measure[i] !== before[i]) { + Objects.resolve(objId); + return; + } + } + + setTimeout(check, 0); + }.bind(this); + + // Start checking if font is loaded. + check(); }, + /** + * Attach the fontObj to the DOM. + */ bindDOM: function font_bindDom(fontObj) { - var fontName = fontObj.loadedName; + // The browser isn't loading a font until it's used on the page. Attaching + // a hidden div that uses the font 'tells' the browser to load the font. + var div = document.createElement('div'); + div.setAttribute('style', + 'visibility: hidden;' + + 'width: 10px; height: 10px;' + + 'position: absolute; top: 0px; left: 0px;' + + 'font-family: ' + fontObj.loadedName); + div.innerHTML = "Hi"; + document.body.appendChild(div); + // Add the font-face rule to the document + var fontName = fontObj.loadedName; var url = ('url(data:' + fontObj.mimetype + ';base64,' + window.btoa(fontObj.str) + ');'); var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}'; var styleSheet = document.styleSheets[0]; styleSheet.insertRule(rule, styleSheet.cssRules.length); return rule; - }, - - executeWaiting: function() { - var rules = []; - var names = []; - var objIds = this.waitingFontIds; - - for (var i = 0; i < this.waitingFontObjs.length; i++) { - var fontObj = this.waitingFontObjs[i]; - var rule = this.bindDOM(fontObj); - names.push(fontObj.loadedName); - rules.push(rule); - } - - this.prepareFontLoadEvent(rules, names, objIds); - this.waitingFontIds = []; - this.waitingFontObjs = []; - }, - - fontLoadEvent: function(objIds) { - for (var i = 0; i < objIds.length; i++) { - var objId = objIds[i]; - Objects.resolve(objId); - } - - this.fontsLoading = false; - - if (this.waitingFontIds.length != 0) { - this.executeWaiting(); - } - }, - - // Set things up so that at least one pdfjsFontLoad event is - // dispatched when all the @font-face |rules| for |names| have been - // loaded in a subdocument. It's expected that the load of |rules| - // has already started in this (outer) document, so that they should - // be ordered before the load in the subdocument. - prepareFontLoadEvent: function(rules, names, objIds) { - this.fontsLoading = true; - /** Hack begin */ - // There's no event when a font has finished downloading so the - // following code is a dirty hack to 'guess' when a font is - // ready. This code will be obsoleted by Mozilla bug 471915. - // - // The only reliable way to know if a font is loaded in Gecko - // (at the moment) is document.onload in a document with - // a @font-face rule defined in a "static" stylesheet. We use a - // subdocument in an