Buckets:
| import fontParser from "./FontParser.js"; | |
| import unicodeFontResolverClientFactory from "../libs/unicode-font-resolver-client.factory.js"; | |
| import { defineWorkerModule } from "troika-worker-utils"; | |
| /** | |
| * @typedef {string | {src:string, label?:string, unicodeRange?:string, lang?:string}} UserFont | |
| */ | |
| /** | |
| * @typedef {ClientOptions} FontResolverOptions | |
| * @property {Array<UserFont>|UserFont} [fonts] | |
| * @property {'normal'|'italic'} [style] | |
| * @property {'normal'|'bold'|number} [style] | |
| * @property {string} [unicodeFontsURL] | |
| */ | |
| /** | |
| * @typedef {Object} FontResolverResult | |
| * @property {Uint8Array} chars | |
| * @property {Array<ParsedFont & {src:string}>} fonts | |
| */ | |
| /** | |
| * @typedef {function} FontResolver | |
| * @param {string} text | |
| * @param {(FontResolverResult) => void} callback | |
| * @param {FontResolverOptions} [options] | |
| */ | |
| /** | |
| * Factory for the FontResolver function. | |
| * @param {FontParser} fontParser | |
| * @param {{getFontsForString: function, CodePointSet: function}} unicodeFontResolverClient | |
| * @return {FontResolver} | |
| */ | |
| export function createFontResolver(fontParser, unicodeFontResolverClient) { | |
| /** | |
| * @type {Record<string, ParsedFont>} | |
| */ | |
| const parsedFonts = Object.create(null) | |
| /** | |
| * @type {Record<string, Array<(ParsedFont) => void>>} | |
| */ | |
| const loadingFonts = Object.create(null) | |
| /** | |
| * Load a given font url | |
| */ | |
| function doLoadFont(url, callback) { | |
| const onError = err => { | |
| console.error(`Failure loading font ${url}`, err) | |
| } | |
| try { | |
| const request = new XMLHttpRequest() | |
| request.open('get', url, true) | |
| request.responseType = 'arraybuffer' | |
| request.onload = function () { | |
| if (request.status >= 400) { | |
| onError(new Error(request.statusText)) | |
| } | |
| else if (request.status > 0) { | |
| try { | |
| const fontObj = fontParser(request.response) | |
| fontObj.src = url; | |
| callback(fontObj) | |
| } catch (e) { | |
| onError(e) | |
| } | |
| } | |
| } | |
| request.onerror = onError | |
| request.send() | |
| } catch(err) { | |
| onError(err) | |
| } | |
| } | |
| /** | |
| * Load a given font url if needed, invoking a callback when it's loaded. If already | |
| * loaded, the callback will be called synchronously. | |
| * @param {string} fontUrl | |
| * @param {(font: ParsedFont) => void} callback | |
| */ | |
| function loadFont(fontUrl, callback) { | |
| let font = parsedFonts[fontUrl] | |
| if (font) { | |
| callback(font) | |
| } else if (loadingFonts[fontUrl]) { | |
| loadingFonts[fontUrl].push(callback) | |
| } else { | |
| loadingFonts[fontUrl] = [callback] | |
| doLoadFont(fontUrl, fontObj => { | |
| fontObj.src = fontUrl | |
| parsedFonts[fontUrl] = fontObj | |
| loadingFonts[fontUrl].forEach(cb => cb(fontObj)) | |
| delete loadingFonts[fontUrl]; | |
| }) | |
| } | |
| } | |
| /** | |
| * For a given string of text, determine which fonts are required to fully render it and | |
| * ensure those fonts are loaded. | |
| */ | |
| return function (text, callback, { | |
| lang, | |
| fonts: userFonts = [], | |
| style = 'normal', | |
| weight = 'normal', | |
| unicodeFontsURL | |
| } = {}) { | |
| const charResolutions = new Uint8Array(text.length); | |
| const fontResolutions = []; | |
| if (!text.length) { | |
| allDone() | |
| } | |
| const fontIndices = new Map(); | |
| const fallbackRanges = [] // [[start, end], ...] | |
| if (style !== 'italic') style = 'normal' | |
| if (typeof weight !== 'number') { | |
| weight = weight === 'bold' ? 700 : 400 | |
| } | |
| if (userFonts && !Array.isArray(userFonts)) { | |
| userFonts = [userFonts] | |
| } | |
| userFonts = userFonts.slice() | |
| // filter by language | |
| .filter(def => !def.lang || def.lang.test(lang)) | |
| // switch order for easier iteration | |
| .reverse() | |
| if (userFonts.length) { | |
| const UNKNOWN = 0 | |
| const RESOLVED = 1 | |
| const NEEDS_FALLBACK = 2 | |
| let prevCharResult = UNKNOWN | |
| ;(function resolveUserFonts (startIndex = 0) { | |
| for (let i = startIndex, iLen = text.length; i < iLen; i++) { | |
| const codePoint = text.codePointAt(i) | |
| // Carry previous character's result forward if: | |
| // - it resolved to a font that also covers this character | |
| // - this character is whitespace | |
| if ( | |
| (prevCharResult === RESOLVED && fontResolutions[charResolutions[i - 1]].supportsCodePoint(codePoint)) || | |
| (i > 0 && /\s/.test(text[i])) | |
| ) { | |
| charResolutions[i] = charResolutions[i - 1] | |
| if (prevCharResult === NEEDS_FALLBACK) { | |
| fallbackRanges[fallbackRanges.length - 1][1] = i | |
| } | |
| } else { | |
| for (let j = charResolutions[i], jLen = userFonts.length; j <= jLen; j++) { | |
| if (j === jLen) { | |
| // none of the user fonts matched; needs fallback | |
| const range = prevCharResult === NEEDS_FALLBACK ? | |
| fallbackRanges[fallbackRanges.length - 1] : | |
| (fallbackRanges[fallbackRanges.length] = [i, i]) | |
| range[1] = i; | |
| prevCharResult = NEEDS_FALLBACK; | |
| } else { | |
| charResolutions[i] = j; | |
| const { src, unicodeRange } = userFonts[j]; | |
| // filter by optional explicit unicode ranges | |
| if (!unicodeRange || isCodeInRanges(codePoint, unicodeRange)) { | |
| const fontObj = parsedFonts[src]; | |
| // font not yet loaded, load it and resume | |
| if (!fontObj) { | |
| loadFont(src, () => { | |
| resolveUserFonts(i); | |
| }); | |
| return; | |
| } | |
| // if the font actually contains a glyph for this char, lock it in | |
| if (fontObj.supportsCodePoint(codePoint)) { | |
| let fontIndex = fontIndices.get(fontObj); | |
| if (typeof fontIndex !== 'number') { | |
| fontIndex = fontResolutions.length; | |
| fontResolutions.push(fontObj); | |
| fontIndices.set(fontObj, fontIndex); | |
| } | |
| charResolutions[i] = fontIndex; | |
| prevCharResult = RESOLVED; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (codePoint > 0xffff && i + 1 < iLen) { | |
| charResolutions[i + 1] = charResolutions[i] | |
| i++ | |
| if (prevCharResult === NEEDS_FALLBACK) { | |
| fallbackRanges[fallbackRanges.length - 1][1] = i | |
| } | |
| } | |
| } | |
| resolveFallbacks(); | |
| })(); | |
| } else { | |
| fallbackRanges.push([0, text.length - 1]) | |
| resolveFallbacks(); | |
| } | |
| function resolveFallbacks() { | |
| if (fallbackRanges.length) { | |
| // Combine all fallback substrings into a single string for querying | |
| const fallbackString = fallbackRanges.map(range => text.substring(range[0], range[1] + 1)).join('\n') | |
| unicodeFontResolverClient.getFontsForString(fallbackString, { | |
| lang: lang || undefined, | |
| style, | |
| weight, | |
| dataUrl: unicodeFontsURL | |
| }).then(({fontUrls, chars}) => { | |
| // Extract results and put them back in the main array | |
| const fontIndexOffset = fontResolutions.length | |
| let charIdx = 0; | |
| fallbackRanges.forEach(range => { | |
| for (let i = 0, endIdx = range[1] - range[0]; i <= endIdx; i++) { | |
| charResolutions[range[0] + i] = chars[charIdx++] + fontIndexOffset | |
| } | |
| charIdx++ //skip segment separator | |
| }) | |
| // Load and parse the fallback fonts - avoiding Promise here to prevent polyfills in the worker | |
| let loadedCount = 0; | |
| fontUrls.forEach((url, i) => { | |
| loadFont(url, fontObj => { | |
| fontResolutions[i + fontIndexOffset] = fontObj | |
| if (++loadedCount === fontUrls.length) { | |
| allDone(); | |
| } | |
| }) | |
| }) | |
| }); | |
| } else { | |
| allDone(); | |
| } | |
| } | |
| function allDone() { | |
| callback({ | |
| chars: charResolutions, | |
| fonts: fontResolutions | |
| }) | |
| } | |
| function isCodeInRanges(code, ranges) { | |
| // todo optimize search - CodePointSet from unicode-font-resolver? | |
| for (let k = 0; k < ranges.length; k++) { | |
| const [start, end = start] = ranges[k] | |
| if (start <= code && code <= end) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| } | |
| } | |
| export const fontResolverWorkerModule = /*#__PURE__*/defineWorkerModule({ | |
| name: 'FontResolver', | |
| dependencies: [ | |
| createFontResolver, | |
| fontParser, | |
| unicodeFontResolverClientFactory, | |
| ], | |
| init(createFontResolver, fontParser, unicodeFontResolverClientFactory) { | |
| return createFontResolver(fontParser, unicodeFontResolverClientFactory()); | |
| } | |
| }) | |
Xet Storage Details
- Size:
- 8.88 kB
- Xet hash:
- cdbc0f85fa69729e4a459e8d5f5d7067119bfc45cdad8cd639d5406f86a11928
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.