Skip to content

Commit a9e2115

Browse files
committed
Merge pull request mozilla#140 from cgjones/font-loading
Improve font loading check by using onload in subdocument
2 parents 0eb33ec + 7b883a8 commit a9e2115

File tree

4 files changed

+133
-105
lines changed

4 files changed

+133
-105
lines changed

fonts.js

Lines changed: 117 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -124,30 +124,129 @@ var Fonts = (function () {
124124
})();
125125

126126
var FontLoader = {
127-
bind: function(fonts) {
128-
var ready = true;
127+
bind: function(fonts, callback) {
128+
function checkFontsLoaded() {
129+
for (var i = 0; i < fonts.length; i++) {
130+
var font = fonts[i];
131+
if (Fonts.lookup(font.name).loading) {
132+
return false;
133+
}
134+
}
135+
136+
document.documentElement.removeEventListener(
137+
"pdfjsFontLoad", checkFontsLoaded, false);
138+
139+
callback();
140+
return true;
141+
}
129142

143+
var rules = [ ], names = [ ];
130144
for (var i = 0; i < fonts.length; i++) {
131145
var font = fonts[i];
132-
if (Fonts.lookup(font.name)) {
133-
ready = ready && !Fonts.lookup(font.name).loading;
134-
continue;
146+
if (!Fonts.lookup(font.name)) {
147+
var obj = new Font(font.name, font.file, font.properties);
148+
149+
var str = "";
150+
var data = Fonts.lookup(font.name).data;
151+
var length = data.length;
152+
for (var j = 0; j < length; j++)
153+
str += String.fromCharCode(data[j]);
154+
155+
var rule = isWorker ? obj.bindWorker(str) : obj.bindDOM(str);
156+
if (rule) {
157+
rules.push(rule);
158+
names.push(font.name);
159+
}
135160
}
161+
}
136162

137-
ready = false;
163+
if (!isWorker && rules.length) {
164+
FontLoader.prepareFontLoadEvent(rules, names);
165+
}
138166

139-
var obj = new Font(font.name, font.file, font.properties);
167+
if (!checkFontsLoaded()) {
168+
document.documentElement.addEventListener(
169+
"pdfjsFontLoad", checkFontsLoaded, false);
170+
}
140171

141-
var str = "";
142-
var data = Fonts.lookup(font.name).data;
143-
var length = data.length;
144-
for (var j = 0; j < length; j++)
145-
str += String.fromCharCode(data[j]);
172+
return;
173+
},
174+
// Set things up so that at least one pdfjsFontLoad event is
175+
// dispatched when all the @font-face |rules| for |names| have been
176+
// loaded in a subdocument. It's expected that the load of |rules|
177+
// has already started in this (outer) document, so that they should
178+
// be ordered before the load in the subdocument.
179+
prepareFontLoadEvent: function(rules, names) {
180+
/** Hack begin */
181+
// There's no event when a font has finished downloading so the
182+
// following code is a dirty hack to 'guess' when a font is
183+
// ready. This code will be obsoleted by Mozilla bug 471915.
184+
//
185+
// The only reliable way to know if a font is loaded in Gecko
186+
// (at the moment) is document.onload in a document with
187+
// a @font-face rule defined in a "static" stylesheet. We use a
188+
// subdocument in an <iframe>, set up properly, to know when
189+
// our @font-face rule was loaded. However, the subdocument and
190+
// outer document can't share CSS rules, so the inner document
191+
// is only part of the puzzle. The second piece is an invisible
192+
// div created in order to force loading of the @font-face in
193+
// the *outer* document. (The font still needs to be loaded for
194+
// its metrics, for reflow). We create the div first for the
195+
// outer document, then create the iframe. Unless something
196+
// goes really wonkily, we expect the @font-face for the outer
197+
// document to be processed before the inner. That's still
198+
// fragile, but seems to work in practice.
146199

147-
isWorker ? obj.bindWorker(str) : obj.bindDOM(str);
148-
}
200+
var div = document.createElement("div");
201+
div.setAttribute("style",
202+
'visibility: hidden;'+
203+
'width: 10px; height: 10px;'+
204+
'position: absolute; top: 0px; left: 0px;');
205+
var html = '';
206+
for (var i = 0; i < names.length; ++i) {
207+
html += '<span style="font-family:'+ names[i] +'">Hi</span>';
208+
}
209+
div.innerHTML = html;
210+
document.body.appendChild(div);
149211

150-
return ready;
212+
// XXX we should have a time-out here too, and maybe fire
213+
// pdfjsFontLoadFailed?
214+
var src = '<!DOCTYPE HTML><html><head>';
215+
src += '<style type="text/css">';
216+
for (var i = 0; i < rules.length; ++i) {
217+
src += rules[i];
218+
}
219+
src += '</style>'
220+
src += '<script type="application/javascript">'
221+
var fontNamesArray = '';
222+
for (var i = 0; i < names.length; ++i) {
223+
fontNamesArray += '"'+ names[i] + '", ';
224+
}
225+
src += ' var fontNames=['+ fontNamesArray +'];\n';
226+
src += ' window.onload = function () {\n'
227+
src += ' var Fonts = top.document.defaultView.Fonts;\n';
228+
src += ' for (var i = 0; i < fontNames.length; ++i) {\n';
229+
src += ' var font = Fonts.lookup(fontNames[i]);\n';
230+
src += ' font.loading = false;\n';
231+
src += ' }\n';
232+
src += ' var doc = top.document;\n';
233+
src += ' var evt = doc.createEvent("Events");\n';
234+
src += ' evt.initEvent("pdfjsFontLoad", true, false);\n'
235+
src += ' doc.documentElement.dispatchEvent(evt);\n';
236+
src += ' }';
237+
src += '</script></head><body>';
238+
for (var i = 0; i < names.length; ++i) {
239+
src += '<p style="font-family:\''+ fontName +'\'">Hi</p>';
240+
}
241+
src += '</body></html>';
242+
var frame = document.createElement("iframe");
243+
frame.src = 'data:text/html,'+ src;
244+
frame.setAttribute("style",
245+
'visibility: hidden;'+
246+
'width: 10px; height: 10px;'+
247+
'position: absolute; top: 0px; left: 0px;');
248+
document.body.appendChild(frame);
249+
/** Hack end */
151250
}
152251
};
153252

@@ -1004,60 +1103,16 @@ var Font = (function () {
10041103
});
10051104
},
10061105

1007-
bindDOM: function font_bindDom(data, callback) {
1106+
bindDOM: function font_bindDom(data) {
10081107
var fontName = this.name;
10091108

1010-
// Just adding the font-face to the DOM doesn't make it load. It
1011-
// seems it's loaded once Gecko notices it's used. Therefore,
1012-
// add a div on the page using the loaded font.
1013-
var div = document.createElement("div");
1014-
var style = 'font-family:"' + name +
1015-
'";position: absolute;top:-99999;left:-99999;z-index:-99999';
1016-
div.setAttribute("style", style);
1017-
document.body.appendChild(div);
1018-
1019-
/** Hack begin */
1020-
// Actually there is not event when a font has finished downloading so
1021-
// the following code are a dirty hack to 'guess' when a font is ready
1022-
// This code could go away when bug 471915 has landed
1023-
var canvas = document.createElement("canvas");
1024-
var ctx = canvas.getContext("2d");
1025-
ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
1026-
var testString = " ";
1027-
1028-
// Periodicaly check for the width of the testString, it will be
1029-
// different once the real font has loaded
1030-
var textWidth = ctx.measureText(testString).width;
1031-
1032-
var interval = window.setInterval(function canvasInterval(self) {
1033-
this.start = this.start || Date.now();
1034-
ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
1035-
1036-
// For some reasons the font has not loaded, so mark it loaded for the
1037-
// page to proceed but cry
1038-
if (textWidth == ctx.measureText(testString).width) {
1039-
if ((Date.now() - this.start) < kMaxWaitForFontFace) {
1040-
return;
1041-
} else {
1042-
warn("Is " + fontName + " loaded?");
1043-
}
1044-
}
1045-
1046-
window.clearInterval(interval);
1047-
Fonts.lookup(fontName).loading = false;
1048-
this.start = 0;
1049-
if (callback) {
1050-
callback();
1051-
}
1052-
}, 30, this);
1053-
1054-
/** Hack end */
1055-
10561109
// Add the @font-face rule to the document
10571110
var url = "url(data:" + this.mimetype + ";base64," + window.btoa(data) + ");";
10581111
var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}";
10591112
var styleSheet = document.styleSheets[0];
10601113
styleSheet.insertRule(rule, styleSheet.length);
1114+
1115+
return rule;
10611116
}
10621117
};
10631118

multi_page_viewer.js

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,7 @@ var PDFViewer = {
130130
var fonts = [];
131131
page.compile(gfx, fonts);
132132

133-
var loadFont = function() {
134-
if (!FontLoader.bind(fonts)) {
135-
pageTimeout = window.setTimeout(loadFont, 10);
136-
return;
137-
}
138-
page.display(gfx);
139-
}
140-
loadFont();
133+
FontLoader.bind(fonts, function() { page.display(gfx); });
141134
}
142135
},
143136

@@ -197,17 +190,10 @@ var PDFViewer = {
197190
var fonts = [];
198191
page.compile(gfx, fonts);
199192

200-
var loadFont = function() {
201-
if (!FontLoader.bind(fonts)) {
202-
pageTimeout = window.setTimeout(loadFont, 10);
203-
return;
204-
}
205-
page.display(gfx);
206-
}
207-
loadFont();
193+
FontLoader.bind(fonts, function() { page.display(gfx); });
208194
}
209195
},
210-
196+
211197
changeScale: function(num) {
212198
while (PDFViewer.element.hasChildNodes()) {
213199
PDFViewer.element.removeChild(PDFViewer.element.firstChild);

test/test_slave.html

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -115,25 +115,18 @@
115115
failure = 'page setup: '+ e.toString();
116116
}
117117

118-
var fontLoaderTimer = null;
119-
function checkFontsLoaded() {
118+
if (!failure) {
120119
try {
121-
if (!FontLoader.bind(fonts)) {
122-
fontLoaderTimer = window.setTimeout(checkFontsLoaded, 10);
123-
return;
124-
}
120+
FontLoader.bind(fonts, function() { snapshotCurrentPage(gfx); });
125121
} catch(e) {
126122
failure = 'fonts: '+ e.toString();
127123
}
128-
snapshotCurrentPage(gfx);
129124
}
130125

131126
if (failure) {
132-
// Skip font loading if there was a failure, since the fonts might
133-
// be in an inconsistent state.
127+
// Skip right to snapshotting if there was a failure, since the
128+
// fonts might be in an inconsistent state.
134129
snapshotCurrentPage(gfx);
135-
} else {
136-
checkFontsLoaded();
137130
}
138131
}
139132

viewer.js

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
"use strict";
55

6-
var pdfDocument, canvas, pageScale, pageDisplay, pageNum, numPages, pageTimeout;
6+
var pdfDocument, canvas, pageScale, pageDisplay, pageNum, numPages;
77
function load(userInput) {
88
canvas = document.getElementById("canvas");
99
canvas.mozOpaque = true;
@@ -53,8 +53,6 @@ function gotoPage(num) {
5353
}
5454

5555
function displayPage(num) {
56-
window.clearTimeout(pageTimeout);
57-
5856
document.getElementById("pageNumber").value = num;
5957

6058
var t0 = Date.now();
@@ -82,22 +80,18 @@ function displayPage(num) {
8280
page.compile(gfx, fonts);
8381
var t2 = Date.now();
8482

85-
function loadFont() {
86-
if (!FontLoader.bind(fonts)) {
87-
pageTimeout = window.setTimeout(loadFont, 10);
88-
return;
89-
}
83+
function displayPage() {
84+
var t3 = Date.now();
9085

91-
var t3 = Date.now();
86+
page.display(gfx);
9287

93-
page.display(gfx);
88+
var t4 = Date.now();
9489

95-
var t4 = Date.now();
90+
var infoDisplay = document.getElementById("info");
91+
infoDisplay.innerHTML = "Time to load/compile/fonts/render: "+ (t1 - t0) + "/" + (t2 - t1) + "/" + (t3 - t2) + "/" + (t4 - t3) + " ms";
92+
}
9693

97-
var infoDisplay = document.getElementById("info");
98-
infoDisplay.innerHTML = "Time to load/compile/fonts/render: "+ (t1 - t0) + "/" + (t2 - t1) + "/" + (t3 - t2) + "/" + (t4 - t3) + " ms";
99-
};
100-
loadFont();
94+
FontLoader.bind(fonts, displayPage);
10195
}
10296

10397
function nextPage() {

0 commit comments

Comments
 (0)