From 6dcf9f42a5da22d21dae274132926be8f27d19eb Mon Sep 17 00:00:00 2001 From: Julian Viereck 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 += ''; 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 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