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.
- package/FEATURES.md +102 -0
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/animator.d.ts +158 -0
- package/dist/animator.js +424 -0
- package/dist/animator.test.d.ts +5 -0
- package/dist/animator.test.js +169 -0
- package/dist/border-radius.test.d.ts +1 -0
- package/dist/border-radius.test.js +148 -0
- package/dist/capture.d.ts +193 -0
- package/dist/capture.js +786 -0
- package/dist/chrome.d.ts +45 -0
- package/dist/chrome.js +107 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +512 -0
- package/dist/client/dom.d.ts +10 -0
- package/dist/client/dom.js +17 -0
- package/dist/conic-raster.d.ts +58 -0
- package/dist/conic-raster.js +292 -0
- package/dist/conic-raster.test.d.ts +1 -0
- package/dist/conic-raster.test.js +187 -0
- package/dist/coretext-extractor.test.d.ts +1 -0
- package/dist/coretext-extractor.test.js +94 -0
- package/dist/coretext-helper.d.ts +60 -0
- package/dist/coretext-helper.js +205 -0
- package/dist/cross-origin-font-face.test.d.ts +1 -0
- package/dist/cross-origin-font-face.test.js +107 -0
- package/dist/cursor-overlay.d.ts +123 -0
- package/dist/cursor-overlay.js +207 -0
- package/dist/cursor-overlay.test.d.ts +1 -0
- package/dist/cursor-overlay.test.js +88 -0
- package/dist/dark-mode-capture.test.d.ts +1 -0
- package/dist/dark-mode-capture.test.js +158 -0
- package/dist/dark-mode-form-controls.test.d.ts +1 -0
- package/dist/dark-mode-form-controls.test.js +218 -0
- package/dist/dom-to-svg.d.ts +1016 -0
- package/dist/dom-to-svg.js +7717 -0
- package/dist/embed-remote-images.test.d.ts +1 -0
- package/dist/embed-remote-images.test.js +424 -0
- package/dist/form-controls.d.ts +70 -0
- package/dist/form-controls.js +1151 -0
- package/dist/frame-merge.d.ts +95 -0
- package/dist/frame-merge.js +374 -0
- package/dist/frame-merge.test.d.ts +6 -0
- package/dist/frame-merge.test.js +144 -0
- package/dist/gradients.d.ts +184 -0
- package/dist/gradients.js +937 -0
- package/dist/gradients.test.d.ts +1 -0
- package/dist/gradients.test.js +150 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +7 -0
- package/dist/jsx-runtime.d.ts +27 -0
- package/dist/jsx-runtime.js +96 -0
- package/dist/jsx-runtime.test.d.ts +1 -0
- package/dist/jsx-runtime.test.js +41 -0
- package/dist/kerfjs-imports.test.d.ts +1 -0
- package/dist/kerfjs-imports.test.js +36 -0
- package/dist/mask.test.d.ts +1 -0
- package/dist/mask.test.js +206 -0
- package/dist/optimize.d.ts +12 -0
- package/dist/optimize.js +32 -0
- package/dist/preserve-aspect-ratio.test.d.ts +1 -0
- package/dist/preserve-aspect-ratio.test.js +38 -0
- package/dist/resize-embedded-images.d.ts +33 -0
- package/dist/resize-embedded-images.js +164 -0
- package/dist/resize-embedded-images.test.d.ts +9 -0
- package/dist/resize-embedded-images.test.js +255 -0
- package/dist/stacking-context.test.d.ts +1 -0
- package/dist/stacking-context.test.js +927 -0
- package/dist/text-renderer.d.ts +42 -0
- package/dist/text-renderer.js +608 -0
- package/dist/text-renderer.test.d.ts +1 -0
- package/dist/text-renderer.test.js +150 -0
- package/dist/text-to-path.d.ts +265 -0
- package/dist/text-to-path.js +1800 -0
- package/dist/text-to-path.test.d.ts +1 -0
- package/dist/text-to-path.test.js +570 -0
- package/dist/utils/escapeHtml.d.ts +2 -0
- package/dist/utils/escapeHtml.js +15 -0
- package/dist/webfont-unicode-range.test.d.ts +1 -0
- package/dist/webfont-unicode-range.test.js +174 -0
- package/package.json +55 -0
- package/src/animator.test.ts +179 -0
- package/src/animator.ts +660 -0
- package/src/border-radius.test.ts +160 -0
- package/src/capture.ts +810 -0
- package/src/cli.ts +582 -0
- package/src/conic-raster.test.ts +213 -0
- package/src/conic-raster.ts +309 -0
- package/src/coretext-extractor.test.ts +130 -0
- package/src/coretext-helper.ts +256 -0
- package/src/cross-origin-font-face.test.ts +119 -0
- package/src/cursor-overlay.test.ts +95 -0
- package/src/cursor-overlay.ts +297 -0
- package/src/dark-mode-capture.test.ts +177 -0
- package/src/dark-mode-form-controls.test.ts +228 -0
- package/src/dom-to-svg.ts +8376 -0
- package/src/embed-remote-images.test.ts +461 -0
- package/src/form-controls.ts +1174 -0
- package/src/frame-merge.test.ts +157 -0
- package/src/frame-merge.ts +447 -0
- package/src/globals.d.ts +2 -0
- package/src/gradients.test.ts +175 -0
- package/src/gradients.ts +955 -0
- package/src/index.ts +12 -0
- package/src/kerf-jsx-augmentation.d.ts +36 -0
- package/src/kerfjs-imports.test.tsx +45 -0
- package/src/mask.test.ts +274 -0
- package/src/optimize.ts +34 -0
- package/src/preserve-aspect-ratio.test.ts +49 -0
- package/src/resize-embedded-images.test.ts +292 -0
- package/src/resize-embedded-images.ts +180 -0
- package/src/stacking-context.test.ts +967 -0
- package/src/text-renderer.test.ts +162 -0
- package/src/text-renderer.ts +623 -0
- package/src/text-to-path.test.ts +639 -0
- package/src/text-to-path.ts +1810 -0
- package/src/utils/escapeHtml.ts +16 -0
- 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 {};
|