Replace the BaseFontLoader
classes with one FontLoader
class
By having just *one* class, and using pre-processor blocks directly in the relevant methods, we reduce the size of this code in the *built* `pdf.js` file. Originally, when the `BaseFontLoader` abstraction was added in PR 9982, the idea was probably that additional build-targets would get their own implementations. Given that this hasn't happened in the four years since that landed, it doesn't seem meaningful to keep it around.
This commit is contained in:
parent
9a03d36af4
commit
ad83fbe2a1
@ -19,22 +19,17 @@ import {
|
|||||||
FeatureTest,
|
FeatureTest,
|
||||||
shadow,
|
shadow,
|
||||||
string32,
|
string32,
|
||||||
unreachable,
|
|
||||||
UNSUPPORTED_FEATURES,
|
UNSUPPORTED_FEATURES,
|
||||||
warn,
|
warn,
|
||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
|
|
||||||
class BaseFontLoader {
|
class FontLoader {
|
||||||
constructor({
|
constructor({
|
||||||
docId,
|
docId,
|
||||||
onUnsupportedFeature,
|
onUnsupportedFeature,
|
||||||
ownerDocument = globalThis.document,
|
ownerDocument = globalThis.document,
|
||||||
// For testing only.
|
styleElement = null, // For testing only.
|
||||||
styleElement = null,
|
|
||||||
}) {
|
}) {
|
||||||
if (this.constructor === BaseFontLoader) {
|
|
||||||
unreachable("Cannot initialize BaseFontLoader.");
|
|
||||||
}
|
|
||||||
this.docId = docId;
|
this.docId = docId;
|
||||||
this._onUnsupportedFeature = onUnsupportedFeature;
|
this._onUnsupportedFeature = onUnsupportedFeature;
|
||||||
this._document = ownerDocument;
|
this._document = ownerDocument;
|
||||||
@ -44,6 +39,11 @@ class BaseFontLoader {
|
|||||||
typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || TESTING")
|
typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || TESTING")
|
||||||
? styleElement
|
? styleElement
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
|
||||||
|
this.loadingRequests = [];
|
||||||
|
this.loadTestFontId = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addNativeFontFace(nativeFontFace) {
|
addNativeFontFace(nativeFontFace) {
|
||||||
@ -120,10 +120,6 @@ class BaseFontLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_queueLoadingCallback(callback) {
|
|
||||||
unreachable("Abstract method `_queueLoadingCallback`.");
|
|
||||||
}
|
|
||||||
|
|
||||||
get isFontLoadingAPISupported() {
|
get isFontLoadingAPISupported() {
|
||||||
const hasFonts = !!this._document?.fonts;
|
const hasFonts = !!this._document?.fonts;
|
||||||
if (
|
if (
|
||||||
@ -139,220 +135,198 @@ class BaseFontLoader {
|
|||||||
return shadow(this, "isFontLoadingAPISupported", hasFonts);
|
return shadow(this, "isFontLoadingAPISupported", hasFonts);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line getter-return
|
|
||||||
get isSyncFontLoadingSupported() {
|
get isSyncFontLoadingSupported() {
|
||||||
unreachable("Abstract method `isSyncFontLoadingSupported`.");
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line getter-return
|
|
||||||
get _loadTestFont() {
|
|
||||||
unreachable("Abstract method `_loadTestFont`.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_prepareFontLoadEvent(rules, fontsToLoad, request) {
|
|
||||||
unreachable("Abstract method `_prepareFontLoadEvent`.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let FontLoader;
|
|
||||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
|
||||||
FontLoader = class MozcentralFontLoader extends BaseFontLoader {
|
|
||||||
get isSyncFontLoadingSupported() {
|
|
||||||
return shadow(this, "isSyncFontLoadingSupported", true);
|
return shadow(this, "isSyncFontLoadingSupported", true);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// PDFJSDev.test('CHROME || GENERIC')
|
|
||||||
|
|
||||||
FontLoader = class GenericFontLoader extends BaseFontLoader {
|
let supported = false;
|
||||||
constructor(params) {
|
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("CHROME")) {
|
||||||
super(params);
|
if (typeof navigator === "undefined") {
|
||||||
this.loadingRequests = [];
|
// Node.js - we can pretend that sync font loading is supported.
|
||||||
this.loadTestFontId = 0;
|
supported = true;
|
||||||
}
|
} else {
|
||||||
|
// User agent string sniffing is bad, but there is no reliable way to
|
||||||
get isSyncFontLoadingSupported() {
|
// tell if the font is fully loaded and ready to be used with canvas.
|
||||||
let supported = false;
|
const m = /Mozilla\/5.0.*?rv:(\d+).*? Gecko/.exec(navigator.userAgent);
|
||||||
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("CHROME")) {
|
if (m?.[1] >= 14) {
|
||||||
if (typeof navigator === "undefined") {
|
|
||||||
// Node.js - we can pretend that sync font loading is supported.
|
|
||||||
supported = true;
|
supported = true;
|
||||||
} else {
|
|
||||||
// User agent string sniffing is bad, but there is no reliable way to
|
|
||||||
// tell if the font is fully loaded and ready to be used with canvas.
|
|
||||||
const m = /Mozilla\/5.0.*?rv:(\d+).*? Gecko/.exec(
|
|
||||||
navigator.userAgent
|
|
||||||
);
|
|
||||||
if (m?.[1] >= 14) {
|
|
||||||
supported = true;
|
|
||||||
}
|
|
||||||
// TODO - other browsers...
|
|
||||||
}
|
}
|
||||||
|
// TODO - other browsers...
|
||||||
}
|
}
|
||||||
return shadow(this, "isSyncFontLoadingSupported", supported);
|
}
|
||||||
|
return shadow(this, "isSyncFontLoadingSupported", supported);
|
||||||
|
}
|
||||||
|
|
||||||
|
_queueLoadingCallback(callback) {
|
||||||
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||||
|
throw new Error("Not implemented: _queueLoadingCallback");
|
||||||
}
|
}
|
||||||
|
|
||||||
_queueLoadingCallback(callback) {
|
function completeRequest() {
|
||||||
function completeRequest() {
|
assert(!request.done, "completeRequest() cannot be called twice.");
|
||||||
assert(!request.done, "completeRequest() cannot be called twice.");
|
request.done = true;
|
||||||
request.done = true;
|
|
||||||
|
|
||||||
// Sending all completed requests in order of how they were queued.
|
// Sending all completed requests in order of how they were queued.
|
||||||
while (loadingRequests.length > 0 && loadingRequests[0].done) {
|
while (loadingRequests.length > 0 && loadingRequests[0].done) {
|
||||||
const otherRequest = loadingRequests.shift();
|
const otherRequest = loadingRequests.shift();
|
||||||
setTimeout(otherRequest.callback, 0);
|
setTimeout(otherRequest.callback, 0);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { loadingRequests } = this;
|
|
||||||
const request = {
|
|
||||||
done: false,
|
|
||||||
complete: completeRequest,
|
|
||||||
callback,
|
|
||||||
};
|
|
||||||
loadingRequests.push(request);
|
|
||||||
return request;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get _loadTestFont() {
|
const { loadingRequests } = this;
|
||||||
const getLoadTestFont = function () {
|
const request = {
|
||||||
// This is a CFF font with 1 glyph for '.' that fills its entire width
|
done: false,
|
||||||
// and height.
|
complete: completeRequest,
|
||||||
return atob(
|
callback,
|
||||||
"T1RUTwALAIAAAwAwQ0ZGIDHtZg4AAAOYAAAAgUZGVE1lkzZwAAAEHAAAABxHREVGABQA" +
|
};
|
||||||
"FQAABDgAAAAeT1MvMlYNYwkAAAEgAAAAYGNtYXABDQLUAAACNAAAAUJoZWFk/xVFDQAA" +
|
loadingRequests.push(request);
|
||||||
"ALwAAAA2aGhlYQdkA+oAAAD0AAAAJGhtdHgD6AAAAAAEWAAAAAZtYXhwAAJQAAAAARgA" +
|
return request;
|
||||||
"AAAGbmFtZVjmdH4AAAGAAAAAsXBvc3T/hgAzAAADeAAAACAAAQAAAAEAALZRFsRfDzz1" +
|
}
|
||||||
"AAsD6AAAAADOBOTLAAAAAM4KHDwAAAAAA+gDIQAAAAgAAgAAAAAAAAABAAADIQAAAFoD" +
|
|
||||||
"6AAAAAAD6AABAAAAAAAAAAAAAAAAAAAAAQAAUAAAAgAAAAQD6AH0AAUAAAKKArwAAACM" +
|
get _loadTestFont() {
|
||||||
"AooCvAAAAeAAMQECAAACAAYJAAAAAAAAAAAAAQAAAAAAAAAAAAAAAFBmRWQAwAAuAC4D" +
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||||
"IP84AFoDIQAAAAAAAQAAAAAAAAAAACAAIAABAAAADgCuAAEAAAAAAAAAAQAAAAEAAAAA" +
|
throw new Error("Not implemented: _loadTestFont");
|
||||||
"AAEAAQAAAAEAAAAAAAIAAQAAAAEAAAAAAAMAAQAAAAEAAAAAAAQAAQAAAAEAAAAAAAUA" +
|
|
||||||
"AQAAAAEAAAAAAAYAAQAAAAMAAQQJAAAAAgABAAMAAQQJAAEAAgABAAMAAQQJAAIAAgAB" +
|
|
||||||
"AAMAAQQJAAMAAgABAAMAAQQJAAQAAgABAAMAAQQJAAUAAgABAAMAAQQJAAYAAgABWABY" +
|
|
||||||
"AAAAAAAAAwAAAAMAAAAcAAEAAAAAADwAAwABAAAAHAAEACAAAAAEAAQAAQAAAC7//wAA" +
|
|
||||||
"AC7////TAAEAAAAAAAABBgAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
|
||||||
"AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
|
||||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
|
||||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
|
||||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
|
||||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAD/gwAyAAAAAQAAAAAAAAAAAAAAAAAA" +
|
|
||||||
"AAABAAQEAAEBAQJYAAEBASH4DwD4GwHEAvgcA/gXBIwMAYuL+nz5tQXkD5j3CBLnEQAC" +
|
|
||||||
"AQEBIVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYAAABAQAADwACAQEEE/t3" +
|
|
||||||
"Dov6fAH6fAT+fPp8+nwHDosMCvm1Cvm1DAz6fBQAAAAAAAABAAAAAMmJbzEAAAAAzgTj" +
|
|
||||||
"FQAAAADOBOQpAAEAAAAAAAAADAAUAAQAAAABAAAAAgABAAAAAAAAAAAD6AAAAAAAAA=="
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return shadow(this, "_loadTestFont", getLoadTestFont());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_prepareFontLoadEvent(rules, fonts, request) {
|
// This is a CFF font with 1 glyph for '.' that fills its entire width
|
||||||
/** Hack begin */
|
// and height.
|
||||||
// There's currently no event when a font has finished downloading so the
|
const testFont = atob(
|
||||||
// following code is a dirty hack to 'guess' when a font is ready.
|
"T1RUTwALAIAAAwAwQ0ZGIDHtZg4AAAOYAAAAgUZGVE1lkzZwAAAEHAAAABxHREVGABQA" +
|
||||||
// It's assumed fonts are loaded in order, so add a known test font after
|
"FQAABDgAAAAeT1MvMlYNYwkAAAEgAAAAYGNtYXABDQLUAAACNAAAAUJoZWFk/xVFDQAA" +
|
||||||
// the desired fonts and then test for the loading of that test font.
|
"ALwAAAA2aGhlYQdkA+oAAAD0AAAAJGhtdHgD6AAAAAAEWAAAAAZtYXhwAAJQAAAAARgA" +
|
||||||
|
"AAAGbmFtZVjmdH4AAAGAAAAAsXBvc3T/hgAzAAADeAAAACAAAQAAAAEAALZRFsRfDzz1" +
|
||||||
|
"AAsD6AAAAADOBOTLAAAAAM4KHDwAAAAAA+gDIQAAAAgAAgAAAAAAAAABAAADIQAAAFoD" +
|
||||||
|
"6AAAAAAD6AABAAAAAAAAAAAAAAAAAAAAAQAAUAAAAgAAAAQD6AH0AAUAAAKKArwAAACM" +
|
||||||
|
"AooCvAAAAeAAMQECAAACAAYJAAAAAAAAAAAAAQAAAAAAAAAAAAAAAFBmRWQAwAAuAC4D" +
|
||||||
|
"IP84AFoDIQAAAAAAAQAAAAAAAAAAACAAIAABAAAADgCuAAEAAAAAAAAAAQAAAAEAAAAA" +
|
||||||
|
"AAEAAQAAAAEAAAAAAAIAAQAAAAEAAAAAAAMAAQAAAAEAAAAAAAQAAQAAAAEAAAAAAAUA" +
|
||||||
|
"AQAAAAEAAAAAAAYAAQAAAAMAAQQJAAAAAgABAAMAAQQJAAEAAgABAAMAAQQJAAIAAgAB" +
|
||||||
|
"AAMAAQQJAAMAAgABAAMAAQQJAAQAAgABAAMAAQQJAAUAAgABAAMAAQQJAAYAAgABWABY" +
|
||||||
|
"AAAAAAAAAwAAAAMAAAAcAAEAAAAAADwAAwABAAAAHAAEACAAAAAEAAQAAQAAAC7//wAA" +
|
||||||
|
"AC7////TAAEAAAAAAAABBgAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
||||||
|
"AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAD/gwAyAAAAAQAAAAAAAAAAAAAAAAAA" +
|
||||||
|
"AAABAAQEAAEBAQJYAAEBASH4DwD4GwHEAvgcA/gXBIwMAYuL+nz5tQXkD5j3CBLnEQAC" +
|
||||||
|
"AQEBIVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYAAABAQAADwACAQEEE/t3" +
|
||||||
|
"Dov6fAH6fAT+fPp8+nwHDosMCvm1Cvm1DAz6fBQAAAAAAAABAAAAAMmJbzEAAAAAzgTj" +
|
||||||
|
"FQAAAADOBOQpAAEAAAAAAAAADAAUAAQAAAABAAAAAgABAAAAAAAAAAAD6AAAAAAAAA=="
|
||||||
|
);
|
||||||
|
return shadow(this, "_loadTestFont", testFont);
|
||||||
|
}
|
||||||
|
|
||||||
function int32(data, offset) {
|
_prepareFontLoadEvent(rules, fonts, request) {
|
||||||
return (
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||||
(data.charCodeAt(offset) << 24) |
|
throw new Error("Not implemented: _prepareFontLoadEvent");
|
||||||
(data.charCodeAt(offset + 1) << 16) |
|
}
|
||||||
(data.charCodeAt(offset + 2) << 8) |
|
|
||||||
(data.charCodeAt(offset + 3) & 0xff)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function spliceString(s, offset, remove, insert) {
|
|
||||||
const chunk1 = s.substring(0, offset);
|
|
||||||
const chunk2 = s.substring(offset + remove);
|
|
||||||
return chunk1 + insert + chunk2;
|
|
||||||
}
|
|
||||||
let i, ii;
|
|
||||||
|
|
||||||
// The temporary canvas is used to determine if fonts are loaded.
|
/** Hack begin */
|
||||||
const canvas = this._document.createElement("canvas");
|
// There's currently no event when a font has finished downloading so the
|
||||||
canvas.width = 1;
|
// following code is a dirty hack to 'guess' when a font is ready.
|
||||||
canvas.height = 1;
|
// It's assumed fonts are loaded in order, so add a known test font after
|
||||||
const ctx = canvas.getContext("2d");
|
// the desired fonts and then test for the loading of that test font.
|
||||||
|
|
||||||
let called = 0;
|
function int32(data, offset) {
|
||||||
function isFontReady(name, callback) {
|
return (
|
||||||
called++;
|
(data.charCodeAt(offset) << 24) |
|
||||||
// With setTimeout clamping this gives the font ~100ms to load.
|
(data.charCodeAt(offset + 1) << 16) |
|
||||||
if (called > 30) {
|
(data.charCodeAt(offset + 2) << 8) |
|
||||||
warn("Load test font never loaded.");
|
(data.charCodeAt(offset + 3) & 0xff)
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.font = "30px " + name;
|
|
||||||
ctx.fillText(".", 0, 20);
|
|
||||||
const imageData = ctx.getImageData(0, 0, 1, 1);
|
|
||||||
if (imageData.data[3] > 0) {
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setTimeout(isFontReady.bind(null, name, callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadTestFontId = `lt${Date.now()}${this.loadTestFontId++}`;
|
|
||||||
// Chromium seems to cache fonts based on a hash of the actual font data,
|
|
||||||
// so the font must be modified for each load test else it will appear to
|
|
||||||
// be loaded already.
|
|
||||||
// TODO: This could maybe be made faster by avoiding the btoa of the full
|
|
||||||
// font by splitting it in chunks before hand and padding the font id.
|
|
||||||
let data = this._loadTestFont;
|
|
||||||
const COMMENT_OFFSET = 976; // has to be on 4 byte boundary (for checksum)
|
|
||||||
data = spliceString(
|
|
||||||
data,
|
|
||||||
COMMENT_OFFSET,
|
|
||||||
loadTestFontId.length,
|
|
||||||
loadTestFontId
|
|
||||||
);
|
);
|
||||||
// CFF checksum is important for IE, adjusting it
|
|
||||||
const CFF_CHECKSUM_OFFSET = 16;
|
|
||||||
const XXXX_VALUE = 0x58585858; // the "comment" filled with 'X'
|
|
||||||
let checksum = int32(data, CFF_CHECKSUM_OFFSET);
|
|
||||||
for (i = 0, ii = loadTestFontId.length - 3; i < ii; i += 4) {
|
|
||||||
checksum = (checksum - XXXX_VALUE + int32(loadTestFontId, i)) | 0;
|
|
||||||
}
|
|
||||||
if (i < loadTestFontId.length) {
|
|
||||||
// align to 4 bytes boundary
|
|
||||||
checksum =
|
|
||||||
(checksum - XXXX_VALUE + int32(loadTestFontId + "XXX", i)) | 0;
|
|
||||||
}
|
|
||||||
data = spliceString(data, CFF_CHECKSUM_OFFSET, 4, string32(checksum));
|
|
||||||
|
|
||||||
const url = `url(data:font/opentype;base64,${btoa(data)});`;
|
|
||||||
const rule = `@font-face {font-family:"${loadTestFontId}";src:${url}}`;
|
|
||||||
this.insertRule(rule);
|
|
||||||
|
|
||||||
const names = [];
|
|
||||||
for (const font of fonts) {
|
|
||||||
names.push(font.loadedName);
|
|
||||||
}
|
|
||||||
names.push(loadTestFontId);
|
|
||||||
|
|
||||||
const div = this._document.createElement("div");
|
|
||||||
div.style.visibility = "hidden";
|
|
||||||
div.style.width = div.style.height = "10px";
|
|
||||||
div.style.position = "absolute";
|
|
||||||
div.style.top = div.style.left = "0px";
|
|
||||||
|
|
||||||
for (const name of names) {
|
|
||||||
const span = this._document.createElement("span");
|
|
||||||
span.textContent = "Hi";
|
|
||||||
span.style.fontFamily = name;
|
|
||||||
div.append(span);
|
|
||||||
}
|
|
||||||
this._document.body.append(div);
|
|
||||||
|
|
||||||
isFontReady(loadTestFontId, () => {
|
|
||||||
div.remove();
|
|
||||||
request.complete();
|
|
||||||
});
|
|
||||||
/** Hack end */
|
|
||||||
}
|
}
|
||||||
};
|
function spliceString(s, offset, remove, insert) {
|
||||||
} // End of PDFJSDev.test('CHROME || GENERIC')
|
const chunk1 = s.substring(0, offset);
|
||||||
|
const chunk2 = s.substring(offset + remove);
|
||||||
|
return chunk1 + insert + chunk2;
|
||||||
|
}
|
||||||
|
let i, ii;
|
||||||
|
|
||||||
|
// The temporary canvas is used to determine if fonts are loaded.
|
||||||
|
const canvas = this._document.createElement("canvas");
|
||||||
|
canvas.width = 1;
|
||||||
|
canvas.height = 1;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
let called = 0;
|
||||||
|
function isFontReady(name, callback) {
|
||||||
|
called++;
|
||||||
|
// With setTimeout clamping this gives the font ~100ms to load.
|
||||||
|
if (called > 30) {
|
||||||
|
warn("Load test font never loaded.");
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.font = "30px " + name;
|
||||||
|
ctx.fillText(".", 0, 20);
|
||||||
|
const imageData = ctx.getImageData(0, 0, 1, 1);
|
||||||
|
if (imageData.data[3] > 0) {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTimeout(isFontReady.bind(null, name, callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadTestFontId = `lt${Date.now()}${this.loadTestFontId++}`;
|
||||||
|
// Chromium seems to cache fonts based on a hash of the actual font data,
|
||||||
|
// so the font must be modified for each load test else it will appear to
|
||||||
|
// be loaded already.
|
||||||
|
// TODO: This could maybe be made faster by avoiding the btoa of the full
|
||||||
|
// font by splitting it in chunks before hand and padding the font id.
|
||||||
|
let data = this._loadTestFont;
|
||||||
|
const COMMENT_OFFSET = 976; // has to be on 4 byte boundary (for checksum)
|
||||||
|
data = spliceString(
|
||||||
|
data,
|
||||||
|
COMMENT_OFFSET,
|
||||||
|
loadTestFontId.length,
|
||||||
|
loadTestFontId
|
||||||
|
);
|
||||||
|
// CFF checksum is important for IE, adjusting it
|
||||||
|
const CFF_CHECKSUM_OFFSET = 16;
|
||||||
|
const XXXX_VALUE = 0x58585858; // the "comment" filled with 'X'
|
||||||
|
let checksum = int32(data, CFF_CHECKSUM_OFFSET);
|
||||||
|
for (i = 0, ii = loadTestFontId.length - 3; i < ii; i += 4) {
|
||||||
|
checksum = (checksum - XXXX_VALUE + int32(loadTestFontId, i)) | 0;
|
||||||
|
}
|
||||||
|
if (i < loadTestFontId.length) {
|
||||||
|
// align to 4 bytes boundary
|
||||||
|
checksum = (checksum - XXXX_VALUE + int32(loadTestFontId + "XXX", i)) | 0;
|
||||||
|
}
|
||||||
|
data = spliceString(data, CFF_CHECKSUM_OFFSET, 4, string32(checksum));
|
||||||
|
|
||||||
|
const url = `url(data:font/opentype;base64,${btoa(data)});`;
|
||||||
|
const rule = `@font-face {font-family:"${loadTestFontId}";src:${url}}`;
|
||||||
|
this.insertRule(rule);
|
||||||
|
|
||||||
|
const names = [];
|
||||||
|
for (const font of fonts) {
|
||||||
|
names.push(font.loadedName);
|
||||||
|
}
|
||||||
|
names.push(loadTestFontId);
|
||||||
|
|
||||||
|
const div = this._document.createElement("div");
|
||||||
|
div.style.visibility = "hidden";
|
||||||
|
div.style.width = div.style.height = "10px";
|
||||||
|
div.style.position = "absolute";
|
||||||
|
div.style.top = div.style.left = "0px";
|
||||||
|
|
||||||
|
for (const name of names) {
|
||||||
|
const span = this._document.createElement("span");
|
||||||
|
span.textContent = "Hi";
|
||||||
|
span.style.fontFamily = name;
|
||||||
|
div.append(span);
|
||||||
|
}
|
||||||
|
this._document.body.append(div);
|
||||||
|
|
||||||
|
isFontReady(loadTestFontId, () => {
|
||||||
|
div.remove();
|
||||||
|
request.complete();
|
||||||
|
});
|
||||||
|
/** Hack end */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class FontFaceObject {
|
class FontFaceObject {
|
||||||
constructor(
|
constructor(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user