diff --git a/fonts.js b/fonts.js index 728bc5c68..381d37c09 100644 --- a/fonts.js +++ b/fonts.js @@ -64,8 +64,15 @@ var Fonts = { var unicode = encoding[charcode]; // Check if the glyph has already been converted - if (unicode instanceof Name) - unicode = encoding[unicode] = GlyphsUnicode[unicode.name]; + // if (unicode instanceof Name) + try { + if (!IsNum(unicode)) + // if ("name" in unicode) + unicode = encoding[unicode] = GlyphsUnicode[unicode.name]; + + } catch(e) { + console.log("FAIL"); + } // Handle surrogate pairs if (unicode > 0xFFFF) { @@ -165,6 +172,7 @@ var Font = (function () { warn("Font " + properties.type + " is not supported"); break; } + this.data = data; Fonts[name] = { data: data, diff --git a/pdf.js b/pdf.js index 3179f42ec..27ccc6c10 100644 --- a/pdf.js +++ b/pdf.js @@ -3819,6 +3819,9 @@ var CanvasGraphics = (function() { this.current.fontSize = size; this.ctx.font = this.current.fontSize +'px "' + fontName + '", Symbol'; + if (this.ctx.$setFont) { + this.ctx.$setFont(fontName); + } }, setTextRenderingMode: function(mode) { TODO("text rendering mode"); diff --git a/viewer_worker.html b/viewer_worker.html index 6443b22c1..fe3319d62 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -1,6 +1,9 @@ <html> <head> <title>Simple pdf.js page worker viewer</title> + <script type="text/javascript" src="fonts.js"></script> + <script type="text/javascript" src="glyphlist.js"></script> + <script type="text/javascript" src="pdf.js"></script> <script type="text/javascript" src="worker/client.js"></script> <script> diff --git a/worker/canvas.js b/worker/canvas.js index d6f5a0a25..bc96d8453 100644 --- a/worker/canvas.js +++ b/worker/canvas.js @@ -119,7 +119,8 @@ function CanvasProxy(width, height) { "$addCurrentX", "$saveCurrentX", "$restoreCurrentX", - "$showText" + "$showText", + "$setFont" ]; function buildFuncCall(name) { diff --git a/worker/client.js b/worker/client.js index 5d0cae05a..78568252f 100644 --- a/worker/client.js +++ b/worker/client.js @@ -18,12 +18,115 @@ if (typeof console.time == "undefined") { }; } +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); +} + +FontWorker.prototype = { + actionHandler: { + "fonts": function(data) { + // console.log("got processed fonts from worker", Object.keys(data)); + for (name in data) { + var base64 = window.btoa(data[name]); + + // Add the @font-face rule to the document + var url = "url(data:font/opentype;base64," + base64 + ");"; + var rule = "@font-face { font-family:'" + name + "';src:" + url + "}"; + var styleSheet = document.styleSheets[0]; + styleSheet.insertRule(rule, styleSheet.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:"' + name + + '";position: absolute;top:-99999;left:-99999;z-index:-99999'; + div.setAttribute("style", style); + document.body.appendChild(div); + this.fontsWaiting --; + } + + // This timeout is necessary right now to make sure the fonts are really + // loaded at the point the callbacks are called. + setTimeout(function() { + // 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](); + } + } + }.bind(this), 100); + } + }, + + ensureFonts: function(data, callback) { + var font; + var notLoaded = []; + for (var i = 0; i < data.length; i++) { + font = data[i]; + if (Fonts[font.name]) { + continue; + } + + // Store only the data on Fonts that is needed later on, such that we + // hold track on as lease memory as possible. + Fonts[font.name] = { + properties: { + charset: font.properties.charset + }, + cache: Object.create(null) + }; + + // Mark this font to be handled later. + notLoaded.push(font); + // Increate the number of fonts to wait for. + this.fontsWaiting++; + } + + // 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) { + // 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; @@ -56,6 +159,7 @@ function WorkerPDFDoc(canvas) { }, "$showText": function(y, text) { + text = Fonts.charsToUnicode(text); this.translate(currentX, -1 * y); this.fillText(text, 0, 0); currentX += this.measureText(text).width; @@ -136,6 +240,10 @@ function WorkerPDFDoc(canvas) { throw "Pattern not found"; } this.strokeStyle = pattern; + }, + + "$setFont": function(name) { + Fonts.active = name; } } @@ -187,6 +295,17 @@ function WorkerPDFDoc(canvas) { div.setAttribute("style", style); document.body.appendChild(div); }, + + "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](); + } + }.bind(this)); + }, "jpeg_stream": function(data) { var img = new Image(); @@ -207,9 +326,7 @@ function WorkerPDFDoc(canvas) { canvasList[id] = newCanvas; } - // There might be fonts that need to get loaded. Shedule the - // rendering at the end of the event queue ensures this. - setTimeout(function() { + var renderData = function() { if (id == 0) { console.time("canvas rendering"); var ctx = this.ctx; @@ -220,11 +337,18 @@ function WorkerPDFDoc(canvas) { } renderProxyCanvas(canvasList[id], cmdQueue); if (id == 0) console.timeEnd("canvas rendering") - }, 0, this); + }.bind(this); + + if (this.waitingForFonts) { + console.log("want to render, but not all fonts are there", id); + this.waitingForFontsCallback.push(renderData); + } else { + renderData(); + } } } - // List to the WebWorker for data and call actionHandler on it. + // 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) { @@ -232,7 +356,7 @@ function WorkerPDFDoc(canvas) { } else { throw "Unkown action from worker: " + data.action; } - } + }.bind(this) } WorkerPDFDoc.prototype.open = function(url, callback) { diff --git a/worker/console.js b/worker/console.js new file mode 100644 index 000000000..e544db7a2 --- /dev/null +++ b/worker/console.js @@ -0,0 +1,27 @@ +/* -*- 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); + } +} diff --git a/worker/font.js b/worker/font.js new file mode 100644 index 000000000..a8ec5ebd4 --- /dev/null +++ b/worker/font.js @@ -0,0 +1,65 @@ +/* -*- 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; + } +} \ No newline at end of file diff --git a/worker/pdf.js b/worker/pdf.js index c4d16d8d0..d9a7b5319 100644 --- a/worker/pdf.js +++ b/worker/pdf.js @@ -27,6 +27,7 @@ var console = { } // +importScripts("console.js") importScripts("canvas.js"); importScripts("../pdf.js"); importScripts("../fonts.js"); @@ -65,21 +66,14 @@ onmessage = function(event) { page.compile(gfx, fonts); console.timeEnd("compile"); + // Send fonts to the main thread. console.time("fonts"); - // Inspect fonts and translate the missing one. - var count = fonts.length; - for (var i = 0; i < count; i++) { - var font = fonts[i]; - if (Fonts[font.name]) { - fontsReady = fontsReady && !Fonts[font.name].loading; - continue; - } - - // This "builds" the font and sents it over to the main thread. - new Font(font.name, font.file, font.properties); - } + postMessage({ + action: "fonts", + data: fonts + }); console.timeEnd("fonts"); - + console.time("display"); page.display(gfx); canvas.flush();