Trying to implement progressive font rendering. Works on FF, but Chrome doesn't catchup the fonts

This commit is contained in:
Julian Viereck 2011-09-09 16:15:51 -07:00
parent ac11f30ae9
commit dd9aea21e9
4 changed files with 115 additions and 98 deletions

View File

@ -159,24 +159,12 @@ if (!isWorker) {
} }
var FontLoader = { var FontLoader = {
listeningForFontLoad: false, fontLoadData: {},
fonts: {},
bind: function(fonts, callback) { bind: function(fonts, callback) {
function checkFontsLoaded() { console.log("requesting fonts", fonts[0].properties.loadedName, fonts[0].name);
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;
}
var rules = [], names = [], objs = []; var rules = [], names = [], objs = [];
for (var i = 0; i < fonts.length; i++) { for (var i = 0; i < fonts.length; i++) {
@ -196,28 +184,33 @@ var FontLoader = {
if (rule) { if (rule) {
rules.push(rule); rules.push(rule);
names.push(obj.loadedName); names.push(obj.loadedName);
this.fonts[obj.loadedName] = obj;
this.fontLoadData[obj.loadedName] = obj;
} }
} }
} }
this.listeningForFontLoad = false; if (rules.length) {
if (!isWorker && rules.length) { this.fontsLoading += rules.length;
FontLoader.prepareFontLoadEvent(rules, names, objs); FontLoader.prepareFontLoadEvent(rules, names);
}
if (!checkFontsLoaded()) {
document.documentElement.addEventListener(
'pdfjsFontLoad', checkFontsLoaded, false);
} }
return objs; 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 // Set things up so that at least one pdfjsFontLoad event is
// dispatched when all the @font-face |rules| for |names| have been // dispatched when all the @font-face |rules| for |names| have been
// loaded in a subdocument. It's expected that the load of |rules| // loaded in a subdocument. It's expected that the load of |rules|
// has already started in this (outer) document, so that they should // has already started in this (outer) document, so that they should
// be ordered before the load in the subdocument. // be ordered before the load in the subdocument.
prepareFontLoadEvent: function(rules, names, objs) { prepareFontLoadEvent: function(rules, names, callback) {
/** Hack begin */ /** Hack begin */
// There's no event when a font has finished downloading so the // There's no event when a font has finished downloading so the
// following code is a dirty hack to 'guess' when a font is // following code is a dirty hack to 'guess' when a font is
@ -253,23 +246,6 @@ var FontLoader = {
div.innerHTML = html; div.innerHTML = html;
document.body.appendChild(div); 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 // XXX we should have a time-out here too, and maybe fire
// pdfjsFontLoadFailed? // pdfjsFontLoadFailed?
var src = '<!DOCTYPE HTML><html><head>'; 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 = [ var UnicodeRanges = [
{ 'begin': 0x0000, 'end': 0x007F }, // Basic Latin { 'begin': 0x0000, 'end': 0x007F }, // Basic Latin
{ 'begin': 0x0080, 'end': 0x00FF }, // Latin-1 Supplement { 'begin': 0x0080, 'end': 0x00FF }, // Latin-1 Supplement

48
pdf.js
View File

@ -3353,16 +3353,17 @@ var Page = (function() {
} catch (e) { } catch (e) {
exc = e.toString(); exc = e.toString();
continuation(exc); continuation(exc);
throw e;
} }
}); });
}; };
this.ensureFonts(fonts, function() { // this.ensureFonts(fonts, function() {
displayContinuation(); displayContinuation();
}); // });
}, },
getIRQueue: function(handler, fonts) { getIRQueue: function(handler) {
if (this.IRQueue) { if (this.IRQueue) {
// content was compiled // content was compiled
return this.IRQueue; return this.IRQueue;
@ -3381,7 +3382,7 @@ var Page = (function() {
var pe = this.pe = new PartialEvaluator(); var pe = this.pe = new PartialEvaluator();
var IRQueue = {}; 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) { ensureFonts: function(fonts, callback) {
@ -4161,7 +4162,13 @@ var PartialEvaluator = (function() {
}; };
constructor.prototype = { 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) { function buildPaintImageXObject(image, inline) {
var dict = image.dict; var dict = image.dict;
var w = dict.get('Width', 'W'); var w = dict.get('Width', 'W');
@ -4172,9 +4179,8 @@ var PartialEvaluator = (function() {
handler.send("obj", [objId, "JpegStream", image.getIR()]); handler.send("obj", [objId, "JpegStream", image.getIR()]);
// Add the dependency on the image object. // Add the dependency on the image object.
fnArray.push("dependency"); insertDependency([objId]);
argsArray.push([ objId ]);
// The normal fn. // The normal fn.
fn = 'paintJpegXObject'; fn = 'paintJpegXObject';
args = [ objId, w, h ]; args = [ objId, w, h ];
@ -4265,7 +4271,7 @@ var PartialEvaluator = (function() {
// TODO: Add dependency here. // TODO: Add dependency here.
// Create an IR of the pattern code. // Create an IR of the pattern code.
var codeIR = this.getIRQueue(pattern, xref, var codeIR = this.getIRQueue(pattern, xref,
dict.get('Resources'), {}, handler, fonts, uniquePrefix); dict.get('Resources'), {}, handler, uniquePrefix);
args = TilingPattern.getIR(codeIR, dict, args); 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 adds the IRQueue of the xObj to the current queue.
this.getIRQueue(xobj, xref, xobj.dict.get('Resources'), queue, this.getIRQueue(xobj, xref, xobj.dict.get('Resources'), queue,
handler, fonts, uniquePrefix); handler, uniquePrefix);
fn = "paintFormXObjectEnd"; fn = "paintFormXObjectEnd";
@ -4324,14 +4330,24 @@ var PartialEvaluator = (function() {
assertWellFormed(IsDict(font)); assertWellFormed(IsDict(font));
if (!font.translated) { if (!font.translated) {
font.translated = this.translateFont(font, xref, resources); 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 // keep track of each font we translated so the caller can
// load them asynchronously before calling display on a page // load them asynchronously before calling display on a page
fonts.push(font.translated);
var loadedName = uniquePrefix + "font_" + (FontLoadedCounter++); var loadedName = uniquePrefix + "font_" + (FontLoadedCounter++);
font.translated.properties.loadedName = loadedName; font.translated.properties.loadedName = loadedName;
FontsMap[loadedName] = font; 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; args[0].name = font.translated.properties.loadedName;
@ -4870,13 +4886,14 @@ var CanvasGraphics = (function() {
var depObjId = deps[n]; var depObjId = deps[n];
var promise; var promise;
if (!Objects[depObjId]) { if (!Objects[depObjId]) {
promise = Objects[depObjId] = new Promise(); promise = Objects[depObjId] = new Promise(depObjId);
} else { } else {
promise = Objects[depObjId]; promise = Objects[depObjId];
} }
// If the promise isn't resolved yet, add the continueCallback // If the promise isn't resolved yet, add the continueCallback
// to the promise and bail out. // to the promise and bail out.
if (!promise.isResolved) { if (!promise.isResolved) {
console.log("depending on obj", depObjId);
promise.then(continueCallback); promise.then(continueCallback);
return i; return i;
} }
@ -5096,13 +5113,14 @@ var CanvasGraphics = (function() {
setFont: function(fontRef, size) { setFont: function(fontRef, size) {
// Lookup the fontObj using fontRef only. // Lookup the fontObj using fontRef only.
var fontRefName = fontRef.name; var fontRefName = fontRef.name;
var fontObj = FontsMap[fontRefName].fontObj; var fontObj = Objects.get(fontRefName);
if (!fontObj) { if (!fontObj) {
throw "Can't find font for " + fontRefName; throw "Can't find font for " + fontRefName;
} }
var name = fontObj.loadedName; var name = fontObj.loadedName;
console.log("setFont", name);
if (!name) { if (!name) {
// TODO: fontDescriptor is not available, fallback to default font // TODO: fontDescriptor is not available, fallback to default font
name = 'sans-serif'; name = 'sans-serif';

View File

@ -62,16 +62,25 @@ var WorkerPage = (function() {
var Objects = { var Objects = {
resolve: function(objId, data) { resolve: function(objId, data) {
// In case there is a promise already on this object, just resolve it. // 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); Objects[objId].resolve(data);
} else { } 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() { 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 you build a promise and pass in some data it's already resolved.
if (data != null) { if (data != null) {
this.isResolved = true; this.isResolved = true;
@ -84,6 +93,8 @@ var Promise = (function() {
Promise.prototype = { Promise.prototype = {
resolve: function(data) { resolve: function(data) {
console.log("resolve", this.name);
if (this.isResolved) { if (this.isResolved) {
throw "A Promise can be resolved only once"; throw "A Promise can be resolved only once";
} }
@ -137,29 +148,6 @@ var WorkerPDFDoc = (function() {
var pageNum = data.pageNum; var pageNum = data.pageNum;
var page = this.pageCache[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); page.startRenderingFromIRQueue(data.IRQueue, data.fonts);
}, this); }, this);
@ -173,6 +161,45 @@ var WorkerPDFDoc = (function() {
var IR = data[2]; var IR = data[2];
new JpegStreamIR(objId, IR); new JpegStreamIR(objId, IR);
break; 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: default:
throw "Got unkown object type " + objType; throw "Got unkown object type " + objType;
} }

View File

@ -19,25 +19,12 @@ var WorkerHandler = {
// The following code does quite the same as Page.prototype.startRendering, // 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. // but stops at one point and sends the result back to the main thread.
var gfx = new CanvasGraphics(null); var gfx = new CanvasGraphics(null);
var fonts = [];
var start = Date.now(); var start = Date.now();
// Pre compile the pdf page and fetch the fonts/images. // 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); 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 */) { if (false /* show used commands */) {
var cmdMap = {}; var cmdMap = {};
@ -59,7 +46,6 @@ var WorkerHandler = {
handler.send("page", { handler.send("page", {
pageNum: pageNum, pageNum: pageNum,
fonts: fontsMin,
IRQueue: IRQueue, IRQueue: IRQueue,
}); });
}, this); }, this);