First pass on FontWorker

This commit is contained in:
Julian Viereck 2011-06-26 20:55:27 +02:00
parent c712712e67
commit b00df76044
8 changed files with 247 additions and 22 deletions

View File

@ -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,

3
pdf.js
View File

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

View File

@ -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>

View File

@ -119,7 +119,8 @@ function CanvasProxy(width, height) {
"$addCurrentX",
"$saveCurrentX",
"$restoreCurrentX",
"$showText"
"$showText",
"$setFont"
];
function buildFuncCall(name) {

View File

@ -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) {

27
worker/console.js Normal file
View File

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

65
worker/font.js Normal file
View File

@ -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;
}
}

View File

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