@weasel-js/font 1.14.2 → 1.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-EQD3U7KB.js → chunk-I7OQG2DF.js} +79 -62
- package/dist/chunk-I7OQG2DF.js.map +1 -0
- package/dist/index.d.ts +19 -5
- package/dist/index.js +25 -8
- package/dist/index.js.map +1 -1
- package/dist/{test-seams-BZPEYFPZ.d.ts → test-seams-DUZZDLT-.d.ts} +18 -1
- package/dist/test-seams.d.ts +1 -1
- package/dist/test-seams.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-EQD3U7KB.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { loadFontOutlines, outlineMetrics, fontPending, fallbackWarningClaimed, claimFallbackWarning, resolveGlyphFallback, registerFontOutlines, createOpenTypeParser, getFont, SDF_RADIUS, BAKE_SIZE, PAGE_SIZE } from './chunk-
|
|
2
|
-
export { DEFAULT_BAKE_BUDGET, PAGE_SIZE as DYNAMIC_PAGE_SIZE, MAX_SHARP_LEVEL, colorPageTextureId, cssFontFamily, cssFontFamilyLoading, dynamicPageTextureId, ensureFontTexture, faceGeneration, faceMetricsFor, fontOutlineRegistry, fontPending, fontRegistry, getDefaultFontFamily, getFont, getFontFallbackPolicy, glyphGeneration, glyphOutline, hasFontOutlines, isCanvasFont, listCanvasFonts, listFontOutlines, listFontWeights, listFonts, loadFontOutlines, markAllFontsNotUploaded, outlineMetrics, outlineStatus, refreshCanvasFont, registerCanvasFont, registerFont, registerFontOutlines, resetBakeBudget, resolveFontVariant, resolveGlyphFallback, setDefaultFontFamily, setFontFallbackPolicy, sharpColorPageTextureId, sharperColorGlyph, subscribeGlyphReady, syncColorPageTexture, syncDynamicPageTexture, syncSharpColorPageTexture, textureCacheKey, unregisterCanvasFont, unregisterFontOutlines, warmFonts } from './chunk-
|
|
1
|
+
import { loadFontOutlines, outlineMetrics, fontPending, fallbackWarningClaimed, claimFallbackWarning, resolveGlyphFallback, registerFontOutlines, createOpenTypeParser, getFont, SDF_RADIUS, BAKE_SIZE, PAGE_SIZE } from './chunk-I7OQG2DF.js';
|
|
2
|
+
export { DEFAULT_BAKE_BUDGET, PAGE_SIZE as DYNAMIC_PAGE_SIZE, MAX_SHARP_LEVEL, cachedFontVariant, colorPageTextureId, cssFontFamily, cssFontFamilyLoading, dynamicPageTextureId, ensureFontTexture, faceGeneration, faceMetricsFor, fontOutlineRegistry, fontPending, fontRegistry, getDefaultFontFamily, getFont, getFontFallbackPolicy, glyphGeneration, glyphOutline, hasFontOutlines, isCanvasFont, listCanvasFonts, listFontOutlines, listFontWeights, listFonts, loadFontOutlines, markAllFontsNotUploaded, outlineMetrics, outlineStatus, refreshCanvasFont, registerCanvasFont, registerFont, registerFontOutlines, resetBakeBudget, resolveFontVariant, resolveGlyphFallback, setDefaultFontFamily, setFontFallbackPolicy, sharpColorPageTextureId, sharperColorGlyph, subscribeGlyphReady, syncColorPageTexture, syncDynamicPageTexture, syncSharpColorPageTexture, textureCacheKey, unregisterCanvasFont, unregisterFontOutlines, warmFonts } from './chunk-I7OQG2DF.js';
|
|
3
3
|
export { FIXTURE_FONT, cssFamilyName, faceMetricsFromTables, faceMetricsOf, parseBmFont, verticalMetricsFromTables } from './chunk-XQPRAGHF.js';
|
|
4
4
|
|
|
5
5
|
// src/outline/fontEmbedding.ts
|
|
@@ -249,6 +249,7 @@ var GLYPH_MODE_MSDF = 1;
|
|
|
249
249
|
var GLYPH_MODE_R8 = 2;
|
|
250
250
|
var GLYPH_AREA_FROM = 0.2;
|
|
251
251
|
var GLYPH_AREA_FULL = 0.3;
|
|
252
|
+
var GLYPH_AREA_MARGIN = 1;
|
|
252
253
|
function glyphNeedsArea(fieldPerUv, pxPerUvU, pxPerUvV) {
|
|
253
254
|
const fieldPerPx = 0.5 * (fieldPerUv[0] / pxPerUvU + fieldPerUv[1] / pxPerUvV);
|
|
254
255
|
return fieldPerPx > GLYPH_AREA_FROM * 0.9;
|
|
@@ -264,12 +265,28 @@ var AREA_TAPS_GLSL = (
|
|
|
264
265
|
vec2 at = vec2(-0.375, -0.125);
|
|
265
266
|
for (int i = 0; i < 4; i++) {
|
|
266
267
|
float tap = glyphField(glyphTap(uv + at.x * du + at.y * dv), mode);
|
|
267
|
-
sum += clamp((tap - lo) / band, 0.0, 1.0);
|
|
268
|
+
sum += clamp((tap - lo) / band, 0.0, 1.0) * glyphInCell(cell + at.x * dcx + at.y * dcy, margin);
|
|
268
269
|
at = vec2(-at.y, at.x);
|
|
269
270
|
}
|
|
270
271
|
coverage = mix(coverage, 0.25 * sum, area);
|
|
271
272
|
}`
|
|
272
273
|
);
|
|
274
|
+
var IN_CELL_GLSL = (
|
|
275
|
+
/* glsl */
|
|
276
|
+
`
|
|
277
|
+
float glyphInCell(vec2 c, vec2 margin) {
|
|
278
|
+
vec2 inside = step(margin, c) * step(c, 1.0 - margin);
|
|
279
|
+
return inside.x * inside.y;
|
|
280
|
+
}
|
|
281
|
+
`
|
|
282
|
+
);
|
|
283
|
+
var CELL_MARGIN_GLSL = (
|
|
284
|
+
/* glsl */
|
|
285
|
+
`
|
|
286
|
+
vec2 dcx = dFdx(cell);
|
|
287
|
+
vec2 dcy = dFdy(cell);
|
|
288
|
+
vec2 margin = ${GLYPH_AREA_MARGIN.toFixed(1)} * vec2(length(vec2(dcx.x, dcy.x)), length(vec2(dcx.y, dcy.y)));`
|
|
289
|
+
);
|
|
273
290
|
function glyphCoverageGlsl(area) {
|
|
274
291
|
return (
|
|
275
292
|
/* glsl */
|
|
@@ -281,15 +298,15 @@ float median(float r, float g, float b) {
|
|
|
281
298
|
float glyphField(vec4 texel, float mode) {
|
|
282
299
|
return mix(median(texel.r, texel.g, texel.b), texel.r, step(1.5, mode));
|
|
283
300
|
}
|
|
284
|
-
|
|
285
|
-
float glyphCoverage(vec4 texel, float mode, float synthBold, vec2 uv, vec2 fieldPerUv) {
|
|
301
|
+
${area ? IN_CELL_GLSL : ""}
|
|
302
|
+
float glyphCoverage(vec4 texel, float mode, float synthBold, vec2 uv, vec2 fieldPerUv${area ? ", vec2 cell" : ""}) {
|
|
286
303
|
vec2 du = dFdx(uv);
|
|
287
|
-
vec2 dv = dFdy(uv)
|
|
304
|
+
vec2 dv = dFdy(uv);${area ? CELL_MARGIN_GLSL : ""}
|
|
288
305
|
float fieldPerPx = 0.5 * (length(du * fieldPerUv) + length(dv * fieldPerUv));
|
|
289
306
|
float aaW = max(0.5 * fieldPerPx, 0.0005);
|
|
290
307
|
float threshold = 0.5 - synthBold;
|
|
291
308
|
float field = glyphField(texel, mode);
|
|
292
|
-
float coverage = smoothstep(threshold - aaW, threshold + aaW, field);${area ? AREA_TAPS_GLSL : ""}
|
|
309
|
+
float coverage = smoothstep(threshold - aaW, threshold + aaW, field)${area ? " * glyphInCell(cell, margin)" : ""};${area ? AREA_TAPS_GLSL : ""}
|
|
293
310
|
return coverage;
|
|
294
311
|
}
|
|
295
312
|
`
|
|
@@ -317,6 +334,6 @@ function syntheticBoldGrowth(source, family, weight, style) {
|
|
|
317
334
|
return SYNTH_BOLD_AMOUNT * (font.distanceRange ?? DEFAULT_MSDF_RANGE) / font.info.size;
|
|
318
335
|
}
|
|
319
336
|
|
|
320
|
-
export { GLYPH_COVERAGE_GLSL, GLYPH_MODE_MSDF, GLYPH_MODE_R8, GLYPH_POINT_COVERAGE_GLSL, SYNTH_BOLD_AMOUNT, canQueryLocalFonts, enableLocalFontOutlines, fontEmbedding, glyphFieldScale, glyphIn, glyphNeedsArea, metricsOf, parseFontStyle, resolveColorCluster, resolveGlyph, syntheticBoldGrowth, warnNoMetrics };
|
|
337
|
+
export { GLYPH_AREA_MARGIN, GLYPH_COVERAGE_GLSL, GLYPH_MODE_MSDF, GLYPH_MODE_R8, GLYPH_POINT_COVERAGE_GLSL, SYNTH_BOLD_AMOUNT, canQueryLocalFonts, enableLocalFontOutlines, fontEmbedding, glyphFieldScale, glyphIn, glyphNeedsArea, metricsOf, parseFontStyle, resolveColorCluster, resolveGlyph, syntheticBoldGrowth, warnNoMetrics };
|
|
321
338
|
//# sourceMappingURL=index.js.map
|
|
322
339
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/outline/fontEmbedding.ts","../src/defaultIgnorable.ts","../src/glyphMetrics.ts","../src/emojiCluster.ts","../src/glyphWarnings.ts","../src/resolveGlyph.ts","../src/outline/localFonts.ts","../src/textSdf.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { FontStyle } from '../fontStyle';\nimport type { FaceEmbedding } from './OutlineFace';\nimport { loadFontOutlines, outlineMetrics } from './outlineRegistry';\n\n/**\n * The embeddable sfnt and glyph map of a registered outline face, once its\n * bytes have loaded. `null` when nothing is registered for the variant, the\n * load failed, or the face's parser has no sfnt behind it.\n */\nexport async function fontEmbedding(\n family: string, weight: number, style: FontStyle,\n): Promise<FaceEmbedding | null> {\n if ((await loadFontOutlines(family, { weight, style })) !== 'ready') return null;\n return outlineMetrics(family, weight, style)?.embedding?.() ?? null;\n}\n","/** Unicode's `Default_Ignorable_Code_Point` ranges (DerivedCoreProperties.txt). */\nconst RANGES: ReadonlyArray<readonly [number, number]> = [\n [0x00ad, 0x00ad], [0x034f, 0x034f], [0x061c, 0x061c], [0x115f, 0x1160],\n [0x17b4, 0x17b5], [0x180b, 0x180f], [0x200b, 0x200f], [0x202a, 0x202e],\n [0x2060, 0x206f], [0x3164, 0x3164], [0xfe00, 0xfe0f], [0xfeff, 0xfeff],\n [0xffa0, 0xffa0], [0xfff0, 0xfff8], [0x1bca0, 0x1bca3], [0x1d173, 0x1d17a],\n [0xe0000, 0xe0fff],\n];\n\n/**\n * Whether `cp` is one a renderer shows as nothing by design: joiners,\n * variation selectors, zero-width spaces, bidi controls, the soft hyphen. A\n * face serving one as no advance and no ink has served it.\n */\nexport function isDefaultIgnorable(cp: number): boolean {\n return RANGES.some(([lo, hi]) => cp >= lo && cp <= hi);\n}\n","/**\n * One shape for the advances, kerning and baseline of whichever tier resolved\n * a face. A baked atlas and the canvas tier measure in their bake-size pixels\n * and a parsed face in ems; `size` is the divisor that takes any of them to\n * world units (`fontSize / size`), which is why an em-space face reports 1.\n */\nimport type { BmFont, BmFontChar } from './FontAtlas';\nimport { isDefaultIgnorable } from './defaultIgnorable';\nimport type { DynamicFace } from './dynamic/dynamicAtlas';\nimport type { OutlineFace } from './outline/OutlineFace';\nimport type { ResolveResult } from './registerFont';\n\n/** Where a face's advances, kerning and baseline come from. */\nexport interface GlyphMetrics {\n /** Units per em of everything else here. */\n readonly size: number;\n /** Line top to baseline in the glyph frame: what an atlas glyph's `yoffset`\n * is measured against. */\n readonly base: number;\n /** Baseline to the top of the face's line box: the ascent a browser sets\n * the face with, or `base` for a face that states none. */\n readonly ascent: number;\n /** Baseline to the bottom of the face's line box. Absent for a face that\n * states no ascent and descent, which hangs its baseline `base` below the\n * line top instead of centering the face in its line. */\n readonly descent?: number;\n /** Advance for `cp`, or null when the face has no glyph for it. On the\n * canvas tier this measures the glyph and queues its bake. */\n advanceOf(cp: number): number | null;\n kernOf(left: number, right: number): number;\n}\n\n/** A canvas face that cannot measure a code point reports no advance and no\n * ink for it, which is its only way to say it has no glyph, and exactly how\n * it serves a default-ignorable one. A glyph with ink has no width until its\n * bake lands, and says so with a negative page. */\nexport function usableGlyph(glyph: BmFontChar): boolean {\n return glyph.xadvance > 0 || glyph.width > 0 || glyph.page < 0 || isDefaultIgnorable(glyph.id);\n}\n\n/** Reads through to `font` rather than copying: the canvas tier measures a\n * face again in place when its web font lands. */\nfunction atlasMetrics(font: BmFont, advanceOf: (cp: number) => number | null): GlyphMetrics {\n // The products are kept until the face's metrics object is replaced, since\n // each fresh one boxed a number on every read: 120 KB a frame of a HUD readout.\n let seen: BmFont['faceMetrics'] | null = null;\n let ascent = 0.5;\n let descent: number | undefined;\n const stated = (): boolean => {\n const face = font.faceMetrics;\n if (face !== seen) {\n seen = face;\n const has = face?.ascent !== undefined && face.descent !== undefined;\n ascent = has ? face.ascent! * font.info.size : 0.5;\n descent = has ? face.descent! * font.info.size : undefined;\n }\n return descent !== undefined;\n };\n return {\n size: font.info.size,\n get base() { return font.common.base; },\n get ascent() { return stated() ? ascent : font.common.base; },\n get descent() { return stated() ? descent : undefined; },\n advanceOf,\n kernOf: (l, r) => font.kerningMap.get(l)?.get(r) ?? 0,\n };\n}\n\nfunction outlineMetricsOf(face: OutlineFace): GlyphMetrics {\n const descent = face.faceMetrics?.descent;\n return {\n size: 1,\n base: face.ascender,\n ascent: face.faceMetrics?.ascent ?? face.ascender,\n ...(descent !== undefined ? { descent } : {}),\n advanceOf: (cp) => face.advanceOf(cp),\n kernOf: (l, r) => face.kernOf(l, r),\n };\n}\n\nfunction canvasMetrics(face: DynamicFace): GlyphMetrics {\n return atlasMetrics(face.font, (cp) => {\n const glyph = face.requestGlyph(cp);\n return usableGlyph(glyph) ? glyph.xadvance : null;\n });\n}\n\nconst byFace = new WeakMap<object, GlyphMetrics>();\n\nfunction memo<F extends object>(face: F, build: (face: F) => GlyphMetrics): GlyphMetrics {\n let m = byFace.get(face);\n if (!m) {\n m = build(face);\n byFace.set(face, m);\n }\n return m;\n}\n\n/**\n * The metrics of whichever tier `result` resolved to, or `undefined` for a\n * miss, which has none. One object per face, so two results that landed on the\n * same face compare equal by identity.\n */\nexport function metricsOf(result: ResolveResult): GlyphMetrics | undefined {\n if (result.outlineFace) return memo(result.outlineFace, outlineMetricsOf);\n if (result.entry) {\n return memo(result.entry.font, (font) => atlasMetrics(font, (cp) => font.charMap.get(cp)?.xadvance ?? null));\n }\n if (result.dynamicFace) return memo(result.dynamicFace, canvasMetrics);\n return undefined;\n}\n","const EMOJI = /\\p{Emoji_Presentation}|\\p{Extended_Pictographic}\\uFE0F|\\uFE0F\\u20E3/u;\n\n/** The first code point that is an emoji by default (U+231A WATCH). */\nconst FIRST_DEFAULT_EMOJI = 0x231a;\n\n/**\n * Whether a grapheme cluster is drawn as an emoji rather than as text: it\n * holds a code point that is one by default, or one made so by U+FE0F, and no\n * U+FE0E asking for text.\n */\nexport function hasEmojiPresentation(cluster: string): boolean {\n if (cluster.length === 1 && cluster.charCodeAt(0) < FIRST_DEFAULT_EMOJI) return false;\n return EMOJI.test(cluster) && !cluster.includes('\\uFE0E');\n}\n","/**\n * Warnings about what the registered fonts cannot serve. Text is laid out per\n * frame, so each is claimed once through the fallback warning gate, which the\n * registry and fallback resets both clear.\n */\nimport { claimFallbackWarning, fallbackWarningClaimed } from './fallback';\nimport type { FontStyle } from './fontStyle';\nimport { fontPending } from './registerFont';\n\n/** Say, once per family and code point, that no tier has a glyph for it. */\nexport function warnMissingGlyph(family: string, cp: number): void {\n if (!claimFallbackWarning(`missing-glyph|${family}|${cp}`)) return;\n const ch = String.fromCodePoint(cp);\n console.warn(\n `weasel: no glyph for U+${cp.toString(16).toUpperCase().padStart(4, '0')} ` +\n `(${JSON.stringify(ch)}) in \"${family}\", and the dynamic tier could not ` +\n `rasterize it — skipping the character. Bake it into the atlas, or call ` +\n `registerCanvasFont(\"${family}\") to serve missing codepoints from ` +\n `installed fonts.`,\n );\n}\n\n/** The registry's own warnings that a face will draw nothing, by key prefix. */\nconst NOTHING_RENDERS = ['unusable-default', 'self-unusable-default', 'unregistered-default'];\n\n/**\n * Say, once per face, that text set in it has no metrics and lays out as\n * nothing. Silent while the face is still loading, which is late rather than\n * missing, and when the registry has already said why it cannot render.\n */\nexport function warnNoMetrics(family: string, weight: number, style: FontStyle): void {\n if (fontPending(family, weight, style)) return;\n const face = `${family}|${weight}|${style}`;\n if (NOTHING_RENDERS.some((kind) => fallbackWarningClaimed(`${kind}|${face}`))) return;\n if (!claimFallbackWarning(`no-metrics|${face}`)) return;\n console.warn(\n `weasel: no metrics for \"${family}\" ${weight}/${style} — neither ` +\n `an atlas nor an outline face resolved, so every run set in it lays out as ` +\n `nothing. Register it with registerFont / registerCanvasFont / ` +\n `registerFontOutlines. If you did, check for a duplicated @weasel-js/font in ` +\n `node_modules — its registry is module state, so a second copy is a second, ` +\n `empty one.`,\n );\n}\n","/**\n * Finding the glyph for one code point: in the tier a run resolved to, then\n * in the dynamic tier, then nowhere. A miss never draws a stand-in `?`, which\n * is indistinguishable from one the author wrote, and the BmFont atlas format\n * has no `.notdef` to draw.\n */\nimport type { BmFont, BmFontChar } from './FontAtlas';\nimport { hasEmojiPresentation } from './emojiCluster';\nimport type { FontStyle } from './fontStyle';\nimport { metricsOf, usableGlyph, type GlyphMetrics } from './glyphMetrics';\nimport { warnMissingGlyph } from './glyphWarnings';\nimport { resolveGlyphFallback, type ResolveResult } from './registerFont';\n\n/** A code point's glyph, and the tier that ended up serving it. */\nexport interface ResolvedGlyph {\n /** The tier serving the glyph. Not the run's own when the code point\n * escalated to the dynamic tier. */\n resolved: ResolveResult;\n /** That tier's metrics, which are what `advance` is measured in. */\n metrics: GlyphMetrics;\n advance: number;\n /** The atlas to sample and the glyph's record in it. Both null on the\n * outline tier, which has geometry instead. A canvas glyph's record gains\n * its atlas rect in place when its bake lands. */\n font: BmFont | null;\n glyph: BmFontChar | null;\n}\n\n/** The glyph for `cp` in the tier `resolved` landed on, or null when that\n * tier has none. With `into`, the answer is written into it and it is\n * returned: a caller that reads the fields at once and keeps none allocates\n * nothing a code point. */\nexport function glyphIn(resolved: ResolveResult, cp: number, into?: ResolvedGlyph): ResolvedGlyph | null {\n const metrics = metricsOf(resolved);\n if (!metrics) return null;\n if (resolved.outlineFace) {\n const advance = metrics.advanceOf(cp);\n return advance === null ? null : answer(into, resolved, metrics, advance, null, null);\n }\n const face = resolved.dynamicFace;\n const font = resolved.entry?.font ?? face?.font;\n const glyph = resolved.entry ? font?.charMap.get(cp) : face?.requestGlyph(cp);\n if (!font || !glyph || (face && !usableGlyph(glyph))) return null;\n return answer(into, resolved, metrics, glyph.xadvance, font, glyph);\n}\n\nfunction answer(\n into: ResolvedGlyph | undefined, resolved: ResolveResult, metrics: GlyphMetrics, advance: number,\n font: BmFont | null, glyph: BmFontChar | null,\n): ResolvedGlyph {\n if (!into) return { resolved, metrics, advance, font, glyph };\n into.resolved = resolved; into.metrics = metrics; into.advance = advance; into.font = font; into.glyph = glyph;\n return into;\n}\n\n/**\n * The glyph for `cp` in a run that asked for `(family, weight, style)` and\n * resolved to `resolved`: that tier's own, else the dynamic tier's under the\n * face the run asked for. Null when neither has it, with a warning naming the\n * code point unless `warn` is false. `into` is as `glyphIn` takes it.\n */\nexport function resolveGlyph(\n resolved: ResolveResult,\n family: string,\n weight: number,\n style: FontStyle,\n cp: number,\n warn = true,\n into?: ResolvedGlyph,\n): ResolvedGlyph | null {\n const own = glyphIn(resolved, cp, into);\n if (own) return own;\n const fallback = resolveGlyphFallback(family, weight, style);\n const escalated = fallback && glyphIn(fallback, cp, into);\n if (escalated) return escalated;\n if (warn) warnMissingGlyph(resolved.resolved.family, cp);\n return null;\n}\n\n/**\n * The color glyph for a whole grapheme cluster with emoji presentation, in a\n * run that asked for `(family, weight, style)` and resolved to `resolved`.\n * The canvas tier is the only one that draws a cluster as one glyph or in its\n * own colors, so it serves these whatever tier serves the run's text. Null for\n * any other cluster, and where the canvas tier is not to be had or draws no\n * color for this one: the cluster is then laid out code point by code point.\n */\nexport function resolveColorCluster(\n resolved: ResolveResult,\n family: string,\n weight: number,\n style: FontStyle,\n cluster: string,\n): ResolvedGlyph | null {\n if (!hasEmojiPresentation(cluster)) return null;\n const tier = resolved.dynamicFace ? resolved : resolveGlyphFallback(family, weight, style);\n const face = tier?.dynamicFace;\n const metrics = tier && metricsOf(tier);\n const glyph = face?.requestCluster(cluster);\n if (!tier || !face || !metrics || !glyph) return null;\n return { resolved: tier, metrics, advance: glyph.xadvance, font: face.font, glyph };\n}\n","/**\n * Local Font Access → outline registrations.\n *\n * The dynamic canvas-SDF tier can render any installed family without ever\n * seeing its bytes: it asks canvas 2D to `fillText` and takes the pixels. The\n * outline tier cannot — it needs the actual font file — and the only web API\n * that hands one over is `window.queryLocalFonts()`, which is Chromium-only\n * and gated behind a permission prompt that requires a user gesture.\n *\n * So this is the one part of the tier a consumer has to *ask* for, from a\n * click. A refusal is ordinary: no registrations, and large text keeps\n * rendering from the SDF tier. Safari and Firefox have no equivalent API at\n * all, so \"denied\" is the permanent state for most of the web.\n *\n * Registration is eager but reading is lazy: every matching face gets a thunk\n * that calls `FontData.blob()` at first glyph request. A machine can have\n * hundreds of faces and a `.ttc` runs to tens of megabytes.\n */\n\nimport type { FontStyle } from '../fontStyle';\nimport { createOpenTypeParser } from './opentypeParser';\nimport { registerFontOutlines } from './outlineRegistry';\n\n/**\n * The slice of the Local Font Access API this module uses. `queryLocalFonts`\n * is not in TypeScript's DOM types.\n */\ninterface LocalFontData {\n postscriptName: string;\n fullName: string;\n family: string;\n style: string;\n blob(): Promise<Blob>;\n}\n\ntype LocalFontWindow = typeof globalThis & {\n queryLocalFonts?: (opts?: { postscriptNames?: string[] }) => Promise<LocalFontData[]>;\n};\n\n/** Is the Local Font Access API present at all? False on every non-Chromium\n * browser and in any non-secure context. */\nexport function canQueryLocalFonts(): boolean {\n return typeof globalThis !== 'undefined'\n && typeof (globalThis as LocalFontWindow).queryLocalFonts === 'function';\n}\n\n/**\n * CSS weights for the weight words that appear in a `style` string.\n *\n * Ordered longest-first where one name contains another (\"ExtraBold\" before\n * \"Bold\", \"DemiBold\" before \"Bold\"), because the match is a substring scan\n * and \"Bold\" would otherwise claim every heavier face for 700.\n */\nconst WEIGHT_WORDS: readonly (readonly [string, number])[] = [\n ['extrablack', 950], ['ultrablack', 950],\n ['extrabold', 800], ['ultrabold', 800],\n ['extralight', 200], ['ultralight', 200],\n ['semibold', 600], ['demibold', 600],\n ['semilight', 350],\n ['black', 900], ['heavy', 900],\n ['bold', 700],\n ['medium', 500],\n ['light', 300],\n ['thin', 100], ['hairline', 100],\n ['book', 400], ['regular', 400], ['normal', 400],\n];\n\n/** Lowercased, with a split weight name closed up: \"Demi Bold\" and\n * \"Semi-Bold\" read as the single words the table above lists. */\nfunction joinWeightWords(style: string): string {\n return style.toLowerCase().replace(/\\b(extra|ultra|semi|demi)[\\s_-]+/g, '$1');\n}\n\n/**\n * Parse a `FontData.style` string — \"Regular\", \"Bold Italic\", \"Condensed\n * ExtraLight Oblique\" — into the CSS variant the kit's registry is keyed by.\n *\n * Anything unrecognized lands on 400/normal.\n */\nexport function parseFontStyle(style: string): { weight: number; style: FontStyle } {\n const s = joinWeightWords(style);\n const italic = s.includes('italic') || s.includes('oblique');\n const weight = WEIGHT_WORDS.find(([word]) => s.includes(word))?.[1] ?? 400;\n return { weight, style: italic ? 'italic' : 'normal' };\n}\n\n/**\n * How many words of a `FontData.style` string are *not* weight or slant —\n * \"Condensed\", \"Display\", \"Poster\". A face with none of them is the plain\n * member of its slot, and the one to keep when two faces collide.\n */\nfunction styleQualifierCount(style: string): number {\n const words = joinWeightWords(style).split(/[\\s_-]+/).filter(Boolean);\n return words.filter((w) => w !== 'italic' && w !== 'oblique'\n && !WEIGHT_WORDS.some(([word]) => word === w)).length;\n}\n\n/** Options for `enableLocalFontOutlines`. */\nexport interface LocalFontOutlinesOptions {\n /**\n * Restrict registration to these families. Defaults to every installed\n * family, which can be hundreds of registry entries.\n */\n families?: readonly string[];\n}\n\n/** What `enableLocalFontOutlines` registered. */\nexport interface LocalFontOutlinesResult {\n /** Families that now have at least one outline face, in menu order. */\n families: readonly string[];\n /** Total faces registered across those families. */\n faces: number;\n}\n\n/**\n * Ask for permission to read installed fonts, and register outline sources\n * for the faces it returns. **Must be called from a user gesture** — the\n * permission prompt requires one, and without it the call rejects.\n *\n * Rejects when the API is missing or the user denies; neither leaves the\n * registry in a partial state.\n */\nexport async function enableLocalFontOutlines(\n opts: LocalFontOutlinesOptions = {},\n): Promise<LocalFontOutlinesResult> {\n const query = (globalThis as LocalFontWindow).queryLocalFonts;\n if (typeof query !== 'function') {\n throw new Error(\n 'weasel enableLocalFontOutlines: this browser has no Local Font Access API ' +\n '(queryLocalFonts). Large text keeps rendering from the SDF tier.',\n );\n }\n\n const wanted = opts.families ? new Set(opts.families) : null;\n const data = await query.call(globalThis);\n\n // Several installed faces can land on one (family, weight, style) slot —\n // \"Helvetica Neue Condensed Bold\" reduces to 700/normal exactly like\n // \"Helvetica Neue Bold\". Pick the least-qualified name for each slot.\n const chosen = new Map<string, { font: LocalFontData; qualifiers: number }>();\n for (const font of data) {\n if (wanted && !wanted.has(font.family)) continue;\n const { weight, style } = parseFontStyle(font.style);\n const key = `${font.family}|${weight}|${style}`;\n const qualifiers = styleQualifierCount(font.style);\n const prev = chosen.get(key);\n if (prev && prev.qualifiers <= qualifiers) continue;\n chosen.set(key, { font, qualifiers });\n }\n\n const families = new Set<string>();\n for (const { font } of chosen.values()) {\n registerFontOutlines(font.family, parseFontStyle(font.style), () => font.blob(), {\n // The PostScript name is how a member is picked out of a `.ttc`\n // collection, which is what most macOS system families ship as.\n parser: createOpenTypeParser(font.postscriptName),\n // The DOM already has every installed face; naming it avoids reading\n // the collection file a second time.\n cssSrc: `local(${JSON.stringify(font.postscriptName)})`,\n });\n families.add(font.family);\n }\n return {\n families: [...families].sort((a, b) => a.localeCompare(b)),\n faces: chosen.size,\n };\n}\n","/**\n * The GLSL that turns an atlas sample into glyph coverage, and the constants\n * naming which kind of atlas a sample came from.\n *\n * Not a program: glyphs stage into the renderer's batch alongside solid\n * geometry and image quads, and the batch shader pastes this in.\n *\n * MSDF channel layout: msdf-bmfont-xml writes R, G, and B as independent\n * signed-distance fields covering different edge directions, and the true\n * field is their median. The runtime canvas bake writes one channel that *is*\n * the field. Both encode the edge at 0.5, which is what lets one threshold\n * serve them.\n */\n\nimport type { FontStyle } from './fontStyle';\nimport { getFont } from './registerFont';\nimport { PAGE_SIZE, SDF_RADIUS } from './dynamic/dynamicAtlas';\nimport { BAKE_SIZE } from './dynamic/glyphRasterizer';\n\n/** `a_paintMode` value for glyphs off an MSDF atlas — the median of R,G,B. */\nexport const GLYPH_MODE_MSDF = 1;\n/** `a_paintMode` value for glyphs off a runtime canvas bake — `.r` alone. */\nexport const GLYPH_MODE_R8 = 2;\n\n/**\n * Fields per screen pixel at which `glyphCoverage` starts taking area coverage\n * from four taps, and at which it takes nothing else. Between them it mixes the\n * two, so text zooming through the range does not change weight in a step.\n *\n * 30 and 20 px an em on a runtime canvas page (6 field units an em), and 40\n * and 27 on Inter's baked atlas (8). A regular stem is about 0.6 field units\n * wide, so these are stems of 3 and 2 pixels.\n */\nexport const GLYPH_AREA_FROM = 0.2;\nexport const GLYPH_AREA_FULL = 0.3;\n\n/**\n * Whether glyphs drawn `pxPerUv` screen pixels to a unit of texture coordinate\n * (along each axis) off an atlas of `fieldPerUv` need `GLYPH_COVERAGE_GLSL`'s\n * taps. Errs toward yes: a turned or sheared glyph's pixel spans a little more\n * field than its axes say.\n */\nexport function glyphNeedsArea(\n fieldPerUv: readonly [number, number], pxPerUvU: number, pxPerUvV: number,\n): boolean {\n const fieldPerPx = 0.5 * (fieldPerUv[0] / pxPerUvU + fieldPerUv[1] / pxPerUvV);\n return fieldPerPx > GLYPH_AREA_FROM * 0.9;\n}\n\nconst AREA_TAPS_GLSL = /* glsl */ `\n float area = smoothstep(${GLYPH_AREA_FROM.toFixed(2)}, ${GLYPH_AREA_FULL.toFixed(2)}, fieldPerPx);\n if (area > 0.0) {\n float band = 0.5 * aaW;\n float lo = threshold - 0.5 * band;\n float sum = 0.0;\n vec2 at = vec2(-0.375, -0.125);\n for (int i = 0; i < 4; i++) {\n float tap = glyphField(glyphTap(uv + at.x * du + at.y * dv), mode);\n sum += clamp((tap - lo) / band, 0.0, 1.0);\n at = vec2(-at.y, at.x);\n }\n coverage = mix(coverage, 0.25 * sum, area);\n }`;\n\nfunction glyphCoverageGlsl(area: boolean): string {\n return /* glsl */ `\nfloat median(float r, float g, float b) {\n return max(min(r, g), min(max(r, g), b));\n}\n\nfloat glyphField(vec4 texel, float mode) {\n return mix(median(texel.r, texel.g, texel.b), texel.r, step(1.5, mode));\n}\n\nfloat glyphCoverage(vec4 texel, float mode, float synthBold, vec2 uv, vec2 fieldPerUv) {\n vec2 du = dFdx(uv);\n vec2 dv = dFdy(uv);\n float fieldPerPx = 0.5 * (length(du * fieldPerUv) + length(dv * fieldPerUv));\n float aaW = max(0.5 * fieldPerPx, 0.0005);\n float threshold = 0.5 - synthBold;\n float field = glyphField(texel, mode);\n float coverage = smoothstep(threshold - aaW, threshold + aaW, field);${area ? AREA_TAPS_GLSL : ''}\n return coverage;\n}\n`;\n}\n\n/**\n * Glyph coverage from the atlas, as GLSL for a program to paste in.\n *\n * `texel` is the atlas sample at `uv`; `mode` says which field it carries:\n * `GLYPH_MODE_MSDF` takes the median of R,G,B, `GLYPH_MODE_R8` reads `.r`\n * alone.\n *\n * The antialiasing band has to be one *screen* pixel wide: `fieldPerUv` is how\n * far the field runs per unit of texture coordinate on each axis (see\n * `glyphFieldScale`), and the screen derivatives of `uv` turn that into field\n * units per screen pixel.\n *\n * Not `fwidth(field)`: that is the field's slope only where the field is a\n * straight ramp. Across a stem narrower than a pixel the two columns of a 2x2\n * quad sample either side of the ridge at equal values, the derivative reads\n * flat, and the stem is thresholded away. The derivative of `uv` is constant\n * across a glyph quad, so it cannot do that. Nor a constant band, which is\n * right at one scale only.\n *\n * **Small text takes area coverage.** One sample at the pixel's center inks a\n * stem narrower than a pixel by where it sits: centered on a column it inks\n * half a pixel plus half its width, straddling two it inks its width, so a\n * glyph's ink shifts as it moves through a pixel. Where a pixel spans\n * more of the field than `GLYPH_AREA_FROM` (see there), coverage is the mean of\n * four taps on a rotated grid, at the centers of the pixel's quarters along\n * either axis, each through a linear ramp a quarter pixel wide: a box filter\n * over the pixel, which keeps a stem's ink to its width wherever it lands. A\n * 2x2 grid would put only two taps along each axis, and a stem narrower than\n * half a pixel snaps between them; at 7.2px that left the centroid swinging\n * by 0.37px. The program defines\n * `vec4 glyphTap(vec2 uv)` before pasting this in, returning the atlas sample\n * at `uv` with no implicit derivative (`textureLod` at level 0: an atlas has no\n * mipmaps), because it runs inside the branch.\n *\n * **Nothing before that branch may be skipped, and the caller must not branch\n * around this.** A derivative in non-uniform control flow is undefined, so both\n * are taken first, and before anything selects on paint mode. A merged program\n * therefore runs the glyph math on fragments that are not glyphs and discards\n * the result; it passes a zero `fieldPerUv` for those, which keeps them out of\n * the branch.\n *\n * The branch costs fragments that never take it: 6 to 15% of a batch fragment\n * that is not a glyph (`tests/perf/recorded/glyph-area-coverage-2026-10-30/`),\n * however its condition is decided. So a program that knows no fragment will\n * need the taps pastes `GLYPH_POINT_COVERAGE_GLSL` instead, which has no\n * branch and needs no `glyphTap`; `glyphNeedsArea` tells a caller staging\n * glyphs which they need.\n *\n * `synthBold` shifts the threshold to thicken strokes where the resolver fell\n * back from a missing bold variant to the regular atlas.\n */\nexport const GLYPH_COVERAGE_GLSL = glyphCoverageGlsl(true);\n\n/** `GLYPH_COVERAGE_GLSL` reading the field at the pixel's center alone, for a\n * program drawing no glyph small enough to need area coverage. */\nexport const GLYPH_POINT_COVERAGE_GLSL = glyphCoverageGlsl(false);\n\n/** msdf-bmfont-xml's default `distanceRange`, for an atlas that records none. */\nconst DEFAULT_MSDF_RANGE = 4;\n\n/** A runtime canvas page: `PAGE_SIZE` texels, field 0 to 1 over `SDF_RADIUS` of them. */\nconst CANVAS_FIELD_SCALE = [PAGE_SIZE / SDF_RADIUS, PAGE_SIZE / SDF_RADIUS] as const;\n\n/**\n * Field units per unit of texture coordinate along u and v for the atlas a\n * glyph group samples — the `fieldPerUv` that `glyphCoverage` takes. A baked\n * MSDF atlas answers from its page size and `distanceRange`, a runtime canvas\n * page from the dynamic tier's page size and bake radius. `undefined` for an\n * atlas that is not registered.\n */\nexport function glyphFieldScale(\n source: 'atlas' | 'canvas', family: string, weight: number, style: FontStyle,\n): readonly [number, number] | undefined {\n if (source === 'canvas') return CANVAS_FIELD_SCALE;\n const font = getFont(family, weight, style)?.font;\n if (!font) return undefined;\n const range = font.distanceRange ?? DEFAULT_MSDF_RANGE;\n const u = font.common.scaleW / range, v = font.common.scaleH / range;\n // Asked once per text command per frame, so the last answer is kept.\n if (lastFieldScale[0] !== u || lastFieldScale[1] !== v) lastFieldScale = [u, v];\n return lastFieldScale;\n}\n\nlet lastFieldScale: readonly [number, number] = [NaN, NaN];\n\n/**\n * How far the field threshold drops for a glyph faked bold from a lighter\n * face. Tuned on Inter: it thickens strokes about a pixel at 16px without\n * breaking glyph topology.\n */\nexport const SYNTH_BOLD_AMOUNT = 0.08;\n\n/**\n * How far synthetic bold moves each edge of a glyph outward, in em, for the\n * atlas that serves it: `SYNTH_BOLD_AMOUNT` of the field's span, which is\n * `distanceRange` texels in a baked MSDF atlas and `SDF_RADIUS` in a runtime\n * canvas page, over the atlas's texels per em. `undefined` for an atlas that\n * is not registered.\n */\nexport function syntheticBoldGrowth(\n source: 'atlas' | 'canvas', family: string, weight: number, style: FontStyle,\n): number | undefined {\n if (source === 'canvas') return (SYNTH_BOLD_AMOUNT * SDF_RADIUS) / BAKE_SIZE;\n const font = getFont(family, weight, style)?.font;\n if (!font) return undefined;\n return (SYNTH_BOLD_AMOUNT * (font.distanceRange ?? DEFAULT_MSDF_RANGE)) / font.info.size;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/outline/fontEmbedding.ts","../src/defaultIgnorable.ts","../src/glyphMetrics.ts","../src/emojiCluster.ts","../src/glyphWarnings.ts","../src/resolveGlyph.ts","../src/outline/localFonts.ts","../src/textSdf.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { FontStyle } from '../fontStyle';\nimport type { FaceEmbedding } from './OutlineFace';\nimport { loadFontOutlines, outlineMetrics } from './outlineRegistry';\n\n/**\n * The embeddable sfnt and glyph map of a registered outline face, once its\n * bytes have loaded. `null` when nothing is registered for the variant, the\n * load failed, or the face's parser has no sfnt behind it.\n */\nexport async function fontEmbedding(\n family: string, weight: number, style: FontStyle,\n): Promise<FaceEmbedding | null> {\n if ((await loadFontOutlines(family, { weight, style })) !== 'ready') return null;\n return outlineMetrics(family, weight, style)?.embedding?.() ?? null;\n}\n","/** Unicode's `Default_Ignorable_Code_Point` ranges (DerivedCoreProperties.txt). */\nconst RANGES: ReadonlyArray<readonly [number, number]> = [\n [0x00ad, 0x00ad], [0x034f, 0x034f], [0x061c, 0x061c], [0x115f, 0x1160],\n [0x17b4, 0x17b5], [0x180b, 0x180f], [0x200b, 0x200f], [0x202a, 0x202e],\n [0x2060, 0x206f], [0x3164, 0x3164], [0xfe00, 0xfe0f], [0xfeff, 0xfeff],\n [0xffa0, 0xffa0], [0xfff0, 0xfff8], [0x1bca0, 0x1bca3], [0x1d173, 0x1d17a],\n [0xe0000, 0xe0fff],\n];\n\n/**\n * Whether `cp` is one a renderer shows as nothing by design: joiners,\n * variation selectors, zero-width spaces, bidi controls, the soft hyphen. A\n * face serving one as no advance and no ink has served it.\n */\nexport function isDefaultIgnorable(cp: number): boolean {\n return RANGES.some(([lo, hi]) => cp >= lo && cp <= hi);\n}\n","/**\n * One shape for the advances, kerning and baseline of whichever tier resolved\n * a face. A baked atlas and the canvas tier measure in their bake-size pixels\n * and a parsed face in ems; `size` is the divisor that takes any of them to\n * world units (`fontSize / size`), which is why an em-space face reports 1.\n */\nimport type { BmFont, BmFontChar } from './FontAtlas';\nimport { isDefaultIgnorable } from './defaultIgnorable';\nimport type { DynamicFace } from './dynamic/dynamicAtlas';\nimport type { OutlineFace } from './outline/OutlineFace';\nimport type { ResolveResult } from './registerFont';\n\n/** Where a face's advances, kerning and baseline come from. */\nexport interface GlyphMetrics {\n /** Units per em of everything else here. */\n readonly size: number;\n /** Line top to baseline in the glyph frame: what an atlas glyph's `yoffset`\n * is measured against. */\n readonly base: number;\n /** Baseline to the top of the face's line box: the ascent a browser sets\n * the face with, or `base` for a face that states none. */\n readonly ascent: number;\n /** Baseline to the bottom of the face's line box. Absent for a face that\n * states no ascent and descent, which hangs its baseline `base` below the\n * line top instead of centering the face in its line. */\n readonly descent?: number;\n /** Advance for `cp`, or null when the face has no glyph for it. On the\n * canvas tier this measures the glyph and queues its bake. */\n advanceOf(cp: number): number | null;\n kernOf(left: number, right: number): number;\n}\n\n/** A canvas face that cannot measure a code point reports no advance and no\n * ink for it, which is its only way to say it has no glyph, and exactly how\n * it serves a default-ignorable one. A glyph with ink has no width until its\n * bake lands, and says so with a negative page. */\nexport function usableGlyph(glyph: BmFontChar): boolean {\n return glyph.xadvance > 0 || glyph.width > 0 || glyph.page < 0 || isDefaultIgnorable(glyph.id);\n}\n\n/** Reads through to `font` rather than copying: the canvas tier measures a\n * face again in place when its web font lands. */\nfunction atlasMetrics(font: BmFont, advanceOf: (cp: number) => number | null): GlyphMetrics {\n // The products are kept until the face's metrics object is replaced, since\n // each fresh one boxed a number on every read: 120 KB a frame of a HUD readout.\n let seen: BmFont['faceMetrics'] | null = null;\n let ascent = 0.5;\n let descent: number | undefined;\n const stated = (): boolean => {\n const face = font.faceMetrics;\n if (face !== seen) {\n seen = face;\n const has = face?.ascent !== undefined && face.descent !== undefined;\n ascent = has ? face.ascent! * font.info.size : 0.5;\n descent = has ? face.descent! * font.info.size : undefined;\n }\n return descent !== undefined;\n };\n return {\n size: font.info.size,\n get base() { return font.common.base; },\n get ascent() { return stated() ? ascent : font.common.base; },\n get descent() { return stated() ? descent : undefined; },\n advanceOf,\n kernOf: (l, r) => font.kerningMap.get(l)?.get(r) ?? 0,\n };\n}\n\nfunction outlineMetricsOf(face: OutlineFace): GlyphMetrics {\n const descent = face.faceMetrics?.descent;\n return {\n size: 1,\n base: face.ascender,\n ascent: face.faceMetrics?.ascent ?? face.ascender,\n ...(descent !== undefined ? { descent } : {}),\n advanceOf: (cp) => face.advanceOf(cp),\n kernOf: (l, r) => face.kernOf(l, r),\n };\n}\n\nfunction canvasMetrics(face: DynamicFace): GlyphMetrics {\n return atlasMetrics(face.font, (cp) => {\n const glyph = face.requestGlyph(cp);\n return usableGlyph(glyph) ? glyph.xadvance : null;\n });\n}\n\nconst byFace = new WeakMap<object, GlyphMetrics>();\n\nfunction memo<F extends object>(face: F, build: (face: F) => GlyphMetrics): GlyphMetrics {\n let m = byFace.get(face);\n if (!m) {\n m = build(face);\n byFace.set(face, m);\n }\n return m;\n}\n\n/**\n * The metrics of whichever tier `result` resolved to, or `undefined` for a\n * miss, which has none. One object per face, so two results that landed on the\n * same face compare equal by identity.\n */\nexport function metricsOf(result: ResolveResult): GlyphMetrics | undefined {\n if (result.outlineFace) return memo(result.outlineFace, outlineMetricsOf);\n if (result.entry) {\n return memo(result.entry.font, (font) => atlasMetrics(font, (cp) => font.charMap.get(cp)?.xadvance ?? null));\n }\n if (result.dynamicFace) return memo(result.dynamicFace, canvasMetrics);\n return undefined;\n}\n","const EMOJI = /\\p{Emoji_Presentation}|\\p{Extended_Pictographic}\\uFE0F|\\uFE0F\\u20E3/u;\n\n/** The first code point that is an emoji by default (U+231A WATCH). */\nconst FIRST_DEFAULT_EMOJI = 0x231a;\n\n/**\n * Whether a grapheme cluster is drawn as an emoji rather than as text: it\n * holds a code point that is one by default, or one made so by U+FE0F, and no\n * U+FE0E asking for text.\n */\nexport function hasEmojiPresentation(cluster: string): boolean {\n if (cluster.length === 1 && cluster.charCodeAt(0) < FIRST_DEFAULT_EMOJI) return false;\n return EMOJI.test(cluster) && !cluster.includes('\\uFE0E');\n}\n","/**\n * Warnings about what the registered fonts cannot serve. Text is laid out per\n * frame, so each is claimed once through the fallback warning gate, which the\n * registry and fallback resets both clear.\n */\nimport { claimFallbackWarning, fallbackWarningClaimed } from './fallback';\nimport type { FontStyle } from './fontStyle';\nimport { fontPending } from './registerFont';\n\n/** Say, once per family and code point, that no tier has a glyph for it. */\nexport function warnMissingGlyph(family: string, cp: number): void {\n if (!claimFallbackWarning(`missing-glyph|${family}|${cp}`)) return;\n const ch = String.fromCodePoint(cp);\n console.warn(\n `weasel: no glyph for U+${cp.toString(16).toUpperCase().padStart(4, '0')} ` +\n `(${JSON.stringify(ch)}) in \"${family}\", and the dynamic tier could not ` +\n `rasterize it — skipping the character. Bake it into the atlas, or call ` +\n `registerCanvasFont(\"${family}\") to serve missing codepoints from ` +\n `installed fonts.`,\n );\n}\n\n/** The registry's own warnings that a face will draw nothing, by key prefix. */\nconst NOTHING_RENDERS = ['unusable-default', 'self-unusable-default', 'unregistered-default'];\n\n/**\n * Say, once per face, that text set in it has no metrics and lays out as\n * nothing. Silent while the face is still loading, which is late rather than\n * missing, and when the registry has already said why it cannot render.\n */\nexport function warnNoMetrics(family: string, weight: number, style: FontStyle): void {\n if (fontPending(family, weight, style)) return;\n const face = `${family}|${weight}|${style}`;\n if (NOTHING_RENDERS.some((kind) => fallbackWarningClaimed(`${kind}|${face}`))) return;\n if (!claimFallbackWarning(`no-metrics|${face}`)) return;\n console.warn(\n `weasel: no metrics for \"${family}\" ${weight}/${style} — neither ` +\n `an atlas nor an outline face resolved, so every run set in it lays out as ` +\n `nothing. Register it with registerFont / registerCanvasFont / ` +\n `registerFontOutlines. If you did, check for a duplicated @weasel-js/font in ` +\n `node_modules — its registry is module state, so a second copy is a second, ` +\n `empty one.`,\n );\n}\n","/**\n * Finding the glyph for one code point: in the tier a run resolved to, then\n * in the dynamic tier, then nowhere. A miss never draws a stand-in `?`, which\n * is indistinguishable from one the author wrote, and the BmFont atlas format\n * has no `.notdef` to draw.\n */\nimport type { BmFont, BmFontChar } from './FontAtlas';\nimport { hasEmojiPresentation } from './emojiCluster';\nimport type { FontStyle } from './fontStyle';\nimport { metricsOf, usableGlyph, type GlyphMetrics } from './glyphMetrics';\nimport { warnMissingGlyph } from './glyphWarnings';\nimport { resolveGlyphFallback, type ResolveResult } from './registerFont';\n\n/** A code point's glyph, and the tier that ended up serving it. */\nexport interface ResolvedGlyph {\n /** The tier serving the glyph. Not the run's own when the code point\n * escalated to the dynamic tier. */\n resolved: ResolveResult;\n /** That tier's metrics, which are what `advance` is measured in. */\n metrics: GlyphMetrics;\n advance: number;\n /** The atlas to sample and the glyph's record in it. Both null on the\n * outline tier, which has geometry instead. A canvas glyph's record gains\n * its atlas rect in place when its bake lands. */\n font: BmFont | null;\n glyph: BmFontChar | null;\n}\n\n/** The glyph for `cp` in the tier `resolved` landed on, or null when that\n * tier has none. With `into`, the answer is written into it and it is\n * returned: a caller that reads the fields at once and keeps none allocates\n * nothing a code point. */\nexport function glyphIn(resolved: ResolveResult, cp: number, into?: ResolvedGlyph): ResolvedGlyph | null {\n const metrics = metricsOf(resolved);\n if (!metrics) return null;\n if (resolved.outlineFace) {\n const advance = metrics.advanceOf(cp);\n return advance === null ? null : answer(into, resolved, metrics, advance, null, null);\n }\n const face = resolved.dynamicFace;\n const font = resolved.entry?.font ?? face?.font;\n const glyph = resolved.entry ? font?.charMap.get(cp) : face?.requestGlyph(cp);\n if (!font || !glyph || (face && !usableGlyph(glyph))) return null;\n return answer(into, resolved, metrics, glyph.xadvance, font, glyph);\n}\n\nfunction answer(\n into: ResolvedGlyph | undefined, resolved: ResolveResult, metrics: GlyphMetrics, advance: number,\n font: BmFont | null, glyph: BmFontChar | null,\n): ResolvedGlyph {\n if (!into) return { resolved, metrics, advance, font, glyph };\n into.resolved = resolved; into.metrics = metrics; into.advance = advance; into.font = font; into.glyph = glyph;\n return into;\n}\n\n/**\n * The glyph for `cp` in a run that asked for `(family, weight, style)` and\n * resolved to `resolved`: that tier's own, else the dynamic tier's under the\n * face the run asked for. Null when neither has it, with a warning naming the\n * code point unless `warn` is false. `into` is as `glyphIn` takes it.\n */\nexport function resolveGlyph(\n resolved: ResolveResult,\n family: string,\n weight: number,\n style: FontStyle,\n cp: number,\n warn = true,\n into?: ResolvedGlyph,\n): ResolvedGlyph | null {\n const own = glyphIn(resolved, cp, into);\n if (own) return own;\n const fallback = resolveGlyphFallback(family, weight, style);\n const escalated = fallback && glyphIn(fallback, cp, into);\n if (escalated) return escalated;\n if (warn) warnMissingGlyph(resolved.resolved.family, cp);\n return null;\n}\n\n/**\n * The color glyph for a whole grapheme cluster with emoji presentation, in a\n * run that asked for `(family, weight, style)` and resolved to `resolved`.\n * The canvas tier is the only one that draws a cluster as one glyph or in its\n * own colors, so it serves these whatever tier serves the run's text. Null for\n * any other cluster, and where the canvas tier is not to be had or draws no\n * color for this one: the cluster is then laid out code point by code point.\n */\nexport function resolveColorCluster(\n resolved: ResolveResult,\n family: string,\n weight: number,\n style: FontStyle,\n cluster: string,\n): ResolvedGlyph | null {\n if (!hasEmojiPresentation(cluster)) return null;\n const tier = resolved.dynamicFace ? resolved : resolveGlyphFallback(family, weight, style);\n const face = tier?.dynamicFace;\n const metrics = tier && metricsOf(tier);\n const glyph = face?.requestCluster(cluster);\n if (!tier || !face || !metrics || !glyph) return null;\n return { resolved: tier, metrics, advance: glyph.xadvance, font: face.font, glyph };\n}\n","/**\n * Local Font Access → outline registrations.\n *\n * The dynamic canvas-SDF tier can render any installed family without ever\n * seeing its bytes: it asks canvas 2D to `fillText` and takes the pixels. The\n * outline tier cannot — it needs the actual font file — and the only web API\n * that hands one over is `window.queryLocalFonts()`, which is Chromium-only\n * and gated behind a permission prompt that requires a user gesture.\n *\n * So this is the one part of the tier a consumer has to *ask* for, from a\n * click. A refusal is ordinary: no registrations, and large text keeps\n * rendering from the SDF tier. Safari and Firefox have no equivalent API at\n * all, so \"denied\" is the permanent state for most of the web.\n *\n * Registration is eager but reading is lazy: every matching face gets a thunk\n * that calls `FontData.blob()` at first glyph request. A machine can have\n * hundreds of faces and a `.ttc` runs to tens of megabytes.\n */\n\nimport type { FontStyle } from '../fontStyle';\nimport { createOpenTypeParser } from './opentypeParser';\nimport { registerFontOutlines } from './outlineRegistry';\n\n/**\n * The slice of the Local Font Access API this module uses. `queryLocalFonts`\n * is not in TypeScript's DOM types.\n */\ninterface LocalFontData {\n postscriptName: string;\n fullName: string;\n family: string;\n style: string;\n blob(): Promise<Blob>;\n}\n\ntype LocalFontWindow = typeof globalThis & {\n queryLocalFonts?: (opts?: { postscriptNames?: string[] }) => Promise<LocalFontData[]>;\n};\n\n/** Is the Local Font Access API present at all? False on every non-Chromium\n * browser and in any non-secure context. */\nexport function canQueryLocalFonts(): boolean {\n return typeof globalThis !== 'undefined'\n && typeof (globalThis as LocalFontWindow).queryLocalFonts === 'function';\n}\n\n/**\n * CSS weights for the weight words that appear in a `style` string.\n *\n * Ordered longest-first where one name contains another (\"ExtraBold\" before\n * \"Bold\", \"DemiBold\" before \"Bold\"), because the match is a substring scan\n * and \"Bold\" would otherwise claim every heavier face for 700.\n */\nconst WEIGHT_WORDS: readonly (readonly [string, number])[] = [\n ['extrablack', 950], ['ultrablack', 950],\n ['extrabold', 800], ['ultrabold', 800],\n ['extralight', 200], ['ultralight', 200],\n ['semibold', 600], ['demibold', 600],\n ['semilight', 350],\n ['black', 900], ['heavy', 900],\n ['bold', 700],\n ['medium', 500],\n ['light', 300],\n ['thin', 100], ['hairline', 100],\n ['book', 400], ['regular', 400], ['normal', 400],\n];\n\n/** Lowercased, with a split weight name closed up: \"Demi Bold\" and\n * \"Semi-Bold\" read as the single words the table above lists. */\nfunction joinWeightWords(style: string): string {\n return style.toLowerCase().replace(/\\b(extra|ultra|semi|demi)[\\s_-]+/g, '$1');\n}\n\n/**\n * Parse a `FontData.style` string — \"Regular\", \"Bold Italic\", \"Condensed\n * ExtraLight Oblique\" — into the CSS variant the kit's registry is keyed by.\n *\n * Anything unrecognized lands on 400/normal.\n */\nexport function parseFontStyle(style: string): { weight: number; style: FontStyle } {\n const s = joinWeightWords(style);\n const italic = s.includes('italic') || s.includes('oblique');\n const weight = WEIGHT_WORDS.find(([word]) => s.includes(word))?.[1] ?? 400;\n return { weight, style: italic ? 'italic' : 'normal' };\n}\n\n/**\n * How many words of a `FontData.style` string are *not* weight or slant —\n * \"Condensed\", \"Display\", \"Poster\". A face with none of them is the plain\n * member of its slot, and the one to keep when two faces collide.\n */\nfunction styleQualifierCount(style: string): number {\n const words = joinWeightWords(style).split(/[\\s_-]+/).filter(Boolean);\n return words.filter((w) => w !== 'italic' && w !== 'oblique'\n && !WEIGHT_WORDS.some(([word]) => word === w)).length;\n}\n\n/** Options for `enableLocalFontOutlines`. */\nexport interface LocalFontOutlinesOptions {\n /**\n * Restrict registration to these families. Defaults to every installed\n * family, which can be hundreds of registry entries.\n */\n families?: readonly string[];\n}\n\n/** What `enableLocalFontOutlines` registered. */\nexport interface LocalFontOutlinesResult {\n /** Families that now have at least one outline face, in menu order. */\n families: readonly string[];\n /** Total faces registered across those families. */\n faces: number;\n}\n\n/**\n * Ask for permission to read installed fonts, and register outline sources\n * for the faces it returns. **Must be called from a user gesture** — the\n * permission prompt requires one, and without it the call rejects.\n *\n * Rejects when the API is missing or the user denies; neither leaves the\n * registry in a partial state.\n */\nexport async function enableLocalFontOutlines(\n opts: LocalFontOutlinesOptions = {},\n): Promise<LocalFontOutlinesResult> {\n const query = (globalThis as LocalFontWindow).queryLocalFonts;\n if (typeof query !== 'function') {\n throw new Error(\n 'weasel enableLocalFontOutlines: this browser has no Local Font Access API ' +\n '(queryLocalFonts). Large text keeps rendering from the SDF tier.',\n );\n }\n\n const wanted = opts.families ? new Set(opts.families) : null;\n const data = await query.call(globalThis);\n\n // Several installed faces can land on one (family, weight, style) slot —\n // \"Helvetica Neue Condensed Bold\" reduces to 700/normal exactly like\n // \"Helvetica Neue Bold\". Pick the least-qualified name for each slot.\n const chosen = new Map<string, { font: LocalFontData; qualifiers: number }>();\n for (const font of data) {\n if (wanted && !wanted.has(font.family)) continue;\n const { weight, style } = parseFontStyle(font.style);\n const key = `${font.family}|${weight}|${style}`;\n const qualifiers = styleQualifierCount(font.style);\n const prev = chosen.get(key);\n if (prev && prev.qualifiers <= qualifiers) continue;\n chosen.set(key, { font, qualifiers });\n }\n\n const families = new Set<string>();\n for (const { font } of chosen.values()) {\n registerFontOutlines(font.family, parseFontStyle(font.style), () => font.blob(), {\n // The PostScript name is how a member is picked out of a `.ttc`\n // collection, which is what most macOS system families ship as.\n parser: createOpenTypeParser(font.postscriptName),\n // The DOM already has every installed face; naming it avoids reading\n // the collection file a second time.\n cssSrc: `local(${JSON.stringify(font.postscriptName)})`,\n });\n families.add(font.family);\n }\n return {\n families: [...families].sort((a, b) => a.localeCompare(b)),\n faces: chosen.size,\n };\n}\n","/**\n * The GLSL that turns an atlas sample into glyph coverage, and the constants\n * naming which kind of atlas a sample came from.\n *\n * Not a program: glyphs stage into the renderer's batch alongside solid\n * geometry and image quads, and the batch shader pastes this in.\n *\n * MSDF channel layout: msdf-bmfont-xml writes R, G, and B as independent\n * signed-distance fields covering different edge directions, and the true\n * field is their median. The runtime canvas bake writes one channel that *is*\n * the field. Both encode the edge at 0.5, which is what lets one threshold\n * serve them.\n */\n\nimport type { FontStyle } from './fontStyle';\nimport { getFont } from './registerFont';\nimport { PAGE_SIZE, SDF_RADIUS } from './dynamic/dynamicAtlas';\nimport { BAKE_SIZE } from './dynamic/glyphRasterizer';\n\n/** `a_paintMode` value for glyphs off an MSDF atlas — the median of R,G,B. */\nexport const GLYPH_MODE_MSDF = 1;\n/** `a_paintMode` value for glyphs off a runtime canvas bake — `.r` alone. */\nexport const GLYPH_MODE_R8 = 2;\n\n/**\n * Fields per screen pixel at which `glyphCoverage` starts taking area coverage\n * from four taps, and at which it takes nothing else. Between them it mixes the\n * two, so text zooming through the range does not change weight in a step.\n *\n * 30 and 20 px an em on a runtime canvas page (6 field units an em), and 40\n * and 27 on Inter's baked atlas (8). A regular stem is about 0.6 field units\n * wide, so these are stems of 3 and 2 pixels.\n */\nexport const GLYPH_AREA_FROM = 0.2;\nexport const GLYPH_AREA_FULL = 0.3;\n\n/**\n * Device pixels a glyph quad grows by on every side where its glyphs take area\n * coverage. A pixel inks up to half a pixel past the outline, and a\n * multisampled target scales a pixel the quad's edge crosses by the samples it\n * covers, so a quad no wider than the glyph's padding (half a pixel for Inter\n * at 7.2px) loses ink wherever an edge lands inside a pixel.\n */\nexport const GLYPH_AREA_MARGIN = 1;\n\n/**\n * Whether glyphs drawn `pxPerUv` screen pixels to a unit of texture coordinate\n * (along each axis) off an atlas of `fieldPerUv` need `GLYPH_COVERAGE_GLSL`'s\n * taps. Errs toward yes: a turned or sheared glyph's pixel spans a little more\n * field than its axes say.\n */\nexport function glyphNeedsArea(\n fieldPerUv: readonly [number, number], pxPerUvU: number, pxPerUvV: number,\n): boolean {\n const fieldPerPx = 0.5 * (fieldPerUv[0] / pxPerUvU + fieldPerUv[1] / pxPerUvV);\n return fieldPerPx > GLYPH_AREA_FROM * 0.9;\n}\n\nconst AREA_TAPS_GLSL = /* glsl */ `\n float area = smoothstep(${GLYPH_AREA_FROM.toFixed(2)}, ${GLYPH_AREA_FULL.toFixed(2)}, fieldPerPx);\n if (area > 0.0) {\n float band = 0.5 * aaW;\n float lo = threshold - 0.5 * band;\n float sum = 0.0;\n vec2 at = vec2(-0.375, -0.125);\n for (int i = 0; i < 4; i++) {\n float tap = glyphField(glyphTap(uv + at.x * du + at.y * dv), mode);\n sum += clamp((tap - lo) / band, 0.0, 1.0) * glyphInCell(cell + at.x * dcx + at.y * dcy, margin);\n at = vec2(-at.y, at.x);\n }\n coverage = mix(coverage, 0.25 * sum, area);\n }`;\n\n/** The grown quad's cell: 1 inside the glyph's own atlas cell, 0 in the margin\n * `GLYPH_AREA_MARGIN` added, where the atlas holds the neighboring glyph. */\nconst IN_CELL_GLSL = /* glsl */ `\nfloat glyphInCell(vec2 c, vec2 margin) {\n vec2 inside = step(margin, c) * step(c, 1.0 - margin);\n return inside.x * inside.y;\n}\n`;\n\nconst CELL_MARGIN_GLSL = /* glsl */ `\n vec2 dcx = dFdx(cell);\n vec2 dcy = dFdy(cell);\n vec2 margin = ${GLYPH_AREA_MARGIN.toFixed(1)} * vec2(length(vec2(dcx.x, dcy.x)), length(vec2(dcx.y, dcy.y)));`;\n\nfunction glyphCoverageGlsl(area: boolean): string {\n return /* glsl */ `\nfloat median(float r, float g, float b) {\n return max(min(r, g), min(max(r, g), b));\n}\n\nfloat glyphField(vec4 texel, float mode) {\n return mix(median(texel.r, texel.g, texel.b), texel.r, step(1.5, mode));\n}\n${area ? IN_CELL_GLSL : ''}\nfloat glyphCoverage(vec4 texel, float mode, float synthBold, vec2 uv, vec2 fieldPerUv${area ? ', vec2 cell' : ''}) {\n vec2 du = dFdx(uv);\n vec2 dv = dFdy(uv);${area ? CELL_MARGIN_GLSL : ''}\n float fieldPerPx = 0.5 * (length(du * fieldPerUv) + length(dv * fieldPerUv));\n float aaW = max(0.5 * fieldPerPx, 0.0005);\n float threshold = 0.5 - synthBold;\n float field = glyphField(texel, mode);\n float coverage = smoothstep(threshold - aaW, threshold + aaW, field)${area ? ' * glyphInCell(cell, margin)' : ''};${area ? AREA_TAPS_GLSL : ''}\n return coverage;\n}\n`;\n}\n\n/**\n * Glyph coverage from the atlas, as GLSL for a program to paste in.\n *\n * `texel` is the atlas sample at `uv`; `mode` says which field it carries:\n * `GLYPH_MODE_MSDF` takes the median of R,G,B, `GLYPH_MODE_R8` reads `.r`\n * alone.\n *\n * The antialiasing band has to be one *screen* pixel wide: `fieldPerUv` is how\n * far the field runs per unit of texture coordinate on each axis (see\n * `glyphFieldScale`), and the screen derivatives of `uv` turn that into field\n * units per screen pixel.\n *\n * Not `fwidth(field)`: that is the field's slope only where the field is a\n * straight ramp. Across a stem narrower than a pixel the two columns of a 2x2\n * quad sample either side of the ridge at equal values, the derivative reads\n * flat, and the stem is thresholded away. The derivative of `uv` is constant\n * across a glyph quad, so it cannot do that. Nor a constant band, which is\n * right at one scale only.\n *\n * **Small text takes area coverage.** One sample at the pixel's center inks a\n * stem narrower than a pixel by where it sits: centered on a column it inks\n * half a pixel plus half its width, straddling two it inks its width, so a\n * glyph's ink shifts as it moves through a pixel. Where a pixel spans\n * more of the field than `GLYPH_AREA_FROM` (see there), coverage is the mean of\n * four taps on a rotated grid, at the centers of the pixel's quarters along\n * either axis, each through a linear ramp a quarter pixel wide: a box filter\n * over the pixel, which keeps a stem's ink to its width wherever it lands. A\n * 2x2 grid would put only two taps along each axis, and a stem narrower than\n * half a pixel snaps between them. The program defines\n * `vec4 glyphTap(vec2 uv)` before pasting this in, returning the atlas sample\n * at `uv` with no implicit derivative (`textureLod` at level 0: an atlas has no\n * mipmaps), because it runs inside the branch.\n *\n * **A glyph taking area coverage draws on a grown quad.** Its quad grows by\n * `GLYPH_AREA_MARGIN` device pixels on every side, and `cell` runs 0 to 1\n * across the grown quad, corner to corner, so the shader can tell the glyph's\n * own cell from the margin, where the atlas holds the next glyph over: what\n * lands there reads as no ink. A quad that has not grown passes one constant\n * `cell`, which reads as all cell.\n *\n * **Nothing before that branch may be skipped, and the caller must not branch\n * around this.** A derivative in non-uniform control flow is undefined, so both\n * are taken first, and before anything selects on paint mode. A merged program\n * therefore runs the glyph math on fragments that are not glyphs and discards\n * the result; it passes a zero `fieldPerUv` for those, which keeps them out of\n * the branch.\n *\n * The branch costs fragments that never take it: 6 to 15% of a batch fragment\n * that is not a glyph (`tests/perf/recorded/glyph-area-coverage-2026-10-30/`),\n * however its condition is decided. So a program that knows no fragment will\n * need the taps pastes `GLYPH_POINT_COVERAGE_GLSL` instead, which has no\n * branch and needs no `glyphTap`; `glyphNeedsArea` tells a caller staging\n * glyphs which they need.\n *\n * `synthBold` shifts the threshold to thicken strokes where the resolver fell\n * back from a missing bold variant to the regular atlas.\n */\nexport const GLYPH_COVERAGE_GLSL = glyphCoverageGlsl(true);\n\n/** `GLYPH_COVERAGE_GLSL` reading the field at the pixel's center alone, for a\n * program drawing no glyph small enough to need area coverage. */\nexport const GLYPH_POINT_COVERAGE_GLSL = glyphCoverageGlsl(false);\n\n/** msdf-bmfont-xml's default `distanceRange`, for an atlas that records none. */\nconst DEFAULT_MSDF_RANGE = 4;\n\n/** A runtime canvas page: `PAGE_SIZE` texels, field 0 to 1 over `SDF_RADIUS` of them. */\nconst CANVAS_FIELD_SCALE = [PAGE_SIZE / SDF_RADIUS, PAGE_SIZE / SDF_RADIUS] as const;\n\n/**\n * Field units per unit of texture coordinate along u and v for the atlas a\n * glyph group samples — the `fieldPerUv` that `glyphCoverage` takes. A baked\n * MSDF atlas answers from its page size and `distanceRange`, a runtime canvas\n * page from the dynamic tier's page size and bake radius. `undefined` for an\n * atlas that is not registered.\n */\nexport function glyphFieldScale(\n source: 'atlas' | 'canvas', family: string, weight: number, style: FontStyle,\n): readonly [number, number] | undefined {\n if (source === 'canvas') return CANVAS_FIELD_SCALE;\n const font = getFont(family, weight, style)?.font;\n if (!font) return undefined;\n const range = font.distanceRange ?? DEFAULT_MSDF_RANGE;\n const u = font.common.scaleW / range, v = font.common.scaleH / range;\n // Asked once per text command per frame, so the last answer is kept.\n if (lastFieldScale[0] !== u || lastFieldScale[1] !== v) lastFieldScale = [u, v];\n return lastFieldScale;\n}\n\nlet lastFieldScale: readonly [number, number] = [NaN, NaN];\n\n/**\n * How far the field threshold drops for a glyph faked bold from a lighter\n * face. Tuned on Inter: it thickens strokes about a pixel at 16px without\n * breaking glyph topology.\n */\nexport const SYNTH_BOLD_AMOUNT = 0.08;\n\n/**\n * How far synthetic bold moves each edge of a glyph outward, in em, for the\n * atlas that serves it: `SYNTH_BOLD_AMOUNT` of the field's span, which is\n * `distanceRange` texels in a baked MSDF atlas and `SDF_RADIUS` in a runtime\n * canvas page, over the atlas's texels per em. `undefined` for an atlas that\n * is not registered.\n */\nexport function syntheticBoldGrowth(\n source: 'atlas' | 'canvas', family: string, weight: number, style: FontStyle,\n): number | undefined {\n if (source === 'canvas') return (SYNTH_BOLD_AMOUNT * SDF_RADIUS) / BAKE_SIZE;\n const font = getFont(family, weight, style)?.font;\n if (!font) return undefined;\n return (SYNTH_BOLD_AMOUNT * (font.distanceRange ?? DEFAULT_MSDF_RANGE)) / font.info.size;\n}\n"]}
|
|
@@ -119,6 +119,10 @@ interface GlyphRasterizer {
|
|
|
119
119
|
* it, or null when it draws none: no ink, or ink in the fill color alone.
|
|
120
120
|
* Drawn at `size` px, `BAKE_SIZE` when omitted, and measured in its px. */
|
|
121
121
|
rasterizeColor?(family: string, weight: number, style: FontStyle, text: string, size?: number): RasterizedColorGlyph | null;
|
|
122
|
+
/** `d`, em-space SVG path data with y down and its origin on the baseline at the pen, filled
|
|
123
|
+
* at `BAKE_SIZE` px: the glyphs a shaper chose, which `fillText` cannot address. Its
|
|
124
|
+
* `advance` is 0; the shaping has the advance. */
|
|
125
|
+
rasterizePath?(d: string): RasterizedGlyph;
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
/**
|
|
@@ -159,6 +163,13 @@ interface DynamicFace {
|
|
|
159
163
|
* not in `font.charMap`, which holds one code point each.
|
|
160
164
|
*/
|
|
161
165
|
requestCluster(text: string): BmFontChar | null;
|
|
166
|
+
/**
|
|
167
|
+
* The record for glyphs a shaper chose, which `fillText` cannot draw: `d`, em-space path data
|
|
168
|
+
* with y down and its origin at the pen on the baseline, filled and baked as `requestGlyph`'s
|
|
169
|
+
* are. `key` names `d` among this face's shaped glyphs, and `d` is read only on its first
|
|
170
|
+
* request. Null where the platform has no `Path2D` to fill with. Not in `font.charMap`.
|
|
171
|
+
*/
|
|
172
|
+
requestShaped(key: string, d: string): BmFontChar | null;
|
|
162
173
|
}
|
|
163
174
|
/** One family served by the dynamic canvas-SDF tier. See `listCanvasFonts`. */
|
|
164
175
|
interface CanvasFontEntry {
|
|
@@ -470,6 +481,12 @@ interface ResolveResult {
|
|
|
470
481
|
* renderer can apply SDF-thicken / vertex-skew fakes.
|
|
471
482
|
*/
|
|
472
483
|
declare function resolveFontVariant(family: string, weight: number, style: FontStyle): ResolveResult;
|
|
484
|
+
/**
|
|
485
|
+
* {@link resolveFontVariant} for a caller that only reads the result: an exact
|
|
486
|
+
* match is one frozen object per face rather than a new one each call, so text
|
|
487
|
+
* laid out every frame resolves its faces without allocating.
|
|
488
|
+
*/
|
|
489
|
+
declare function cachedFontVariant(family: string, weight: number, style: FontStyle): Readonly<ResolveResult>;
|
|
473
490
|
/**
|
|
474
491
|
* Escalate a *single codepoint* to the dynamic tier when the atlas that
|
|
475
492
|
* resolved for the run has no glyph for it.
|
|
@@ -689,4 +706,4 @@ declare function loadFontOutlines(family: string, variant?: OutlineVariant): Pro
|
|
|
689
706
|
/** @internal Test seam — registry and warn-once keys are module state. */
|
|
690
707
|
declare function _resetFontOutlinesForTests(): void;
|
|
691
708
|
|
|
692
|
-
export {
|
|
709
|
+
export { sharpColorPageTextureId as $, glyphOutline as A, hasFontOutlines as B, type CanvasFontEntry as C, type DynamicFace as D, isCanvasFont as E, type FaceEmbedding as F, type GlyphTextureSink as G, listCanvasFonts as H, listFontOutlines as I, listFontWeights as J, listFonts as K, loadFontOutlines as L, markAllFontsNotUploaded as M, outlineMetrics as N, type OutlineFace as O, PAGE_SIZE as P, outlineStatus as Q, type ResolveResult as R, refreshCanvasFont as S, type TexSource as T, registerCanvasFont as U, registerFont as V, registerFontOutlines as W, resolveFontVariant as X, resolveGlyphFallback as Y, setDefaultFontFamily as Z, setFontFallbackPolicy as _, type FontEntry as a, syncColorPageTexture as a0, syncDynamicPageTexture as a1, syncSharpColorPageTexture as a2, unregisterCanvasFont as a3, unregisterFontOutlines as a4, warmFonts as a5, __setGlyphRasterizerForTests as a6, _getPagesForTests as a7, _resetDomFacesForTests as a8, _resetDynamicFontsForTests as a9, _resetFallbackForTests as aa, _resetFontOutlinesForTests as ab, _resetFontRegistryForTests as ac, type FontFallbackPolicy as b, type FontFamilyFaces as c, type FontRequest as d, type FontVariant as e, type OutlineFaceInfo as f, type OutlineFontOptions as g, type OutlineParser as h, type OutlineSource as i, type OutlineStatus as j, type OutlineVariant as k, type RegisterFontOptions as l, type RegisteredFont as m, cachedFontVariant as n, colorPageTextureId as o, cssFontFamily as p, cssFontFamilyLoading as q, dynamicPageTextureId as r, ensureFontTexture as s, faceMetricsFor as t, fontOutlineRegistry as u, fontPending as v, fontRegistry as w, getDefaultFontFamily as x, getFont as y, getFontFallbackPolicy as z };
|
package/dist/test-seams.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { a6 as __setGlyphRasterizerForTests, a7 as _getPagesForTests, a8 as _resetDomFacesForTests, a9 as _resetDynamicFontsForTests, aa as _resetFallbackForTests, ab as _resetFontOutlinesForTests, ac as _resetFontRegistryForTests } from './test-seams-DUZZDLT-.js';
|
|
2
2
|
import '@weasel-js/registry';
|
|
3
3
|
import './fontStyle-D1y21YZ9.js';
|
package/dist/test-seams.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { __setGlyphRasterizerForTests, _getPagesForTests, _resetDomFacesForTests, _resetDynamicFontsForTests, _resetFallbackForTests, _resetFontOutlinesForTests, _resetFontRegistryForTests } from './chunk-
|
|
1
|
+
export { __setGlyphRasterizerForTests, _getPagesForTests, _resetDomFacesForTests, _resetDynamicFontsForTests, _resetFallbackForTests, _resetFontOutlinesForTests, _resetFontRegistryForTests } from './chunk-I7OQG2DF.js';
|
|
2
2
|
import './chunk-XQPRAGHF.js';
|
|
3
3
|
//# sourceMappingURL=test-seams.js.map
|
|
4
4
|
//# sourceMappingURL=test-seams.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weasel-js/font",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.1",
|
|
4
4
|
"description": "MSDF font atlases, glyph metrics, and runtime glyph rasterization for @weasel-js/core. Registry, kerning-aware glyph layout, and the SDF text shader source.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"provenance": true
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@weasel-js/registry": "1.
|
|
53
|
+
"@weasel-js/registry": "1.15.1",
|
|
54
54
|
"opentype.js": "2.0.0"
|
|
55
55
|
}
|
|
56
56
|
}
|