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);