From dd9aea21e9c306c3899679eac851b20e68b1c574 Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Fri, 9 Sep 2011 16:15:51 -0700 Subject: [PATCH] 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 0ca75e88a..f9af8d068 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 = '<!DOCTYPE HTML><html><head>'; @@ -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 3f0594939..eb8003072 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 558bba90b..440b94817 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 ab8079564..fe6c6d8ef 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);