From 6dcf9f42a5da22d21dae274132926be8f27d19eb Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Fri, 9 Sep 2011 18:17:56 -0700
Subject: [PATCH] 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 fd467eeca..6fb99a4d2 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) {
+  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;
 
-    for (var i = 0; i < names.length; i++) {
-      var name = names[i];
-      Objects.resolve(name, this.fonts[name]);
-    }
-
-    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 += '</style>';
       src += '<script type="application/javascript">';
-      var fontNamesArray = '';
-      for (var i = 0; i < names.length; ++i) {
-        fontNamesArray += '"' + names[i] + '", ';
+      var objIdsArray = '';
+      for (var i = 0; i < objIds.length; ++i) {
+        objIdsArray += '"' + objIds[i] + '", ';
       }
-      src += '  var fontNames=[' + fontNamesArray + '];\n';
+      src += '  var objIds=[' + objIdsArray + '];\n';      
       src += '  window.onload = function () {\n';
-      src += '    setTimeout(function(){parent.postMessage(JSON.stringify(fontNames), "*")},0);\n';
+      src += '    setTimeout(function(){parent.postMessage(JSON.stringify(objIds), "*")},0);\n';
       src += '  }';
       src += '</script></head><body>';
-      for (var i = 0; i < names.length; ++i) {
-        src += '<p style="font-family:\'' + names[i] + '\'">Hi</p>';
-      }
+      src += '<p style="font-family:\'' + name + '\'">Hi</p>';
       src += '</body></html>';
       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 0273f7170..3d994dfdd 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 6f4a38da8..9c6bf40df 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