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,16 @@
|
|
|
1
|
+
export function escapeHtml(str: string): string {
|
|
2
|
+
return str
|
|
3
|
+
.replace(/&/g, "&")
|
|
4
|
+
.replace(/</g, "<")
|
|
5
|
+
.replace(/>/g, ">")
|
|
6
|
+
.replace(/"/g, """);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function escapeAttr(str: string): string {
|
|
10
|
+
return str
|
|
11
|
+
.replace(/&/g, "&")
|
|
12
|
+
.replace(/"/g, """)
|
|
13
|
+
.replace(/'/g, "'")
|
|
14
|
+
.replace(/</g, "<")
|
|
15
|
+
.replace(/>/g, ">");
|
|
16
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import { describe, expect, it, beforeEach } from "vitest";
|
|
3
|
+
import { parseUnicodeRangeDescriptor } from "./capture.js";
|
|
4
|
+
import { __pickWebfontVariantMetaForCodepointForTest, __pickWebfontVariantMetaForTest, clearWebfonts, registerWebfont, unicodeRangeCovers } from "./text-to-path.js";
|
|
5
|
+
|
|
6
|
+
// DM-517: webfont registration honors the `@font-face { unicode-range: ... }`
|
|
7
|
+
// descriptor. Google-Fonts-style partitioning declares the same `(family,
|
|
8
|
+
// weight, style)` pair across multiple `@font-face` rules, each with a
|
|
9
|
+
// distinct `unicode-range` (Latin, Latin Ext, Cyrillic, Greek, …). Without
|
|
10
|
+
// honoring the descriptor, `pickWebfontVariant` may return the Cyrillic-only
|
|
11
|
+
// partition for a Latin run — the run lays out as .notdef tofu.
|
|
12
|
+
|
|
13
|
+
describe("parseUnicodeRangeDescriptor", () => {
|
|
14
|
+
it("returns undefined for empty / whitespace input", () => {
|
|
15
|
+
expect(parseUnicodeRangeDescriptor("")).toBeUndefined();
|
|
16
|
+
expect(parseUnicodeRangeDescriptor(" ")).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("parses single codepoints (U+26)", () => {
|
|
20
|
+
expect(parseUnicodeRangeDescriptor("U+26")).toEqual([[0x26, 0x26]]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("parses interval forms (U+0-7F)", () => {
|
|
24
|
+
expect(parseUnicodeRangeDescriptor("U+0-7F")).toEqual([[0x0, 0x7f]]);
|
|
25
|
+
expect(parseUnicodeRangeDescriptor("U+0000-00FF")).toEqual([[0x0, 0xff]]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("parses wildcard forms (U+4??)", () => {
|
|
29
|
+
expect(parseUnicodeRangeDescriptor("U+4??")).toEqual([[0x400, 0x4ff]]);
|
|
30
|
+
expect(parseUnicodeRangeDescriptor("U+1F??")).toEqual([[0x1f00, 0x1fff]]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("parses comma-separated mixed forms (real Google Fonts Cyrillic partition)", () => {
|
|
34
|
+
const ranges = parseUnicodeRangeDescriptor("U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116");
|
|
35
|
+
expect(ranges).toEqual([
|
|
36
|
+
[0x0301, 0x0301],
|
|
37
|
+
[0x0400, 0x045f],
|
|
38
|
+
[0x0490, 0x0491],
|
|
39
|
+
[0x04b0, 0x04b1],
|
|
40
|
+
[0x2116, 0x2116],
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("is case-insensitive on the U+ prefix", () => {
|
|
45
|
+
expect(parseUnicodeRangeDescriptor("u+0-7f")).toEqual([[0x0, 0x7f]]);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("unicodeRangeCovers", () => {
|
|
50
|
+
it("returns true for any codepoint when ranges is undefined (CSS default)", () => {
|
|
51
|
+
expect(unicodeRangeCovers(undefined, 0x0041)).toBe(true);
|
|
52
|
+
expect(unicodeRangeCovers(undefined, 0x10ffff)).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns true for codepoints inside any interval", () => {
|
|
56
|
+
const ranges: Array<[number, number]> = [[0x0, 0x7f], [0x0400, 0x04ff]];
|
|
57
|
+
expect(unicodeRangeCovers(ranges, 0x0041)).toBe(true); // 'A' in Basic Latin
|
|
58
|
+
expect(unicodeRangeCovers(ranges, 0x0410)).toBe(true); // Cyrillic 'А'
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("returns false for codepoints outside all intervals", () => {
|
|
62
|
+
const ranges: Array<[number, number]> = [[0x0400, 0x04ff]]; // Cyrillic only
|
|
63
|
+
expect(unicodeRangeCovers(ranges, 0x0041)).toBe(false); // 'A' Latin — not covered
|
|
64
|
+
expect(unicodeRangeCovers(ranges, 0x4e00)).toBe(false); // CJK — not covered
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("respects interval boundaries (inclusive)", () => {
|
|
68
|
+
const ranges: Array<[number, number]> = [[0x0020, 0x007f]];
|
|
69
|
+
expect(unicodeRangeCovers(ranges, 0x001f)).toBe(false);
|
|
70
|
+
expect(unicodeRangeCovers(ranges, 0x0020)).toBe(true);
|
|
71
|
+
expect(unicodeRangeCovers(ranges, 0x007f)).toBe(true);
|
|
72
|
+
expect(unicodeRangeCovers(ranges, 0x0080)).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("pickWebfontVariant: unicode-range preference (DM-517)", () => {
|
|
77
|
+
// Use a real system font buffer — `registerWebfont` calls `fontkit.create`
|
|
78
|
+
// and silently skips on parse failure, so we can't fake the buffer.
|
|
79
|
+
const helveticaBuf = fs.existsSync("/System/Library/Fonts/Helvetica.ttc")
|
|
80
|
+
? fs.readFileSync("/System/Library/Fonts/Helvetica.ttc")
|
|
81
|
+
: null;
|
|
82
|
+
const haveFontFixture = helveticaBuf != null;
|
|
83
|
+
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
clearWebfonts();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it.skipIf(!haveFontFixture)("prefers Latin-covering variant when ties on weight + italic (Cyrillic-first registration order)", () => {
|
|
89
|
+
// Simulates Google Fonts' Geist@400 partitioning: Cyrillic partition
|
|
90
|
+
// registers first, then the Latin partition. Without unicode-range
|
|
91
|
+
// awareness, `pickWebfontVariant` would return the Cyrillic variant for
|
|
92
|
+
// a request like (geist, 400, italic=false) because it scores first.
|
|
93
|
+
registerWebfont("geist", 400, "normal", helveticaBuf!, [[0x0301, 0x0301], [0x0400, 0x045f]]);
|
|
94
|
+
registerWebfont("geist", 400, "normal", helveticaBuf!, [[0x0000, 0x00ff], [0x0131, 0x0131]]);
|
|
95
|
+
|
|
96
|
+
const picked = __pickWebfontVariantMetaForTest("geist", 400, false);
|
|
97
|
+
expect(picked).not.toBeNull();
|
|
98
|
+
// The Latin-covering partition (second registration) must win.
|
|
99
|
+
expect(picked!.unicodeRange).toEqual([[0x0000, 0x00ff], [0x0131, 0x0131]]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it.skipIf(!haveFontFixture)("variant with no unicode-range (CSS default = U+0..U+10FFFF) ties non-partitioned cases unchanged", () => {
|
|
103
|
+
// Single registration, no unicode-range — most common case (single woff2
|
|
104
|
+
// covering everything). Behavior unchanged from pre-DM-517.
|
|
105
|
+
registerWebfont("inter", 500, "normal", helveticaBuf!);
|
|
106
|
+
const picked = __pickWebfontVariantMetaForTest("inter", 500, false);
|
|
107
|
+
expect(picked).not.toBeNull();
|
|
108
|
+
expect(picked!.unicodeRange).toBeUndefined();
|
|
109
|
+
expect(picked!.weight).toBe(500);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it.skipIf(!haveFontFixture)("range coverage outweighs italic mismatch — synthesized italic on Latin font beats tofu from italic Cyrillic font", () => {
|
|
113
|
+
// Pathological registration: italic Cyrillic-only + upright Latin-only.
|
|
114
|
+
// Asked for italic, the picker should still prefer the Latin variant
|
|
115
|
+
// because rendering tofu (.notdef) is far worse than synthesized italic.
|
|
116
|
+
registerWebfont("geist", 400, "italic", helveticaBuf!, [[0x0400, 0x045f]]);
|
|
117
|
+
registerWebfont("geist", 400, "normal", helveticaBuf!, [[0x0000, 0x00ff]]);
|
|
118
|
+
|
|
119
|
+
const picked = __pickWebfontVariantMetaForTest("geist", 400, true);
|
|
120
|
+
expect(picked).not.toBeNull();
|
|
121
|
+
// Upright Latin-covering variant wins despite italic mismatch.
|
|
122
|
+
expect(picked!.italic).toBe(false);
|
|
123
|
+
expect(picked!.unicodeRange).toEqual([[0x0000, 0x00ff]]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it.skipIf(!haveFontFixture)("when only non-Latin partitions are registered, picker returns the closest by weight (last-resort fallback)", () => {
|
|
127
|
+
// No Latin-covering variant available — picker still returns the best
|
|
128
|
+
// available (weight match). Avoids returning null for pages where only
|
|
129
|
+
// non-Latin partitions were fetched.
|
|
130
|
+
registerWebfont("geist", 400, "normal", helveticaBuf!, [[0x0400, 0x045f]]);
|
|
131
|
+
registerWebfont("geist", 700, "normal", helveticaBuf!, [[0x0400, 0x045f]]);
|
|
132
|
+
|
|
133
|
+
const picked = __pickWebfontVariantMetaForTest("geist", 400, false);
|
|
134
|
+
expect(picked).not.toBeNull();
|
|
135
|
+
expect(picked!.weight).toBe(400);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("pickWebfontVariantForCodepoint: per-codepoint partition routing (DM-557)", () => {
|
|
140
|
+
// Run-splitter calls this for codepoints the primary (Latin-biased)
|
|
141
|
+
// variant can't shape. The function filters variants by `unicode-range`
|
|
142
|
+
// coverage of the codepoint, then scores remaining variants by italic +
|
|
143
|
+
// weight match.
|
|
144
|
+
const helveticaBuf = require("fs").existsSync("/System/Library/Fonts/Helvetica.ttc")
|
|
145
|
+
? require("fs").readFileSync("/System/Library/Fonts/Helvetica.ttc") : null;
|
|
146
|
+
const haveFontFixture = helveticaBuf != null;
|
|
147
|
+
|
|
148
|
+
beforeEach(() => {
|
|
149
|
+
clearWebfonts();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it.skipIf(!haveFontFixture)("returns the variant whose unicode-range covers the requested codepoint", () => {
|
|
153
|
+
// Multi-partition Geist: Latin + Cyrillic + Latin-Ext partitions all
|
|
154
|
+
// registered at weight=400. Asking for codepoint U+0410 (Cyrillic 'А')
|
|
155
|
+
// must return the Cyrillic partition.
|
|
156
|
+
registerWebfont("geist", 400, "normal", helveticaBuf!, [[0x0000, 0x00ff]]); // Latin
|
|
157
|
+
registerWebfont("geist", 400, "normal", helveticaBuf!, [[0x0400, 0x045f]]); // Cyrillic
|
|
158
|
+
registerWebfont("geist", 400, "normal", helveticaBuf!, [[0x0100, 0x017f]]); // Latin Ext
|
|
159
|
+
|
|
160
|
+
const picked = __pickWebfontVariantMetaForCodepointForTest("geist", 400, false, 0x0410);
|
|
161
|
+
expect(picked).not.toBeNull();
|
|
162
|
+
expect(picked!.unicodeRange).toEqual([[0x0400, 0x045f]]);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it.skipIf(!haveFontFixture)("returns the Latin partition for ASCII codepoints", () => {
|
|
166
|
+
registerWebfont("geist", 400, "normal", helveticaBuf!, [[0x0000, 0x00ff]]);
|
|
167
|
+
registerWebfont("geist", 400, "normal", helveticaBuf!, [[0x0400, 0x045f]]);
|
|
168
|
+
|
|
169
|
+
const picked = __pickWebfontVariantMetaForCodepointForTest("geist", 400, false, 0x0041); // 'A'
|
|
170
|
+
expect(picked).not.toBeNull();
|
|
171
|
+
expect(picked!.unicodeRange).toEqual([[0x0000, 0x00ff]]);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it.skipIf(!haveFontFixture)("returns null when no registered variant covers the codepoint", () => {
|
|
175
|
+
// Only Cyrillic registered; ask for a CJK codepoint outside any range.
|
|
176
|
+
registerWebfont("geist", 400, "normal", helveticaBuf!, [[0x0400, 0x045f]]);
|
|
177
|
+
|
|
178
|
+
const picked = __pickWebfontVariantMetaForCodepointForTest("geist", 400, false, 0x4e00); // CJK
|
|
179
|
+
expect(picked).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it.skipIf(!haveFontFixture)("variant with no unicode-range covers all codepoints (CSS default)", () => {
|
|
183
|
+
// Single non-partitioned registration; should match any codepoint.
|
|
184
|
+
registerWebfont("inter", 400, "normal", helveticaBuf!);
|
|
185
|
+
|
|
186
|
+
const picked = __pickWebfontVariantMetaForCodepointForTest("inter", 400, false, 0x4e00);
|
|
187
|
+
expect(picked).not.toBeNull();
|
|
188
|
+
expect(picked!.unicodeRange).toBeUndefined();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it.skipIf(!haveFontFixture)("scores by italic + weight when multiple variants cover the codepoint", () => {
|
|
192
|
+
// Two italic Latin variants at weights 400 and 700; ask for italic 600.
|
|
193
|
+
// Should pick weight=700 (closer to 600 than 400).
|
|
194
|
+
registerWebfont("geist", 400, "italic", helveticaBuf!, [[0x0000, 0x00ff]]);
|
|
195
|
+
registerWebfont("geist", 700, "italic", helveticaBuf!, [[0x0000, 0x00ff]]);
|
|
196
|
+
registerWebfont("geist", 400, "normal", helveticaBuf!, [[0x0000, 0x00ff]]);
|
|
197
|
+
|
|
198
|
+
const picked = __pickWebfontVariantMetaForCodepointForTest("geist", 600, true, 0x0041);
|
|
199
|
+
expect(picked).not.toBeNull();
|
|
200
|
+
expect(picked!.italic).toBe(true);
|
|
201
|
+
expect(picked!.weight).toBe(700);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it.skipIf(!haveFontFixture)("returns null when family is not registered at all", () => {
|
|
205
|
+
expect(__pickWebfontVariantMetaForCodepointForTest("nonexistent", 400, false, 0x0041)).toBeNull();
|
|
206
|
+
});
|
|
207
|
+
});
|