domotion-svg 0.1.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.
Files changed (119) hide show
  1. package/FEATURES.md +102 -0
  2. package/LICENSE +21 -0
  3. package/README.md +66 -0
  4. package/dist/animator.d.ts +158 -0
  5. package/dist/animator.js +424 -0
  6. package/dist/animator.test.d.ts +5 -0
  7. package/dist/animator.test.js +169 -0
  8. package/dist/border-radius.test.d.ts +1 -0
  9. package/dist/border-radius.test.js +148 -0
  10. package/dist/capture.d.ts +193 -0
  11. package/dist/capture.js +786 -0
  12. package/dist/chrome.d.ts +45 -0
  13. package/dist/chrome.js +107 -0
  14. package/dist/cli.d.ts +16 -0
  15. package/dist/cli.js +512 -0
  16. package/dist/client/dom.d.ts +10 -0
  17. package/dist/client/dom.js +17 -0
  18. package/dist/conic-raster.d.ts +58 -0
  19. package/dist/conic-raster.js +292 -0
  20. package/dist/conic-raster.test.d.ts +1 -0
  21. package/dist/conic-raster.test.js +187 -0
  22. package/dist/coretext-extractor.test.d.ts +1 -0
  23. package/dist/coretext-extractor.test.js +94 -0
  24. package/dist/coretext-helper.d.ts +60 -0
  25. package/dist/coretext-helper.js +205 -0
  26. package/dist/cross-origin-font-face.test.d.ts +1 -0
  27. package/dist/cross-origin-font-face.test.js +107 -0
  28. package/dist/cursor-overlay.d.ts +123 -0
  29. package/dist/cursor-overlay.js +207 -0
  30. package/dist/cursor-overlay.test.d.ts +1 -0
  31. package/dist/cursor-overlay.test.js +88 -0
  32. package/dist/dark-mode-capture.test.d.ts +1 -0
  33. package/dist/dark-mode-capture.test.js +158 -0
  34. package/dist/dark-mode-form-controls.test.d.ts +1 -0
  35. package/dist/dark-mode-form-controls.test.js +218 -0
  36. package/dist/dom-to-svg.d.ts +1016 -0
  37. package/dist/dom-to-svg.js +7717 -0
  38. package/dist/embed-remote-images.test.d.ts +1 -0
  39. package/dist/embed-remote-images.test.js +424 -0
  40. package/dist/form-controls.d.ts +70 -0
  41. package/dist/form-controls.js +1151 -0
  42. package/dist/frame-merge.d.ts +95 -0
  43. package/dist/frame-merge.js +374 -0
  44. package/dist/frame-merge.test.d.ts +6 -0
  45. package/dist/frame-merge.test.js +144 -0
  46. package/dist/gradients.d.ts +184 -0
  47. package/dist/gradients.js +937 -0
  48. package/dist/gradients.test.d.ts +1 -0
  49. package/dist/gradients.test.js +150 -0
  50. package/dist/index.d.ts +12 -0
  51. package/dist/index.js +7 -0
  52. package/dist/jsx-runtime.d.ts +27 -0
  53. package/dist/jsx-runtime.js +96 -0
  54. package/dist/jsx-runtime.test.d.ts +1 -0
  55. package/dist/jsx-runtime.test.js +41 -0
  56. package/dist/kerfjs-imports.test.d.ts +1 -0
  57. package/dist/kerfjs-imports.test.js +36 -0
  58. package/dist/mask.test.d.ts +1 -0
  59. package/dist/mask.test.js +206 -0
  60. package/dist/optimize.d.ts +12 -0
  61. package/dist/optimize.js +32 -0
  62. package/dist/preserve-aspect-ratio.test.d.ts +1 -0
  63. package/dist/preserve-aspect-ratio.test.js +38 -0
  64. package/dist/resize-embedded-images.d.ts +33 -0
  65. package/dist/resize-embedded-images.js +164 -0
  66. package/dist/resize-embedded-images.test.d.ts +9 -0
  67. package/dist/resize-embedded-images.test.js +255 -0
  68. package/dist/stacking-context.test.d.ts +1 -0
  69. package/dist/stacking-context.test.js +927 -0
  70. package/dist/text-renderer.d.ts +42 -0
  71. package/dist/text-renderer.js +608 -0
  72. package/dist/text-renderer.test.d.ts +1 -0
  73. package/dist/text-renderer.test.js +150 -0
  74. package/dist/text-to-path.d.ts +265 -0
  75. package/dist/text-to-path.js +1800 -0
  76. package/dist/text-to-path.test.d.ts +1 -0
  77. package/dist/text-to-path.test.js +570 -0
  78. package/dist/utils/escapeHtml.d.ts +2 -0
  79. package/dist/utils/escapeHtml.js +15 -0
  80. package/dist/webfont-unicode-range.test.d.ts +1 -0
  81. package/dist/webfont-unicode-range.test.js +174 -0
  82. package/package.json +55 -0
  83. package/src/animator.test.ts +179 -0
  84. package/src/animator.ts +660 -0
  85. package/src/border-radius.test.ts +160 -0
  86. package/src/capture.ts +810 -0
  87. package/src/cli.ts +582 -0
  88. package/src/conic-raster.test.ts +213 -0
  89. package/src/conic-raster.ts +309 -0
  90. package/src/coretext-extractor.test.ts +130 -0
  91. package/src/coretext-helper.ts +256 -0
  92. package/src/cross-origin-font-face.test.ts +119 -0
  93. package/src/cursor-overlay.test.ts +95 -0
  94. package/src/cursor-overlay.ts +297 -0
  95. package/src/dark-mode-capture.test.ts +177 -0
  96. package/src/dark-mode-form-controls.test.ts +228 -0
  97. package/src/dom-to-svg.ts +8376 -0
  98. package/src/embed-remote-images.test.ts +461 -0
  99. package/src/form-controls.ts +1174 -0
  100. package/src/frame-merge.test.ts +157 -0
  101. package/src/frame-merge.ts +447 -0
  102. package/src/globals.d.ts +2 -0
  103. package/src/gradients.test.ts +175 -0
  104. package/src/gradients.ts +955 -0
  105. package/src/index.ts +12 -0
  106. package/src/kerf-jsx-augmentation.d.ts +36 -0
  107. package/src/kerfjs-imports.test.tsx +45 -0
  108. package/src/mask.test.ts +274 -0
  109. package/src/optimize.ts +34 -0
  110. package/src/preserve-aspect-ratio.test.ts +49 -0
  111. package/src/resize-embedded-images.test.ts +292 -0
  112. package/src/resize-embedded-images.ts +180 -0
  113. package/src/stacking-context.test.ts +967 -0
  114. package/src/text-renderer.test.ts +162 -0
  115. package/src/text-renderer.ts +623 -0
  116. package/src/text-to-path.test.ts +639 -0
  117. package/src/text-to-path.ts +1810 -0
  118. package/src/utils/escapeHtml.ts +16 -0
  119. package/src/webfont-unicode-range.test.ts +207 -0
@@ -0,0 +1,150 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { rasterGlyphOverlays, renderSingleLineText } from "./text-renderer.js";
3
+ describe("rasterGlyphOverlays — emoji bitmap sizing (DM-381)", () => {
4
+ // The captured per-char rect spans the line-box height (~lineHeight) and
5
+ // the glyph advance — bigger than what Chrome actually paints, which is
6
+ // the em-square (fontSize × fontSize) anchored to the baseline. The
7
+ // overlay emits the bitmap at em-square size centered in the rect so the
8
+ // <image> dims match Chrome's painted region instead of being stretched
9
+ // out to the full line-box.
10
+ const seg = {
11
+ text: "😀",
12
+ x: 347.7, y: 695.4, width: 23, height: 25,
13
+ rasterGlyphs: [{
14
+ charIndex: 0,
15
+ rect: { x: 347.7, y: 695.4, width: 23, height: 25 },
16
+ dataUri: "data:image/png;base64,iVBORw0KGgo="
17
+ }]
18
+ };
19
+ it("emits the bitmap at the captured rect coords + dims (DM-401 / DM-411 / DM-414)", () => {
20
+ // The screenshot was captured from Chrome's actual paint at this rect,
21
+ // so re-embedding at the same coords + dims preserves the painted
22
+ // geometry pixel-for-pixel. Avoid the prior em-square-stretch which
23
+ // squished tall line-box rects horizontally and rendered emojis
24
+ // visibly larger than Chrome's actual paint.
25
+ const out = rasterGlyphOverlays(seg, 22, "ct1");
26
+ expect(out).toContain('x="347.7"');
27
+ expect(out).toContain('y="695.4"');
28
+ expect(out).toContain('width="23"');
29
+ expect(out).toContain('height="25"');
30
+ expect(out).toContain('preserveAspectRatio="none"');
31
+ });
32
+ it("ignores fontSize for sizing — the captured rect dims are authoritative", () => {
33
+ const segWithFs = { ...seg, fontSize: 32 };
34
+ const out = rasterGlyphOverlays(segWithFs, 16, "ct1");
35
+ expect(out).toContain('width="23"');
36
+ expect(out).toContain('height="25"');
37
+ });
38
+ it("returns empty when there are no resolved dataUris", () => {
39
+ const empty = { ...seg, rasterGlyphs: [{ charIndex: 0, rect: seg.rect, dataUri: undefined }] };
40
+ expect(rasterGlyphOverlays(empty, 22, "ct1")).toBe("");
41
+ });
42
+ it("returns empty when the segment has no rasterGlyphs at all", () => {
43
+ expect(rasterGlyphOverlays({ ...seg, rasterGlyphs: undefined }, 22, "ct1")).toBe("");
44
+ });
45
+ });
46
+ describe("renderSingleLineText — pseudo-only segment positioning (DM-495)", () => {
47
+ // When a host element has no main text and only a positioned ::after / ::before
48
+ // pseudo, the segment carries its own x/y/color/fontSize. Before DM-495 the
49
+ // single-segment path read host-level fields exclusively, so the pseudo's
50
+ // text was emitted at translate(0,0) in the host's color (typically the
51
+ // inherited default black) instead of at the pseudo's anchor in its own
52
+ // CSS-declared color. Capture sets host textLeft/textTop/textWidth from
53
+ // the pseudo seg in this case, and the renderer reads seg.color /
54
+ // seg.fontSize / seg.fontWeight / seg.fontAscent so all per-pseudo
55
+ // overrides flow through.
56
+ const baseStyles = {
57
+ color: "rgb(0,0,0)",
58
+ fontSize: "16px",
59
+ fontFamily: "-apple-system, sans-serif",
60
+ fontWeight: "400",
61
+ fontStyle: "normal",
62
+ direction: "ltr",
63
+ textDecorationLine: "none",
64
+ textDecorationColor: "currentcolor",
65
+ textDecorationStyle: "solid",
66
+ };
67
+ const makeEl = (seg) => ({
68
+ tag: "span",
69
+ x: 100, y: 50, width: 200, height: 80,
70
+ textLeft: seg.x, textTop: seg.y, textWidth: seg.width, textHeight: seg.height,
71
+ fontAscent: seg.fontAscent,
72
+ text: seg.text,
73
+ textSegments: [seg],
74
+ styles: baseStyles,
75
+ });
76
+ it("renders the pseudo's own color, not the host's color", () => {
77
+ const seg = {
78
+ text: "TAG",
79
+ x: 108, y: 56, width: 22, height: 11,
80
+ color: "rgb(255, 255, 255)",
81
+ fontSize: 11,
82
+ fontWeight: "400",
83
+ fontAscent: 9,
84
+ };
85
+ const out = renderSingleLineText({
86
+ el: makeEl(seg),
87
+ idPrefix: "t",
88
+ clipId: "ct0",
89
+ fillColor: "rgb(0,0,0)",
90
+ });
91
+ expect(out).toContain('fill="rgb(255, 255, 255)"');
92
+ expect(out).not.toContain('fill="rgb(0,0,0)"');
93
+ });
94
+ it("anchors the path at the pseudo's x/y, not at the SVG origin", () => {
95
+ const seg = {
96
+ text: "TAG",
97
+ x: 108, y: 56, width: 22, height: 11,
98
+ color: "rgb(255, 255, 255)",
99
+ fontSize: 11,
100
+ fontWeight: "400",
101
+ fontAscent: 9,
102
+ };
103
+ const out = renderSingleLineText({
104
+ el: makeEl(seg),
105
+ idPrefix: "t",
106
+ clipId: "ct0",
107
+ fillColor: "rgb(0,0,0)",
108
+ });
109
+ // baselineY = textTop + fontAscent = 56 + 9 = 65
110
+ expect(out).toMatch(/transform="translate\(108,\s*65\)"/);
111
+ expect(out).not.toContain('translate(0,0)');
112
+ });
113
+ it("uses the pseudo's fontSize when set, not the host's", () => {
114
+ const seg = {
115
+ text: "T",
116
+ x: 108, y: 56, width: 8, height: 11,
117
+ color: "rgb(255, 255, 255)",
118
+ fontSize: 11,
119
+ fontWeight: "400",
120
+ fontAscent: 9,
121
+ };
122
+ // Host fontSize is 16, pseudo is 11 — output should reflect 11px scale.
123
+ const elWithLargerHost = { ...makeEl(seg), styles: { ...baseStyles, fontSize: "16px" } };
124
+ const outAt11 = renderSingleLineText({
125
+ el: elWithLargerHost,
126
+ idPrefix: "t",
127
+ clipId: "ct0",
128
+ fillColor: "rgb(0,0,0)",
129
+ });
130
+ // The inner glyph scale = fontSize / unitsPerEm. For typical fonts (UPM
131
+ // ~2048), 11/2048 ≈ 0.00537; 16/2048 ≈ 0.00781. Spot-check the small one.
132
+ expect(outAt11).toMatch(/scale\(0\.00[0-9]+,/);
133
+ // Negative comparison: the host-fontSize scale shouldn't appear.
134
+ expect(outAt11).not.toContain('scale(0.00781,');
135
+ });
136
+ it("falls back to host fillColor when seg.color is absent", () => {
137
+ const seg = {
138
+ text: "TAG",
139
+ x: 108, y: 56, width: 22, height: 11,
140
+ fontAscent: 9,
141
+ };
142
+ const out = renderSingleLineText({
143
+ el: makeEl(seg),
144
+ idPrefix: "t",
145
+ clipId: "ct0",
146
+ fillColor: "rgb(34, 139, 34)",
147
+ });
148
+ expect(out).toContain('fill="rgb(34, 139, 34)"');
149
+ });
150
+ });
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Text-to-Path Converter
3
+ *
4
+ * Uses fontkit to convert text strings into SVG <path> outlines using
5
+ * the actual macOS system fonts. Glyphs are deduplicated using SVG
6
+ * <defs>/<use> — each unique glyph shape is defined once and referenced
7
+ * everywhere it appears.
8
+ */
9
+ interface FontInstance {
10
+ layout(text: string, features?: string[]): {
11
+ glyphs: Array<{
12
+ id: number;
13
+ path: {
14
+ commands: Array<{
15
+ command: string;
16
+ args: number[];
17
+ }>;
18
+ };
19
+ advanceWidth: number;
20
+ codePoints?: number[];
21
+ }>;
22
+ positions: Array<{
23
+ xAdvance: number;
24
+ yAdvance: number;
25
+ xOffset: number;
26
+ yOffset: number;
27
+ }>;
28
+ };
29
+ unitsPerEm: number;
30
+ ascent: number;
31
+ descent: number;
32
+ underlinePosition: number;
33
+ underlineThickness: number;
34
+ /** Available OpenType feature tags (e.g. ['liga', 'kern', 'smcp']). Used by
35
+ * the synthesized-small-caps path to detect when smcp is missing. */
36
+ availableFeatures?: string[];
37
+ "OS/2"?: {
38
+ yStrikeoutPosition?: number;
39
+ yStrikeoutSize?: number;
40
+ };
41
+ }
42
+ /**
43
+ * Open a webfont buffer with fontkit and register it under the given family
44
+ * name (case-insensitive). `weight` is a CSS numeric weight (100-900); 400
45
+ * when omitted. `style` is "normal" / "italic" / "oblique"; treated as italic
46
+ * for any non-normal value.
47
+ *
48
+ * `unicodeRange` mirrors the `@font-face { unicode-range: ... }` descriptor as
49
+ * a list of inclusive `[from, to]` codepoint intervals. Google-Fonts-style
50
+ * partitioning declares the same `(family, weight)` pair across multiple
51
+ * `@font-face` rules, each with a distinct `unicode-range` (Latin, Latin Ext,
52
+ * Cyrillic, Greek, Vietnamese, …). Without honoring the descriptor,
53
+ * `pickWebfontVariant` may return the Cyrillic-only partition for a Latin
54
+ * text run — the run lays out as .notdef tofu (DM-517).
55
+ *
56
+ * Buffers must be decompressed already — fontkit's `create()` reads TTF/OTF
57
+ * directly. WOFF2/WOFF bytes are decompressed in `loadWebfont()` (capture.ts)
58
+ * before they reach this function.
59
+ */
60
+ export declare function registerWebfont(family: string, weight: number, style: string, buffer: Buffer, unicodeRange?: Array<[number, number]>): void;
61
+ /** True iff `cp` falls in any of the inclusive `[from, to]` intervals. */
62
+ export declare function unicodeRangeCovers(ranges: Array<[number, number]> | undefined, cp: number): boolean;
63
+ /**
64
+ * DM-557: codepoint-aware variant pick for partitioned webfonts. Filters
65
+ * registered variants by whether their `unicode-range` covers `codepoint`
66
+ * (per CSS Fonts 4 §11.5 — a partition only declares it can shape glyphs
67
+ * within its declared range), then scores by (italic, weight) like
68
+ * `pickWebfontVariant`. Returns null when no registered variant covers the
69
+ * codepoint — the caller is expected to walk the system fallback chain in
70
+ * that case.
71
+ *
72
+ * Used by the run-splitter in `textToPathMarkup` to route per-codepoint
73
+ * within a Google-Fonts-style partitioned family (Geist@400 split across
74
+ * Latin/Latin-Ext/Cyrillic/etc.). Without this, the Latin-biased
75
+ * `pickWebfontVariant` is the single primary font for the whole text and
76
+ * codepoints outside its range fall straight to system fonts — losing the
77
+ * matching Cyrillic/Greek/Latin-Ext partition that's registered but
78
+ * unselected.
79
+ */
80
+ export declare function pickWebfontVariantForCodepoint(family: string, weight: number, fontSize: number, slant: number, codepoint: number): FontInstance | null;
81
+ /**
82
+ * Test-only: return metadata for the variant `pickWebfontVariant` would
83
+ * choose, without resolving variation axes / returning a FontInstance. Lets
84
+ * unit tests verify scoring (weight, italic, unicode-range) without needing
85
+ * to introspect glyph paths.
86
+ */
87
+ export declare function __pickWebfontVariantMetaForTest(family: string, weight: number, italic: boolean): {
88
+ weight: number;
89
+ italic: boolean;
90
+ unicodeRange?: Array<[number, number]>;
91
+ } | null;
92
+ /** Test-only meta variant for `pickWebfontVariantForCodepoint` (DM-557). */
93
+ export declare function __pickWebfontVariantMetaForCodepointForTest(family: string, weight: number, italic: boolean, codepoint: number): {
94
+ weight: number;
95
+ italic: boolean;
96
+ unicodeRange?: Array<[number, number]>;
97
+ } | null;
98
+ /** Drop all registered webfonts. Call at the start of a fresh capture run. */
99
+ export declare function clearWebfonts(): void;
100
+ export declare function registerLocalFontAlias(family: string, resolvedKey: string, weight?: number, italic?: boolean): void;
101
+ /**
102
+ * Ordered list of fallback font keys to try when the primary font lacks a
103
+ * glyph for `codepoint`. Caller iterates the chain and picks the first font
104
+ * whose `glyphForCodePoint(cp).id !== 0`. Returns an empty array when no
105
+ * fallback is needed (caller should keep using primary).
106
+ *
107
+ * Order matches Chrome's macOS CoreText fallback per Unicode block, verified
108
+ * empirically by probing Chrome's painted width vs each candidate font's
109
+ * natural advance (DM-241 follow-up audit). Apple Symbols stays as the final
110
+ * safety net so we never end up with a .notdef tofu — better to draw a
111
+ * slightly-wrong glyph than nothing.
112
+ */
113
+ /**
114
+ * Pick the PingFang regional variant key (or `hiragino-jp` for Japanese)
115
+ * that matches the element's computed `lang`. Returns null when the lang
116
+ * is empty / unknown — caller should fall through to the default `pingfang-sc`
117
+ * route in that case. DM-394.
118
+ *
119
+ * Matches BCP-47 language tags: the primary subtag wins, with a Han-script
120
+ * subtag (`Hans` / `Hant`) overriding region for the simplified-vs-traditional
121
+ * split. Examples:
122
+ * "zh-TW" → pingfang-tc
123
+ * "zh-Hant" → pingfang-tc
124
+ * "zh-Hant-HK" → pingfang-hk (region is more specific than script)
125
+ * "zh-HK" → pingfang-hk
126
+ * "zh-MO" → pingfang-mo
127
+ * "zh-CN" / "zh-Hans" / "zh" / "" → null (caller picks pingfang-sc)
128
+ * "ja" / "ja-JP" → hiragino-jp (PingFang has no JP subfont on macOS)
129
+ */
130
+ export declare function pingfangKeyForLang(lang: string | undefined): string | null;
131
+ export declare function fallbackFontChain(codepoint: number, primaryKey?: string, lang?: string): string[];
132
+ /** @deprecated Single-key wrapper for back-compat — prefer `fallbackFontChain`. */
133
+ export declare function fallbackFontKey(codepoint: number): string | null;
134
+ export declare function resolveFontKey(fontFamily: string): string;
135
+ /**
136
+ * Get all glyph <defs> accumulated so far. Call this once when building the final SVG.
137
+ * Returns SVG markup to place inside a <defs> block.
138
+ */
139
+ export declare function getGlyphDefs(): string;
140
+ /** Clear the glyph registry (call between independent SVG generations). */
141
+ export declare function clearGlyphDefs(): void;
142
+ export interface TextPathResult {
143
+ /** SVG markup: <use> references for each glyph */
144
+ markup: string;
145
+ /** Actual rendered width in CSS pixels */
146
+ width: number;
147
+ }
148
+ /**
149
+ * Convert a text string to SVG markup using <use> references to glyph defs.
150
+ *
151
+ * Positioning modes (in order of preference):
152
+ * 1. xOffsets (per-char x in CSS pixels, relative to text origin) — used
153
+ * when the capture layer measured each character's actual rect.left.
154
+ * This eliminates per-character drift because glyph placement matches
155
+ * exactly what the browser painted (including kerning, letter-spacing,
156
+ * optical-size effects, etc.).
157
+ * 2. targetWidth — scales native fontkit advances uniformly so the total
158
+ * width matches Chrome. Good for single-line text where per-char drift
159
+ * is small. Kept as a fallback for inputs/textarea values (no per-char
160
+ * rect data) and legacy callers.
161
+ * 3. Native fontkit advances — if neither is provided.
162
+ */
163
+ export declare function textToPathMarkup(text: string, fontSize: number, fontFamily: string, fontWeight: string, targetWidth?: number,
164
+ /** CSS-pixel x offsets per visible char, relative to the text origin. */
165
+ xOffsets?: number[],
166
+ /** CSS font-style ('italic' / 'oblique' / 'normal'). Drives SF Pro's slnt. */
167
+ fontStyle?: string,
168
+ /**
169
+ * OpenType feature tags to enable for shaping (e.g. ['smcp'] for
170
+ * `font-variant: small-caps`). Threaded through to every fontkit
171
+ * `font.layout()` call so single-char and multi-char shaping both pick the
172
+ * substituted glyph. Empty / undefined means default shaping. (DM-294)
173
+ */
174
+ features?: string[],
175
+ /** BCP-47 language tag from the element's computed `lang` attribute. Routes
176
+ * Han fallbacks to the matching PingFang regional variant — `zh-TW` / `zh-Hant`
177
+ * → PingFang TC, `zh-HK` → PingFang HK, `zh-MO` → PingFang MO, `ja` →
178
+ * Hiragino Kaku, otherwise PingFang SC. (DM-394) */
179
+ lang?: string): TextPathResult | null;
180
+ /**
181
+ * Render text as SVG markup using path outlines with <defs>/<use> deduplication.
182
+ * Returns a <g> element containing <use> references, positioned at (x, y) top.
183
+ */
184
+ export declare function renderTextAsPath(text: string, x: number, y: number, fontSize: number, fontFamily: string, fontWeight: string, fill: string, _clipPath?: string,
185
+ /** Chrome's measured text width — used to scale glyph positions for accurate layout */
186
+ targetWidth?: number,
187
+ /** Per-char x offsets relative to this text's origin (CSS pixels). */
188
+ xOffsets?: number[],
189
+ /** CSS font-style; 'italic' / 'oblique' activate SF Pro's slnt axis. */
190
+ fontStyle?: string,
191
+ /**
192
+ * Captured `canvas.measureText().fontBoundingBoxAscent` (px) — distance
193
+ * from line-box top to baseline as Chrome will paint it. Overrides the
194
+ * fontkit-derived ascent below, which is HHEA-based and disagrees with
195
+ * Chrome on macOS for Helvetica/Arial/Times/Georgia/Menlo/Courier (Chrome
196
+ * uses winAscent there). Per-font metric-selection rules are fragile to
197
+ * derive but trivial to read from the browser. See SK-1267 / DM-237.
198
+ */
199
+ ascentOverride?: number,
200
+ /**
201
+ * OpenType feature tags forwarded to fontkit (e.g. ['smcp'] when CSS
202
+ * `font-variant: small-caps` is in effect on this run). DM-294.
203
+ */
204
+ features?: string[],
205
+ /** BCP-47 language tag for locale-aware Han fallback variant routing
206
+ * (PingFang TC / HK / MO, or Hiragino Kaku for `ja`). DM-394. */
207
+ lang?: string): string | null;
208
+ /**
209
+ * Check if text-to-path conversion is available for a font family.
210
+ */
211
+ export declare function isTextToPathAvailable(fontFamily: string): boolean;
212
+ export interface DecorationMetrics {
213
+ /** Underline stroke center, px below baseline (positive = below). */
214
+ underlineOffsetY: number;
215
+ underlineThickness: number;
216
+ /** Line-through stroke center, px above baseline (positive = above). */
217
+ strikeoutOffsetY: number;
218
+ strikeoutThickness: number;
219
+ /** Overline stroke center, px above baseline (positive = above). */
220
+ overlineOffsetY: number;
221
+ overlineThickness: number;
222
+ }
223
+ /**
224
+ * Resolve text-decoration line placement from the font's actual `post`/`OS/2`
225
+ * tables (SK-1236). Chromium uses these same metric tables, so reading them
226
+ * directly tightens decoration alignment vs the previous fontSize-fraction
227
+ * approximation — most visible on SF Mono (underline ~0.75px higher than the
228
+ * generic 15%-of-fontSize estimate) and on large-fontSize text.
229
+ *
230
+ * Returns offsets in px from the baseline (sign convention noted on each
231
+ * field). Falls back to the legacy 15%/30%/95% approximations when the font
232
+ * fails to resolve.
233
+ */
234
+ export declare function getDecorationMetrics(fontFamily: string, fontSize: number, fontWeight: string | number, fontStyle?: string,
235
+ /** CSS `text-decoration-thickness` — when set to a length value (e.g. "5px"),
236
+ * overrides the auto thickness. Pass `undefined` or `auto` to use the auto
237
+ * rule. DM-431. */
238
+ thicknessOverride?: string,
239
+ /** CSS `text-underline-offset` — when set to a length value, adds this much
240
+ * EXTRA distance below the baseline (on top of the auto offset). DM-431. */
241
+ underlineOffsetCss?: string): DecorationMetrics;
242
+ /**
243
+ * Compute X-range gaps where the underline rect [decorationCenterY - thickness/2,
244
+ * decorationCenterY + thickness/2] crosses glyph ink for `text` rendered in
245
+ * the given font. Returns gaps in the text's local coordinate system (X=0 at
246
+ * the run's anchor — caller adds segX to translate). Used to honor
247
+ * `text-decoration-skip-ink: auto` on solid / double underlines, matching
248
+ * Chromium's `decoration_line_painter.cc::ComputeUnderlineSkipFromIntercepts`.
249
+ *
250
+ * Algorithm: shape via fontkit, walk each glyph's path, flatten quadratic /
251
+ * cubic Beziers to short polylines, find segment-vs-horizontal-line
252
+ * intersections at the rect's top and bottom Y. Per glyph, the gap spans
253
+ * `[minIntersectX - pad, maxIntersectX + pad]` where pad = 0.5 * thickness
254
+ * (matches Chromium's `kIntersectionExtension`).
255
+ *
256
+ * `decorationCenterYRel` is in baseline-relative screen coords (positive =
257
+ * below baseline). Returns `[]` when font isn't resolvable, no glyphs cross
258
+ * the rect, or shaping throws. (DM-446.)
259
+ */
260
+ export declare function computeSkipInkGaps(text: string, fontSize: number, fontFamily: string, fontWeight: string | number, fontStyle?: string, decorationCenterYRel?: number, decorationThickness?: number, features?: string[],
261
+ /** Chromium-measured run width — when set, intercepts are scaled to match
262
+ * so gaps line up with the painted glyph positions even when fontkit's
263
+ * layout disagrees with HarfBuzz at sub-px scale. */
264
+ targetWidth?: number): Array<[number, number]>;
265
+ export {};