@weasel-js/font 1.7.0 → 1.7.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-CDPTPB26.js → chunk-RYN4NYX4.js} +570 -25
- package/dist/chunk-RYN4NYX4.js.map +1 -0
- package/dist/index.d.ts +33 -18
- package/dist/index.js +19 -6
- package/dist/index.js.map +1 -1
- package/dist/{test-seams-D1ex7UxJ.d.ts → test-seams-Df2Zqwk8.d.ts} +356 -90
- package/dist/test-seams.d.ts +1 -1
- package/dist/test-seams.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-CDPTPB26.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { registerFontOutlines, createOpenTypeParser } from './chunk-
|
|
2
|
-
export { DEFAULT_BAKE_BUDGET, FIXTURE_FONT, dynamicPageTextureId, ensureFontTexture, fontOutlineRegistry, fontRegistry, getDefaultFontFamily, getFont, getFontFallbackPolicy, glyphGeneration, glyphOutline, hasFontOutlines, isCanvasFont, listCanvasFonts, listFontOutlines, listFonts, markAllFontsNotUploaded, outlineMetrics, outlineStatus, parseBmFont, registerCanvasFont, registerFont, registerFontOutlines, resetBakeBudget, resolveFontVariant, resolveGlyphFallback, setDefaultFontFamily, setFontFallbackPolicy, subscribeGlyphReady, syncDynamicPageTexture, textureCacheKey, unregisterCanvasFont, unregisterFontOutlines } from './chunk-
|
|
1
|
+
import { registerFontOutlines, createOpenTypeParser, getFont, PAGE_SIZE, SDF_RADIUS } from './chunk-RYN4NYX4.js';
|
|
2
|
+
export { DEFAULT_BAKE_BUDGET, FIXTURE_FONT, cssFontFamily, cssFontFamilyLoading, dynamicPageTextureId, ensureFontTexture, faceMetricsFor, faceMetricsFromTables, faceMetricsOf, fontOutlineRegistry, fontPending, fontRegistry, getDefaultFontFamily, getFont, getFontFallbackPolicy, glyphGeneration, glyphOutline, hasFontOutlines, isCanvasFont, listCanvasFonts, listFontOutlines, listFontWeights, listFonts, loadFontOutlines, markAllFontsNotUploaded, outlineMetrics, outlineStatus, parseBmFont, registerCanvasFont, registerFont, registerFontOutlines, resetBakeBudget, resolveFontVariant, resolveGlyphFallback, setDefaultFontFamily, setFontFallbackPolicy, subscribeGlyphReady, syncDynamicPageTexture, textureCacheKey, unregisterCanvasFont, unregisterFontOutlines, verticalMetricsFromTables, warmFonts } from './chunk-RYN4NYX4.js';
|
|
3
3
|
|
|
4
4
|
// src/outline/localFonts.ts
|
|
5
5
|
function canQueryLocalFonts() {
|
|
@@ -60,7 +60,10 @@ async function enableLocalFontOutlines(opts = {}) {
|
|
|
60
60
|
registerFontOutlines(font.family, parseFontStyle(font.style), () => font.blob(), {
|
|
61
61
|
// The PostScript name is how a member is picked out of a `.ttc`
|
|
62
62
|
// collection, which is what most macOS system families ship as.
|
|
63
|
-
parser: createOpenTypeParser(font.postscriptName)
|
|
63
|
+
parser: createOpenTypeParser(font.postscriptName),
|
|
64
|
+
// The DOM already has every installed face; naming it avoids reading
|
|
65
|
+
// the collection file a second time.
|
|
66
|
+
cssSrc: `local(${JSON.stringify(font.postscriptName)})`
|
|
64
67
|
});
|
|
65
68
|
families.add(font.family);
|
|
66
69
|
}
|
|
@@ -80,15 +83,25 @@ float median(float r, float g, float b) {
|
|
|
80
83
|
return max(min(r, g), min(max(r, g), b));
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
float glyphCoverage(vec4 texel, float mode, float synthBold) {
|
|
86
|
+
float glyphCoverage(vec4 texel, float mode, float synthBold, vec2 uv, vec2 fieldPerUv) {
|
|
84
87
|
float field = mix(median(texel.r, texel.g, texel.b), texel.r, step(1.5, mode));
|
|
85
|
-
float
|
|
88
|
+
float fieldPerPx = 0.5 * (length(dFdx(uv) * fieldPerUv) + length(dFdy(uv) * fieldPerUv));
|
|
89
|
+
float aaW = max(0.5 * fieldPerPx, 0.0005);
|
|
86
90
|
float threshold = 0.5 - synthBold;
|
|
87
91
|
return smoothstep(threshold - aaW, threshold + aaW, field);
|
|
88
92
|
}
|
|
89
93
|
`
|
|
90
94
|
);
|
|
95
|
+
var DEFAULT_MSDF_RANGE = 4;
|
|
96
|
+
var CANVAS_FIELD_SCALE = [PAGE_SIZE / SDF_RADIUS, PAGE_SIZE / SDF_RADIUS];
|
|
97
|
+
function glyphFieldScale(source, family, weight, style) {
|
|
98
|
+
if (source === "canvas") return CANVAS_FIELD_SCALE;
|
|
99
|
+
const font = getFont(family, weight, style)?.font;
|
|
100
|
+
if (!font) return void 0;
|
|
101
|
+
const range = font.distanceRange ?? DEFAULT_MSDF_RANGE;
|
|
102
|
+
return [font.common.scaleW / range, font.common.scaleH / range];
|
|
103
|
+
}
|
|
91
104
|
|
|
92
|
-
export { GLYPH_COVERAGE_GLSL, GLYPH_MODE_MSDF, GLYPH_MODE_R8, canQueryLocalFonts, enableLocalFontOutlines, parseFontStyle };
|
|
105
|
+
export { GLYPH_COVERAGE_GLSL, GLYPH_MODE_MSDF, GLYPH_MODE_R8, canQueryLocalFonts, enableLocalFontOutlines, glyphFieldScale, parseFontStyle };
|
|
93
106
|
//# sourceMappingURL=index.js.map
|
|
94
107
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/outline/localFonts.ts","../src/textSdf.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\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. Everything downstream treats a refusal as ordinary: no\n * registrations, `glyphOutline` keeps answering `null`, and large text keeps\n * rendering from the SDF tier exactly as it does today. That ladder is\n * load-bearing rather than defensive — Safari and Firefox have no equivalent\n * API at 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 easily\n * have hundreds of faces and a `.ttc` runs to tens of megabytes, so\n * downloading them all at enable time would be worse than not offering the\n * tier.\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. Declared here\n * rather than pulled from a lib: `queryLocalFonts` is not in TypeScript's DOM\n * types, and the alternative — casting `window` to `any` at the call site —\n * would lose the shape of what comes back.\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/**\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, which is what the string\n * \"Regular\" would have produced anyway. A wrong guess here costs a face that\n * registers under a variant nothing asks for; it cannot mis-paint anything,\n * because resolution is an exact-match lookup that simply misses.\n */\nexport function parseFontStyle(style: string): { weight: number; style: FontStyle } {\n const s = style.toLowerCase();\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 = style.toLowerCase().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 `registerLocalFontOutlines`. */\nexport interface LocalFontOutlinesOptions {\n /**\n * Restrict registration to these families. Defaults to every installed\n * family. Worth passing when the consumer already knows its font menu:\n * a machine with 900 faces otherwise gets 900 registry entries, all inert\n * but all held.\n */\n families?: readonly string[];\n}\n\n/** What `registerLocalFontOutlines` 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; both are ordinary\n * outcomes and neither leaves the registry in a partial state, so a caller\n * that simply ignores the rejection still has a working SDF tier.\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\". Registering both left whichever `queryLocalFonts`\n // happened to return last, so a condensed face could paint at the upright\n // face's advances. Pick the least-qualified name for each slot instead.\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 });\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: text has no program of its own any more. Glyphs stage into\n * the renderer's batch alongside solid geometry and image quads, and the batch\n * shader pastes this in — which is why what lives here is a snippet rather\n * than a vertex and fragment pair. The two channel layouts a glyph atlas can\n * have are the part that belongs to this package, so they stay here.\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 — which recovers a sharp outline while averaging out\n * single-channel aliasing. The runtime canvas bake writes one channel that\n * *is* the field. Both encode the edge at 0.5, which is what lets one\n * threshold serve them.\n *\n * The single-channel bake rounds corners away from its bake size, mildest near\n * it. `glyphRasterizer.ts` carries the measurements and the reason neither a\n * larger bake nor extra taps would improve the small-text end.\n */\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 * Glyph coverage from one atlas sample, as GLSL for a program to paste in.\n *\n * `mode` says which field the sample carries: `GLYPH_MODE_MSDF` takes the\n * median of R,G,B, `GLYPH_MODE_R8` reads `.r` alone. A caller whose fragment\n * is not a glyph at all still calls this and discards the result — see below.\n *\n * The antialiasing band has to be one *screen* pixel wide, so it comes from\n * `fwidth` of the field rather than from a constant: that single quantity\n * folds in font size, zoom and DPR at once. Minify the glyph and the field\n * changes faster between neighboring fragments, so the band widens in field\n * units to stay one pixel on screen; magnify it and the band narrows. A\n * constant band cannot be right at more than one scale, and this was one\n * (0.05) until 2026-07-29 — at 16px it fell well under a pixel and edges\n * quantized to stair-steps, while display sizes read mushy. The `max()` floor\n * keeps a degenerate derivative — a flat field, or a driver answering 0 — from\n * collapsing the band back to that.\n *\n * **Nothing here branches, and the caller must not branch around it.**\n * `fwidth` in non-uniform control flow is undefined, so the derivative has to\n * be taken before anything selects on paint mode. That is why a merged program\n * runs the glyph math on fragments that are not glyphs, and it is why the two\n * fields are selected with a `mix` rather than an `if`.\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 = /* glsl */ `\nfloat median(float r, float g, float b) {\n return max(min(r, g), min(max(r, g), b));\n}\n\nfloat glyphCoverage(vec4 texel, float mode, float synthBold) {\n float field = mix(median(texel.r, texel.g, texel.b), texel.r, step(1.5, mode));\n float aaW = max(0.5 * fwidth(field), 0.0005);\n float threshold = 0.5 - synthBold;\n return smoothstep(threshold - aaW, threshold + aaW, field);\n}\n`;\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/outline/localFonts.ts","../src/textSdf.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\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. Everything downstream treats a refusal as ordinary: no\n * registrations, `glyphOutline` keeps answering `null`, and large text keeps\n * rendering from the SDF tier exactly as it does today. That ladder is\n * load-bearing rather than defensive — Safari and Firefox have no equivalent\n * API at 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 easily\n * have hundreds of faces and a `.ttc` runs to tens of megabytes, so\n * downloading them all at enable time would be worse than not offering the\n * tier.\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. Declared here\n * rather than pulled from a lib: `queryLocalFonts` is not in TypeScript's DOM\n * types, and the alternative — casting `window` to `any` at the call site —\n * would lose the shape of what comes back.\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/**\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, which is what the string\n * \"Regular\" would have produced anyway. A wrong guess here costs a face that\n * registers under a variant nothing asks for; it cannot mis-paint anything,\n * because resolution is an exact-match lookup that simply misses.\n */\nexport function parseFontStyle(style: string): { weight: number; style: FontStyle } {\n const s = style.toLowerCase();\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 = style.toLowerCase().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 `registerLocalFontOutlines`. */\nexport interface LocalFontOutlinesOptions {\n /**\n * Restrict registration to these families. Defaults to every installed\n * family. Worth passing when the consumer already knows its font menu:\n * a machine with 900 faces otherwise gets 900 registry entries, all inert\n * but all held.\n */\n families?: readonly string[];\n}\n\n/** What `registerLocalFontOutlines` 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; both are ordinary\n * outcomes and neither leaves the registry in a partial state, so a caller\n * that simply ignores the rejection still has a working SDF tier.\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\". Registering both left whichever `queryLocalFonts`\n // happened to return last, so a condensed face could paint at the upright\n // face's advances. Pick the least-qualified name for each slot instead.\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: text has no program of its own any more. Glyphs stage into\n * the renderer's batch alongside solid geometry and image quads, and the batch\n * shader pastes this in — which is why what lives here is a snippet rather\n * than a vertex and fragment pair. The two channel layouts a glyph atlas can\n * have are the part that belongs to this package, so they stay here.\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 — which recovers a sharp outline while averaging out\n * single-channel aliasing. The runtime canvas bake writes one channel that\n * *is* the field. Both encode the edge at 0.5, which is what lets one\n * threshold serve them.\n *\n * The single-channel bake rounds corners away from its bake size, mildest near\n * it. `glyphRasterizer.ts` carries the measurements and the reason neither a\n * larger bake nor extra taps would improve the small-text end.\n */\n\nimport type { FontStyle } from './fontStyle';\nimport { getFont } from './registerFont';\nimport { PAGE_SIZE, SDF_RADIUS } from './dynamic/dynamicAtlas';\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 * Glyph coverage from one atlas sample, as GLSL for a program to paste in.\n *\n * `mode` says which field the sample carries: `GLYPH_MODE_MSDF` takes the\n * median of R,G,B, `GLYPH_MODE_R8` reads `.r` alone. A caller whose fragment\n * is not a glyph at all still calls this and discards the result — see below.\n *\n * The antialiasing band has to be one *screen* pixel wide, so it is measured\n * from how fast the atlas moves under the fragment: `fieldPerUv` is how far\n * 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 — one quantity folding in font size, zoom and DPR.\n *\n * Not `fwidth(field)`, which it was until 2026-09-29: that is the field's\n * slope only where the field is a straight ramp. Across a stem narrower than a\n * pixel — a 12px superscript's, minified 4x off the atlas — the two columns of\n * a 2x2 quad sample either side of the ridge at equal values, the derivative\n * reads flat, the band collapses to the floor, and the stem is thresholded\n * away. The derivative of `uv` is constant across a glyph quad, so it cannot\n * do that. Nor a constant band, which was 0.05 until 2026-07-29 and right at\n * one scale only. The `max()` floor keeps a degenerate derivative from\n * collapsing the band.\n *\n * **Nothing here branches, and the caller must not branch around it.**\n * A derivative in non-uniform control flow is undefined, so it has to\n * be taken before anything selects on paint mode. That is why a merged program\n * runs the glyph math on fragments that are not glyphs, and it is why the two\n * fields are selected with a `mix` rather than an `if`.\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 = /* glsl */ `\nfloat median(float r, float g, float b) {\n return max(min(r, g), min(max(r, g), b));\n}\n\nfloat glyphCoverage(vec4 texel, float mode, float synthBold, vec2 uv, vec2 fieldPerUv) {\n float field = mix(median(texel.r, texel.g, texel.b), texel.r, step(1.5, mode));\n float fieldPerPx = 0.5 * (length(dFdx(uv) * fieldPerUv) + length(dFdy(uv) * fieldPerUv));\n float aaW = max(0.5 * fieldPerPx, 0.0005);\n float threshold = 0.5 - synthBold;\n return smoothstep(threshold - aaW, threshold + aaW, field);\n}\n`;\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 return [font.common.scaleW / range, font.common.scaleH / range];\n}\n"]}
|
|
@@ -1,85 +1,5 @@
|
|
|
1
1
|
import { Reflection } from '@weasel-js/registry';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* BmFont JSON types and parser for the MSDF atlas format produced by
|
|
5
|
-
* msdf-bmfont-xml. The parser builds accelerator maps (charMap, kerningMap)
|
|
6
|
-
* for O(1) glyph and kerning lookup during layout.
|
|
7
|
-
*/
|
|
8
|
-
interface BmFontInfo {
|
|
9
|
-
face: string;
|
|
10
|
-
size: number;
|
|
11
|
-
}
|
|
12
|
-
/** Font-wide vertical metrics and the atlas page dimensions, in pixels. */
|
|
13
|
-
interface BmFontCommon {
|
|
14
|
-
lineHeight: number;
|
|
15
|
-
base: number;
|
|
16
|
-
scaleW: number;
|
|
17
|
-
scaleH: number;
|
|
18
|
-
}
|
|
19
|
-
/** One glyph: where it sits in the atlas page (`x`/`y`/`width`/`height`),
|
|
20
|
-
* where to draw it relative to the pen (`xoffset`/`yoffset`), and how far the
|
|
21
|
-
* pen then advances. */
|
|
22
|
-
interface BmFontChar {
|
|
23
|
-
id: number;
|
|
24
|
-
x: number;
|
|
25
|
-
y: number;
|
|
26
|
-
width: number;
|
|
27
|
-
height: number;
|
|
28
|
-
xoffset: number;
|
|
29
|
-
yoffset: number;
|
|
30
|
-
xadvance: number;
|
|
31
|
-
page: number;
|
|
32
|
-
}
|
|
33
|
-
/** A kerning pair: `amount` px to add to the advance when `second` follows
|
|
34
|
-
* `first`. Codepoints, not glyph indices. */
|
|
35
|
-
interface BmFontKerning {
|
|
36
|
-
first: number;
|
|
37
|
-
second: number;
|
|
38
|
-
amount: number;
|
|
39
|
-
}
|
|
40
|
-
/** A parsed MSDF atlas: the BmFont record plus the `charMap` / `kerningMap`
|
|
41
|
-
* accelerators layout reads per glyph. */
|
|
42
|
-
interface BmFont {
|
|
43
|
-
info: BmFontInfo;
|
|
44
|
-
common: BmFontCommon;
|
|
45
|
-
chars: BmFontChar[];
|
|
46
|
-
kernings: BmFontKerning[];
|
|
47
|
-
charMap: Map<number, BmFontChar>;
|
|
48
|
-
kerningMap: Map<number, Map<number, number>>;
|
|
49
|
-
}
|
|
50
|
-
/** Two-glyph fixture for unit tests. */
|
|
51
|
-
declare const FIXTURE_FONT: {
|
|
52
|
-
info: {
|
|
53
|
-
face: string;
|
|
54
|
-
size: number;
|
|
55
|
-
};
|
|
56
|
-
common: {
|
|
57
|
-
lineHeight: number;
|
|
58
|
-
base: number;
|
|
59
|
-
scaleW: number;
|
|
60
|
-
scaleH: number;
|
|
61
|
-
};
|
|
62
|
-
chars: {
|
|
63
|
-
id: number;
|
|
64
|
-
x: number;
|
|
65
|
-
y: number;
|
|
66
|
-
width: number;
|
|
67
|
-
height: number;
|
|
68
|
-
xoffset: number;
|
|
69
|
-
yoffset: number;
|
|
70
|
-
xadvance: number;
|
|
71
|
-
page: number;
|
|
72
|
-
}[];
|
|
73
|
-
kernings: {
|
|
74
|
-
first: number;
|
|
75
|
-
second: number;
|
|
76
|
-
amount: number;
|
|
77
|
-
}[];
|
|
78
|
-
};
|
|
79
|
-
/** Validate a BmFont JSON document and build its lookup maps. Throws with the
|
|
80
|
-
* missing or malformed field named. */
|
|
81
|
-
declare function parseBmFont(raw: unknown): BmFont;
|
|
82
|
-
|
|
83
3
|
/**
|
|
84
4
|
* The style axis of a font variant, one declaration for the whole stack.
|
|
85
5
|
*
|
|
@@ -147,7 +67,7 @@ interface GlyphTextureSink {
|
|
|
147
67
|
* mip levels blend across glyph rects).
|
|
148
68
|
*/
|
|
149
69
|
|
|
150
|
-
interface FaceMetrics {
|
|
70
|
+
interface FaceMetrics$1 {
|
|
151
71
|
ascent: number;
|
|
152
72
|
descent: number;
|
|
153
73
|
}
|
|
@@ -160,7 +80,7 @@ interface RasterizedGlyph {
|
|
|
160
80
|
advance: number;
|
|
161
81
|
}
|
|
162
82
|
interface GlyphRasterizer {
|
|
163
|
-
faceMetrics(family: string, weight: number, style: FontStyle): FaceMetrics;
|
|
83
|
+
faceMetrics(family: string, weight: number, style: FontStyle): FaceMetrics$1;
|
|
164
84
|
rasterize(family: string, weight: number, style: FontStyle, codepoint: number): RasterizedGlyph;
|
|
165
85
|
}
|
|
166
86
|
|
|
@@ -299,6 +219,7 @@ declare function _resetDynamicFontsForTests(): void;
|
|
|
299
219
|
* parse happens once per glyph, and core caches the *tessellation*, not the
|
|
300
220
|
* text.
|
|
301
221
|
*/
|
|
222
|
+
|
|
302
223
|
/** A parsed font face, viewed only as a source of glyph outlines. See the
|
|
303
224
|
* module comment for the em-space contract `glyphD` returns. */
|
|
304
225
|
interface OutlineFace {
|
|
@@ -309,11 +230,16 @@ interface OutlineFace {
|
|
|
309
230
|
* Distance from the line top to the baseline, in em units — the outline
|
|
310
231
|
* tier's analogue of a `BmFont`'s `common.base`.
|
|
311
232
|
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
233
|
+
* The default parser reports `faceMetrics.ascent`, the ascent a browser
|
|
234
|
+
* sets the face with, so a face used for metrics puts text on the same
|
|
235
|
+
* baseline as an atlas of the same font and as the DOM edit overlay.
|
|
315
236
|
*/
|
|
316
237
|
ascender: number;
|
|
238
|
+
/** The face's decoration and script metrics, derived with
|
|
239
|
+
* `faceMetricsFromTables` so they match an atlas baked from the same
|
|
240
|
+
* font. Optional: a custom parser that omits it leaves layout on its
|
|
241
|
+
* defaults. */
|
|
242
|
+
faceMetrics?: FaceMetrics;
|
|
317
243
|
/**
|
|
318
244
|
* Em-space SVG path data for `cp`, or `null` when this face has no glyph
|
|
319
245
|
* for the codepoint *or* the glyph has no contours (a space). Both answers
|
|
@@ -367,6 +293,15 @@ declare const fontRegistry: Reflection<FontFamilyFaces>;
|
|
|
367
293
|
declare function _resetFontRegistryForTests(): void;
|
|
368
294
|
/** Exact lookup — does NOT walk the fallback chain. Use `resolveFontVariant` for that. */
|
|
369
295
|
declare function getFont(family: string, weight?: number, style?: FontStyle): FontEntry | null;
|
|
296
|
+
/**
|
|
297
|
+
* The decoration and script metrics of an exact `(family, weight, style)` —
|
|
298
|
+
* the atlas's when one is registered, else a loaded outline face's. Like
|
|
299
|
+
* {@link getFont}, it walks no fallback chain and enrolls no canvas font,
|
|
300
|
+
* which suits a caller holding a CSS font name rather than a layout result.
|
|
301
|
+
* An idle outline face starts loading, as it would for layout.
|
|
302
|
+
* A run already resolved for layout reads `faceMetricsOf(result)` instead.
|
|
303
|
+
*/
|
|
304
|
+
declare function faceMetricsFor(family: string, weight?: number, style?: FontStyle): FaceMetrics | undefined;
|
|
370
305
|
/** One family in the registry and the variants registered for it — what a
|
|
371
306
|
* font picker can honestly offer. */
|
|
372
307
|
interface RegisteredFont {
|
|
@@ -383,11 +318,72 @@ interface RegisteredFont {
|
|
|
383
318
|
*/
|
|
384
319
|
declare function listFonts(): readonly RegisteredFont[];
|
|
385
320
|
/**
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
321
|
+
* The weights `family` has a real face for, on the atlas tier or the outline
|
|
322
|
+
* tier, in either style — what a weight picker can honestly offer. Ascending
|
|
323
|
+
* and deduplicated; empty for a family nothing registered. An outline face
|
|
324
|
+
* whose load failed is left out. The canvas tier is not consulted: it
|
|
325
|
+
* rasterizes whatever weight it is asked for, so it has no list to report.
|
|
326
|
+
*/
|
|
327
|
+
declare function listFontWeights(family: string): readonly number[];
|
|
328
|
+
/** Options for {@link registerFont}. */
|
|
329
|
+
interface RegisterFontOptions {
|
|
330
|
+
/**
|
|
331
|
+
* Fetch nothing until text first resolves this face. Layout treats the face
|
|
332
|
+
* as pending from registration on, so a run set in it lays out as nothing —
|
|
333
|
+
* never in a fallback face's metrics — and relays out when the atlas lands.
|
|
334
|
+
* The returned promise settles with that load, so it never settles for a
|
|
335
|
+
* face no text uses.
|
|
336
|
+
*/
|
|
337
|
+
lazy?: boolean;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Register a baked MSDF atlas and its metrics as one variant of `family`.
|
|
341
|
+
* Resolves once the face is usable; re-registering a variant that is already
|
|
342
|
+
* present is a no-op.
|
|
343
|
+
*
|
|
344
|
+
* Eager by default: both files are fetched now. With `{ lazy: true }` they are
|
|
345
|
+
* fetched the first time text asks for the family, which keeps the atlas off
|
|
346
|
+
* the critical path of anything that draws no text.
|
|
347
|
+
*/
|
|
348
|
+
declare function registerFont(family: string, variant: FontVariant, metricsUrl: string, atlasUrl: string, opts?: RegisterFontOptions): Promise<void>;
|
|
349
|
+
/** One face text is set in, the way a render asks for it — what
|
|
350
|
+
* {@link warmFonts} takes to load that face and nothing else. Weight
|
|
351
|
+
* defaults to 400 and style to `'normal'`. */
|
|
352
|
+
interface FontRequest {
|
|
353
|
+
family: string;
|
|
354
|
+
weight?: number;
|
|
355
|
+
style?: FontStyle;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Load registered atlases ahead of their first use, so a synchronous render —
|
|
359
|
+
* `renderSceneToPixels`, an export, a print — draws text set in a lazily
|
|
360
|
+
* registered face rather than nothing. With no list, loads every family
|
|
361
|
+
* `registerFont` was called for.
|
|
362
|
+
*
|
|
363
|
+
* A family name loads every variant registered for it. A {@link FontRequest}
|
|
364
|
+
* loads what resolving that one variant would draw with: the exact variant
|
|
365
|
+
* when one was registered, otherwise every variant of the family, since the
|
|
366
|
+
* within-family chain may land on any; the family the `'substitute'` policy
|
|
367
|
+
* would draw instead when the requested one has no atlas; and the variant's
|
|
368
|
+
* outline face when one is registered.
|
|
369
|
+
*
|
|
370
|
+
* Starts each lazily declared variant and joins any fetch already running.
|
|
371
|
+
* Resolves once they have all landed; a registered variant counts as loaded.
|
|
372
|
+
* Rejects when a load fails, or for a family name `registerFont` never saw —
|
|
373
|
+
* a request for such a family is not an error, since the canvas and outline
|
|
374
|
+
* tiers serve families no atlas was registered for.
|
|
389
375
|
*/
|
|
390
|
-
declare function
|
|
376
|
+
declare function warmFonts(fonts?: readonly (string | FontRequest)[]): Promise<void>;
|
|
377
|
+
/**
|
|
378
|
+
* Could a registration that has not landed yet still serve this request?
|
|
379
|
+
* True while any `registerFont` for the family is fetching — the within-family
|
|
380
|
+
* chain may resolve to whichever variant it brings — or while an outline face
|
|
381
|
+
* registered for this exact variant has not finished loading.
|
|
382
|
+
*
|
|
383
|
+
* A face that resolves to nothing right now is either missing or merely late;
|
|
384
|
+
* this tells the two apart, so "not registered" is reported only when it is.
|
|
385
|
+
*/
|
|
386
|
+
declare function fontPending(family: string, weight?: number, style?: FontStyle): boolean;
|
|
391
387
|
/**
|
|
392
388
|
* Ensure the atlas for `(family, weight, style)` is uploaded to
|
|
393
389
|
* `textureCache`. Cache key is `${family}|${weight}|${style}` so each
|
|
@@ -479,6 +475,206 @@ declare function resolveFontVariant(family: string, weight: number, style: FontS
|
|
|
479
475
|
*/
|
|
480
476
|
declare function resolveGlyphFallback(family: string, weight: number, style: FontStyle): ResolveResult | null;
|
|
481
477
|
|
|
478
|
+
/**
|
|
479
|
+
* The line, decoration and script metrics a font states for itself, in the
|
|
480
|
+
* kit's units: em fractions, with a rule's `offset` measured *down* from the
|
|
481
|
+
* baseline to its top edge (y grows down) and a script's `shift` positive
|
|
482
|
+
* for a rise.
|
|
483
|
+
*
|
|
484
|
+
* Both glyph tiers carry the same record — an atlas bakes it into its JSON,
|
|
485
|
+
* a parsed face reads it off the font's tables — and both derive it through
|
|
486
|
+
* {@link faceMetricsFromTables}, so one font yields one set of numbers
|
|
487
|
+
* whichever tier ends up serving a run.
|
|
488
|
+
*/
|
|
489
|
+
|
|
490
|
+
/** One rule: the top edge's distance below the baseline, and its weight. */
|
|
491
|
+
interface FaceRuleMetrics {
|
|
492
|
+
readonly offset: number;
|
|
493
|
+
readonly thickness: number;
|
|
494
|
+
}
|
|
495
|
+
/** One script preset: `size` scales the inherited font size, `shift` raises
|
|
496
|
+
* (positive) or lowers (negative) the baseline, in inherited ems. */
|
|
497
|
+
interface FaceScriptMetrics {
|
|
498
|
+
readonly size: number;
|
|
499
|
+
readonly shift: number;
|
|
500
|
+
}
|
|
501
|
+
/** What a face says about its line, rules, scripts and letter heights.
|
|
502
|
+
* Every field is optional: a font may carry some of these tables and not
|
|
503
|
+
* others. */
|
|
504
|
+
interface FaceMetrics {
|
|
505
|
+
/** Baseline to the top of the face's line box, in ems — the ascent a
|
|
506
|
+
* browser sets this face with. See {@link verticalMetricsFromTables}. */
|
|
507
|
+
readonly ascent?: number;
|
|
508
|
+
/** Baseline to the bottom of the face's line box, in ems, positive
|
|
509
|
+
* downward. Present exactly when `ascent` is. */
|
|
510
|
+
readonly descent?: number;
|
|
511
|
+
readonly underline?: FaceRuleMetrics;
|
|
512
|
+
readonly strikethrough?: FaceRuleMetrics;
|
|
513
|
+
readonly superscript?: FaceScriptMetrics;
|
|
514
|
+
readonly subscript?: FaceScriptMetrics;
|
|
515
|
+
/** Height of a flat lowercase letter above the baseline, in ems
|
|
516
|
+
* (`OS/2.sxHeight`). */
|
|
517
|
+
readonly xHeight?: number;
|
|
518
|
+
/** Height of a flat capital above the baseline, in ems
|
|
519
|
+
* (`OS/2.sCapHeight`). */
|
|
520
|
+
readonly capHeight?: number;
|
|
521
|
+
}
|
|
522
|
+
/** The table fields {@link faceMetricsFromTables} reads, in font units, as
|
|
523
|
+
* the OpenType `post` and `OS/2` tables state them. */
|
|
524
|
+
interface FaceMetricTables {
|
|
525
|
+
unitsPerEm: number;
|
|
526
|
+
hhea?: {
|
|
527
|
+
ascender?: number;
|
|
528
|
+
descender?: number;
|
|
529
|
+
};
|
|
530
|
+
post?: {
|
|
531
|
+
underlinePosition?: number;
|
|
532
|
+
underlineThickness?: number;
|
|
533
|
+
};
|
|
534
|
+
os2?: {
|
|
535
|
+
fsSelection?: number;
|
|
536
|
+
sTypoAscender?: number;
|
|
537
|
+
sTypoDescender?: number;
|
|
538
|
+
usWinAscent?: number;
|
|
539
|
+
usWinDescent?: number;
|
|
540
|
+
yStrikeoutPosition?: number;
|
|
541
|
+
yStrikeoutSize?: number;
|
|
542
|
+
ySuperscriptYSize?: number;
|
|
543
|
+
ySuperscriptYOffset?: number;
|
|
544
|
+
ySubscriptYSize?: number;
|
|
545
|
+
ySubscriptYOffset?: number;
|
|
546
|
+
sxHeight?: number;
|
|
547
|
+
sCapHeight?: number;
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* The ascent and descent a browser sets this face with, in ems, descent
|
|
552
|
+
* positive downward.
|
|
553
|
+
*
|
|
554
|
+
* The OpenType rule: `OS/2` `sTypoAscender`/`sTypoDescender` when the font
|
|
555
|
+
* sets `USE_TYPO_METRICS`, otherwise `hhea`. It is the browser's rule rather
|
|
556
|
+
* than a typographer's because the DOM edit overlay stands in for the canvas
|
|
557
|
+
* and can only ever get the browser's numbers. Firefox, and every engine on
|
|
558
|
+
* Linux, follow it; Chromium and WebKit on macOS read `hhea` even when the
|
|
559
|
+
* flag is set, and the overlay's measured baseline correction absorbs the
|
|
560
|
+
* difference there. `verticalMetrics.browser.test.ts` pins both.
|
|
561
|
+
*
|
|
562
|
+
* Falls back to the typo pair, then the win pair, when `hhea` is absent or
|
|
563
|
+
* zero.
|
|
564
|
+
*/
|
|
565
|
+
declare function verticalMetricsFromTables(t: FaceMetricTables): {
|
|
566
|
+
ascent: number;
|
|
567
|
+
descent: number;
|
|
568
|
+
} | undefined;
|
|
569
|
+
/**
|
|
570
|
+
* Convert raw table values to {@link FaceMetrics}.
|
|
571
|
+
*
|
|
572
|
+
* `post.underlinePosition` and `OS/2.yStrikeoutPosition` are the rule's top
|
|
573
|
+
* edge, positive above the baseline. `ySubscriptYOffset` is positive
|
|
574
|
+
* *downward*, unlike its superscript twin. A zero weight or size is a font
|
|
575
|
+
* that left the field blank, not one asking for an invisible rule, so that
|
|
576
|
+
* entry is dropped and the caller's default applies — which is also how an
|
|
577
|
+
* `OS/2` table older than version 2 reads, since it has no heights at all.
|
|
578
|
+
*/
|
|
579
|
+
declare function faceMetricsFromTables(t: FaceMetricTables): FaceMetrics | undefined;
|
|
580
|
+
/**
|
|
581
|
+
* The face metrics behind a resolved run — the outline face's on the outline
|
|
582
|
+
* tier, the atlas's on the atlas tier. The canvas tier rasterizes through
|
|
583
|
+
* `CanvasRenderingContext2D`, which exposes no font tables, so it has none
|
|
584
|
+
* and callers fall back to their defaults.
|
|
585
|
+
*/
|
|
586
|
+
declare function faceMetricsOf(result: ResolveResult): FaceMetrics | undefined;
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* BmFont JSON types and parser for the MSDF atlas format produced by
|
|
590
|
+
* msdf-bmfont-xml. The parser builds accelerator maps (charMap, kerningMap)
|
|
591
|
+
* for O(1) glyph and kerning lookup during layout.
|
|
592
|
+
*/
|
|
593
|
+
|
|
594
|
+
interface BmFontInfo {
|
|
595
|
+
face: string;
|
|
596
|
+
size: number;
|
|
597
|
+
}
|
|
598
|
+
/** Font-wide vertical metrics and the atlas page dimensions, in pixels. */
|
|
599
|
+
interface BmFontCommon {
|
|
600
|
+
lineHeight: number;
|
|
601
|
+
base: number;
|
|
602
|
+
scaleW: number;
|
|
603
|
+
scaleH: number;
|
|
604
|
+
}
|
|
605
|
+
/** One glyph: where it sits in the atlas page (`x`/`y`/`width`/`height`),
|
|
606
|
+
* where to draw it relative to the pen (`xoffset`/`yoffset`), and how far the
|
|
607
|
+
* pen then advances. */
|
|
608
|
+
interface BmFontChar {
|
|
609
|
+
id: number;
|
|
610
|
+
x: number;
|
|
611
|
+
y: number;
|
|
612
|
+
width: number;
|
|
613
|
+
height: number;
|
|
614
|
+
xoffset: number;
|
|
615
|
+
yoffset: number;
|
|
616
|
+
xadvance: number;
|
|
617
|
+
page: number;
|
|
618
|
+
}
|
|
619
|
+
/** A kerning pair: `amount` px to add to the advance when `second` follows
|
|
620
|
+
* `first`. Codepoints, not glyph indices. */
|
|
621
|
+
interface BmFontKerning {
|
|
622
|
+
first: number;
|
|
623
|
+
second: number;
|
|
624
|
+
amount: number;
|
|
625
|
+
}
|
|
626
|
+
/** A parsed MSDF atlas: the BmFont record plus the `charMap` / `kerningMap`
|
|
627
|
+
* accelerators layout reads per glyph. */
|
|
628
|
+
interface BmFont {
|
|
629
|
+
info: BmFontInfo;
|
|
630
|
+
common: BmFontCommon;
|
|
631
|
+
chars: BmFontChar[];
|
|
632
|
+
kernings: BmFontKerning[];
|
|
633
|
+
charMap: Map<number, BmFontChar>;
|
|
634
|
+
kerningMap: Map<number, Map<number, number>>;
|
|
635
|
+
/** The font's own decoration and script metrics, when the bake recorded
|
|
636
|
+
* them. Not part of the BMFont format: `gen-font` adds it as a top-level
|
|
637
|
+
* `faceMetrics` block, and an atlas without one leaves layout on its
|
|
638
|
+
* defaults. */
|
|
639
|
+
faceMetrics?: FaceMetrics;
|
|
640
|
+
/** Atlas texels over which the distance field runs from 0 to 1, edge at
|
|
641
|
+
* 0.5 — msdf-bmfont-xml's `distanceField.distanceRange`. Absent when the
|
|
642
|
+
* file does not record it. */
|
|
643
|
+
distanceRange?: number;
|
|
644
|
+
}
|
|
645
|
+
/** Two-glyph fixture for unit tests. */
|
|
646
|
+
declare const FIXTURE_FONT: {
|
|
647
|
+
info: {
|
|
648
|
+
face: string;
|
|
649
|
+
size: number;
|
|
650
|
+
};
|
|
651
|
+
common: {
|
|
652
|
+
lineHeight: number;
|
|
653
|
+
base: number;
|
|
654
|
+
scaleW: number;
|
|
655
|
+
scaleH: number;
|
|
656
|
+
};
|
|
657
|
+
chars: {
|
|
658
|
+
id: number;
|
|
659
|
+
x: number;
|
|
660
|
+
y: number;
|
|
661
|
+
width: number;
|
|
662
|
+
height: number;
|
|
663
|
+
xoffset: number;
|
|
664
|
+
yoffset: number;
|
|
665
|
+
xadvance: number;
|
|
666
|
+
page: number;
|
|
667
|
+
}[];
|
|
668
|
+
kernings: {
|
|
669
|
+
first: number;
|
|
670
|
+
second: number;
|
|
671
|
+
amount: number;
|
|
672
|
+
}[];
|
|
673
|
+
};
|
|
674
|
+
/** Validate a BmFont JSON document and build its lookup maps. Throws with the
|
|
675
|
+
* missing or malformed field named. */
|
|
676
|
+
declare function parseBmFont(raw: unknown): BmFont;
|
|
677
|
+
|
|
482
678
|
/**
|
|
483
679
|
* Cross-family fallback policy. The per-family chain in `resolveFontVariant`
|
|
484
680
|
* (weight → style → synthetic) has always been rich; what was missing is what
|
|
@@ -511,6 +707,57 @@ declare function getDefaultFontFamily(): string | null;
|
|
|
511
707
|
/** Test helper. Do not call from product code. */
|
|
512
708
|
declare function _resetFallbackForTests(): void;
|
|
513
709
|
|
|
710
|
+
/**
|
|
711
|
+
* The face the canvas draws, handed to DOM text.
|
|
712
|
+
*
|
|
713
|
+
* A family name means one thing to the registry and another to CSS: the kit
|
|
714
|
+
* may draw `sans-serif` from a baked Inter atlas while a `font-family:
|
|
715
|
+
* sans-serif` in the page resolves to Helvetica. Anything the DOM sets in
|
|
716
|
+
* place of canvas text — the text edit overlay above all — asks here instead
|
|
717
|
+
* of writing the name through. The answer names a private `FontFace` loaded
|
|
718
|
+
* from the family's registered font file, with the family itself as fallback
|
|
719
|
+
* while that loads.
|
|
720
|
+
*
|
|
721
|
+
* The font file is the one `registerFontOutlines` holds; that registry is the
|
|
722
|
+
* kit's record of which faces have real bytes behind them. An atlas-only
|
|
723
|
+
* family has none, so the DOM gets the browser's own face for the name and a
|
|
724
|
+
* one-time warning says which registration would fix it.
|
|
725
|
+
*/
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* The CSS `font-family` value that sets DOM text in the face the canvas draws
|
|
729
|
+
* for `family` at this variant.
|
|
730
|
+
*
|
|
731
|
+
* - A family the canvas draws through the browser (`registerCanvasFont`, or
|
|
732
|
+
* the `'canvas'` fallback policy) comes back unchanged: the DOM already
|
|
733
|
+
* resolves the name to the same face.
|
|
734
|
+
* - A family drawn from a baked atlas or from outlines comes back as a private
|
|
735
|
+
* face built from its `registerFontOutlines` file — every registered weight
|
|
736
|
+
* and style of it, so the browser picks and synthesizes within the family
|
|
737
|
+
* the way the canvas does — followed by `family` as the fallback.
|
|
738
|
+
* - Under the `'substitute'` policy, the face is the substitute's.
|
|
739
|
+
*
|
|
740
|
+
* Starts loading the face on first call and never waits for it; ask
|
|
741
|
+
* {@link cssFontFamilyLoading} for that. A face whose bytes arrived earlier —
|
|
742
|
+
* passed to `registerFontOutlines` as an `ArrayBuffer`, or read for the canvas
|
|
743
|
+
* — was built when they did. Outside a browser, `family` comes back unchanged.
|
|
744
|
+
*/
|
|
745
|
+
declare function cssFontFamily(family: string, variant?: FontVariant): string;
|
|
746
|
+
/**
|
|
747
|
+
* The load still in flight for the face {@link cssFontFamily} names for
|
|
748
|
+
* `family` at this variant, or `null` when there is nothing to wait for: it
|
|
749
|
+
* has loaded or failed, or the DOM uses `family` as it stands. Waits on the
|
|
750
|
+
* variant's own face when one is registered and on the whole family
|
|
751
|
+
* otherwise, since the browser then picks among them. Starts the load like
|
|
752
|
+
* `cssFontFamily` does, and never rejects.
|
|
753
|
+
*
|
|
754
|
+
* `null` rather than a settled promise, so a caller whose face is loaded goes
|
|
755
|
+
* on in the same task instead of a microtask later.
|
|
756
|
+
*/
|
|
757
|
+
declare function cssFontFamilyLoading(family: string, variant?: FontVariant): Promise<void> | null;
|
|
758
|
+
/** @internal Test seam — module state. */
|
|
759
|
+
declare function _resetDomFacesForTests(): void;
|
|
760
|
+
|
|
514
761
|
/**
|
|
515
762
|
* The outline tier's registry: which faces have real font bytes behind them,
|
|
516
763
|
* and what a given glyph looks like.
|
|
@@ -581,6 +828,14 @@ interface OutlineFontOptions {
|
|
|
581
828
|
/** Override the default opentype.js parser. Mostly a test seam; also the
|
|
582
829
|
* hook for a consumer who already has a font parser in their bundle. */
|
|
583
830
|
parser?: OutlineParser;
|
|
831
|
+
/**
|
|
832
|
+
* The `@font-face` `src` that gives DOM text this face — what the text edit
|
|
833
|
+
* overlay sets its glyphs in, so they match the canvas's. Defaults to
|
|
834
|
+
* `url(…)` of a URL source and to the bytes themselves otherwise; a face
|
|
835
|
+
* whose bytes the browser cannot load directly, such as one member of a
|
|
836
|
+
* `.ttc`, names it here instead (`local("Helvetica-Bold")`).
|
|
837
|
+
*/
|
|
838
|
+
cssSrc?: string;
|
|
584
839
|
}
|
|
585
840
|
/** Load state of one registered face. */
|
|
586
841
|
type OutlineStatus = 'idle' | 'loading' | 'ready' | 'failed';
|
|
@@ -648,7 +903,18 @@ declare function glyphOutline(family: string, weight: number, style: FontStyle,
|
|
|
648
903
|
* frame after the bytes land.
|
|
649
904
|
*/
|
|
650
905
|
declare function outlineMetrics(family: string, weight: number, style: FontStyle): OutlineFace | null;
|
|
906
|
+
/**
|
|
907
|
+
* Load a registered face's bytes now and resolve with its settled status:
|
|
908
|
+
* `'ready'`, `'failed'`, or `null` when nothing is registered for the
|
|
909
|
+
* variant. Never rejects — a failed load is a status, the same one
|
|
910
|
+
* `outlineStatus` reports.
|
|
911
|
+
*
|
|
912
|
+
* The per-frame readers start a load and answer `null` until it lands, which
|
|
913
|
+
* suits a renderer and nothing that needs the geometry in one go: converting
|
|
914
|
+
* text to paths awaits this first.
|
|
915
|
+
*/
|
|
916
|
+
declare function loadFontOutlines(family: string, variant?: OutlineVariant): Promise<OutlineStatus | null>;
|
|
651
917
|
/** @internal Test seam — registry and warn-once keys are module state. */
|
|
652
918
|
declare function _resetFontOutlinesForTests(): void;
|
|
653
919
|
|
|
654
|
-
export {
|
|
920
|
+
export { outlineStatus as $, faceMetricsFor as A, type BmFont as B, type CanvasFontEntry as C, DEFAULT_BAKE_BUDGET as D, faceMetricsFromTables as E, type FontStyle as F, type GlyphTextureSink as G, faceMetricsOf as H, fontOutlineRegistry as I, fontPending as J, fontRegistry as K, getDefaultFontFamily as L, getFont as M, getFontFallbackPolicy as N, type OutlineFace as O, glyphOutline as P, hasFontOutlines as Q, type RegisterFontOptions as R, isCanvasFont as S, type TexSource as T, listCanvasFonts as U, listFontOutlines as V, listFontWeights as W, listFonts as X, loadFontOutlines as Y, markAllFontsNotUploaded as Z, outlineMetrics as _, type BmFontChar as a, parseBmFont as a0, registerCanvasFont as a1, registerFont as a2, registerFontOutlines as a3, resetBakeBudget as a4, resolveFontVariant as a5, resolveGlyphFallback as a6, setDefaultFontFamily as a7, setFontFallbackPolicy as a8, syncDynamicPageTexture as a9, textureCacheKey as aa, unregisterCanvasFont as ab, unregisterFontOutlines as ac, verticalMetricsFromTables as ad, warmFonts as ae, __setGlyphRasterizerForTests as af, _getPagesForTests as ag, _resetDomFacesForTests as ah, _resetDynamicFontsForTests as ai, _resetFallbackForTests as aj, _resetFontOutlinesForTests as ak, _resetFontRegistryForTests as al, type BmFontCommon as b, type BmFontInfo as c, type BmFontKerning as d, FIXTURE_FONT as e, type FaceMetricTables as f, type FaceMetrics as g, type FaceRuleMetrics as h, type FaceScriptMetrics as i, type FontEntry as j, type FontFallbackPolicy as k, type FontFamilyFaces as l, type FontRequest as m, type FontVariant as n, type OutlineFaceInfo as o, type OutlineFontOptions as p, type OutlineParser as q, type OutlineSource as r, type OutlineStatus as s, type OutlineVariant as t, type RegisteredFont as u, type ResolveResult as v, cssFontFamily as w, cssFontFamilyLoading as x, dynamicPageTextureId as y, ensureFontTexture as z };
|
package/dist/test-seams.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { af as __setGlyphRasterizerForTests, ag as _getPagesForTests, ah as _resetDomFacesForTests, ai as _resetDynamicFontsForTests, aj as _resetFallbackForTests, ak as _resetFontOutlinesForTests, al as _resetFontRegistryForTests } from './test-seams-Df2Zqwk8.js';
|
|
2
2
|
import '@weasel-js/registry';
|
package/dist/test-seams.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { __setGlyphRasterizerForTests, _getPagesForTests, _resetDynamicFontsForTests, _resetFallbackForTests, _resetFontOutlinesForTests, _resetFontRegistryForTests } from './chunk-
|
|
1
|
+
export { __setGlyphRasterizerForTests, _getPagesForTests, _resetDomFacesForTests, _resetDynamicFontsForTests, _resetFallbackForTests, _resetFontOutlinesForTests, _resetFontRegistryForTests } from './chunk-RYN4NYX4.js';
|
|
2
2
|
//# sourceMappingURL=test-seams.js.map
|
|
3
3
|
//# sourceMappingURL=test-seams.js.map
|