UNPKG

lib-font

Version:

A JS based OpenType font inspector

190 lines (173 loc) 5.87 kB
import "./src/utils/shim-fetch.js"; import { Event, EventManager } from "./src/eventing.js"; import { SFNT, WOFF, WOFF2 } from "./src/opentype/index.js"; import { loadTableClasses } from "./src/opentype/tables/createTable.js"; import { setupFontFace } from "./src/utils/fontface.js"; import { validFontFormat } from "./src/utils/validator.js"; /** * Borderline trivial http response helper function * * @param {HttpResponse} response */ function checkFetchResponseStatus(response) { if (!response.ok) { throw new Error(`HTTP ${response.status} - ${response.statusText}`); } return response; } /** * The Font object, which the WebAPIs are still sorely missing. */ class Font extends EventManager { constructor(name, options={}) { super(); this.name = name; this.options = options; this.metrics = false; } get src() { return this.__src; } /** * Just like Image and Audio, we kick everything off when * our `src` gets assigned. * * @param {string} source url for this font ("real" or blob/base64) */ set src(src) { this.__src = src; (async() => { if (globalThis.document && !this.options.skipStyleSheet) { await setupFontFace(this.name, src, this.options); } this.loadFont(src); })(); } /** * This is a non-blocking operation. * * @param {String} url The URL for the font in question * @param {String} filename The filename to use when URL is a blob/base64 string */ async loadFont(url, filename) { fetch(url) .then(response => checkFetchResponseStatus(response) && response.arrayBuffer()) .then(buffer => this.fromDataBuffer(buffer, filename || url)) .catch(err => { const evt = new Event(`error`, err, `Failed to load font at ${filename || url}`); this.dispatch(evt); if (this.onerror) this.onerror(evt); }); } /** * This is a non-blocking operation. * * @param {Buffer} buffer The binary data associated with this font. */ async fromDataBuffer(buffer, filenameOrUrL) { this.fontData = new DataView(buffer); // Big Endian let type = validFontFormat(this.fontData); if (!type) { // handled in loadFont's .catch() throw new Error(`${filenameOrUrL} is either an unsupported font format, or not a font at all.`); } await this.parseBasicData(type); const evt = new Event("load", { font: this }); this.dispatch(evt); if (this.onload) this.onload(evt); } /** * This is a non-blocking operation IF called from an async function */ async parseBasicData(type) { return loadTableClasses().then(createTable => { if (type === `SFNT`) { this.opentype = new SFNT(this, this.fontData, createTable); } if (type === `WOFF`) { this.opentype = new WOFF(this, this.fontData, createTable); } if (type === `WOFF2`) { this.opentype = new WOFF2(this, this.fontData, createTable); } return this.opentype; }); } /** * Does this font support the specified character? * @param {*} char */ getGlyphId(char) { return this.opentype.tables.cmap.getGlyphId(char); } /** * find the actual "letter" for a given glyphid * @param {*} glyphid */ reverse(glyphid) { return this.opentype.tables.cmap.reverse(glyphid); } /** * Does this font support the specified character? * @param {*} char */ supports(char) { return this.getGlyphId(char) !== 0 } /** * Does this font support the specified unicode variation? * @param {*} variation */ supportsVariation(variation) { return this.opentype.tables.cmap.supportsVariation(variation) !== false; } /** * Effectively, be https://html.spec.whatwg.org/multipage/canvas.html#textmetrics * @param {*} text * @param {*} size */ measureText(text, size=16) { if (this.__unloaded) throw new Error("Cannot measure text: font was unloaded. Please reload before calling measureText()"); let d = document.createElement('div'); d.textContent = text; d.style.fontFamily = this.name; d.style.fontSize = `${size}px`; d.style.color = `transparent`; d.style.background = `transparent`; d.style.top = `0`; d.style.left = `0`; d.style.position = `absolute`; document.body.appendChild(d); let bbox = d.getBoundingClientRect(); document.body.removeChild(d); const OS2 = this.opentype.tables["OS/2"]; bbox.fontSize = size; bbox.ascender = OS2.sTypoAscender; bbox.descender = OS2.sTypoDescender; return bbox; } /** * unload this font from the DOM context, making it no longer available for CSS purposes */ unload() { if (this.styleElement.parentNode) { this.styleElement.parentNode.removeElement(this.styleElement); const evt = new Event("unload", { font: this }); this.dispatch(evt); if (this.onunload) this.onunload(evt); } this._unloaded = true; } /** * load this font back into the DOM context after being unload()'d earlier. */ load() { if (this.__unloaded) { delete this.__unloaded; document.head.appendChild(this.styleElement); const evt = new Event("load", { font: this }); this.dispatch(evt); if (this.onload) this.onload(evt); } } } globalThis.Font = Font; export { Font };