domotion-svg 0.2.2 → 0.3.2
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 +1 -0
- package/README.md +29 -0
- package/dist/animation/animator.js +25 -14
- package/dist/animation/animator.test.js +54 -21
- package/dist/animation/cursor-overlay.js +0 -2
- package/dist/capture/emoji.js +29 -18
- package/dist/capture/index.js +5 -4
- package/dist/capture/script/color-norm.d.ts +1 -0
- package/dist/capture/script/color-norm.js +43 -1
- package/dist/capture/script/emoji-detect.js +14 -0
- package/dist/capture/script/index.js +593 -65
- package/dist/capture/script/walker/borders-backgrounds.d.ts +24 -17
- package/dist/capture/script/walker/borders-backgrounds.js +123 -7
- package/dist/capture/script/walker/counter-style-resolver.d.ts +7 -0
- package/dist/capture/script/walker/counter-style-resolver.js +218 -0
- package/dist/capture/script/walker/input-value.js +14 -1
- package/dist/capture/script/walker/lists-counters.d.ts +3 -1
- package/dist/capture/script/walker/lists-counters.js +22 -2
- package/dist/capture/script/walker/masks-clips.d.ts +2 -0
- package/dist/capture/script/walker/masks-clips.js +41 -1
- package/dist/capture/script/walker/pseudo-content.d.ts +14 -1
- package/dist/capture/script/walker/pseudo-content.js +301 -61
- package/dist/capture/script/walker/pseudo-inject.js +20 -0
- package/dist/capture/script/walker/text-segments.js +98 -4
- package/dist/capture/script/walker/transforms.d.ts +1 -0
- package/dist/capture/script/walker/transforms.js +16 -0
- package/dist/capture/script.generated.js +1 -1
- package/dist/capture/types.d.ts +213 -2
- package/dist/cli/animate.js +151 -15
- package/dist/mask.test.js +12 -7
- package/dist/render/borders.d.ts +9 -13
- package/dist/render/borders.js +379 -14
- package/dist/render/element-tree-to-svg.d.ts +11 -12
- package/dist/render/element-tree-to-svg.js +2046 -241
- package/dist/render/embedded-font-builder.d.ts +49 -0
- package/dist/render/embedded-font-builder.js +149 -0
- package/dist/render/form-controls.js +45 -24
- package/dist/render/gradients.d.ts +15 -0
- package/dist/render/gradients.js +103 -2
- package/dist/render/gradients.test.js +34 -0
- package/dist/render/text-to-path.d.ts +38 -1
- package/dist/render/text-to-path.js +654 -29
- package/dist/render/text-to-path.test.js +230 -9
- package/dist/render/text.d.ts +14 -0
- package/dist/render/text.js +344 -40
- package/dist/scroll/composer.d.ts +26 -0
- package/dist/scroll/composer.js +199 -11
- package/dist/scroll/composer.test.js +293 -16
- package/dist/scroll/executor.d.ts +3 -1
- package/dist/scroll/executor.js +15 -6
- package/dist/scroll/executor.test.js +25 -0
- package/dist/scroll/hoist-fixed.d.ts +48 -0
- package/dist/scroll/hoist-fixed.js +85 -0
- package/dist/scroll/hoist-fixed.test.d.ts +1 -0
- package/dist/scroll/hoist-fixed.test.js +103 -0
- package/dist/scroll/hoist-sticky.d.ts +45 -0
- package/dist/scroll/hoist-sticky.js +157 -0
- package/dist/scroll/hoist-sticky.test.d.ts +1 -0
- package/dist/scroll/hoist-sticky.test.js +154 -0
- package/dist/scroll/pattern.d.ts +22 -5
- package/dist/scroll/pattern.js +55 -7
- package/dist/scroll/pattern.test.js +48 -1
- package/dist/tree-ops/frame-merge.d.ts +10 -0
- package/dist/tree-ops/frame-merge.js +23 -5
- package/dist/tree-ops/frame-merge.test.js +45 -0
- package/dist/tree-ops/tree-diff.js +1 -1
- package/dist/tree-ops/viewbox-culling.js +32 -18
- package/dist/tree-ops/viewbox-culling.test.js +40 -6
- package/package.json +8 -2
- package/src/animation/animator.test.ts +56 -21
- package/src/animation/animator.ts +25 -14
- package/src/animation/cursor-overlay.ts +0 -2
- package/src/capture/emoji.ts +28 -18
- package/src/capture/index.ts +15 -14
- package/src/capture/script/color-norm.ts +38 -1
- package/src/capture/script/emoji-detect.ts +14 -0
- package/src/capture/script/index.ts +555 -48
- package/src/capture/script/walker/borders-backgrounds.ts +114 -7
- package/src/capture/script/walker/counter-style-resolver.ts +184 -0
- package/src/capture/script/walker/input-value.ts +14 -1
- package/src/capture/script/walker/lists-counters.ts +24 -2
- package/src/capture/script/walker/masks-clips.ts +40 -1
- package/src/capture/script/walker/pseudo-content.ts +297 -55
- package/src/capture/script/walker/pseudo-inject.ts +20 -0
- package/src/capture/script/walker/text-segments.ts +93 -4
- package/src/capture/script/walker/transforms.ts +14 -0
- package/src/capture/script.generated.ts +1 -1
- package/src/capture/types.ts +202 -2
- package/src/cli/animate.ts +135 -15
- package/src/mask.test.ts +12 -7
- package/src/render/borders.ts +383 -17
- package/src/render/element-tree-to-svg.ts +2051 -238
- package/src/render/embedded-font-builder.ts +221 -0
- package/src/render/form-controls.ts +45 -24
- package/src/render/gradients.test.ts +46 -0
- package/src/render/gradients.ts +94 -2
- package/src/render/opentype.js.d.ts +7 -0
- package/src/render/text-to-path.test.ts +246 -9
- package/src/render/text-to-path.ts +702 -31
- package/src/render/text.ts +344 -40
- package/src/scroll/composer.test.ts +322 -16
- package/src/scroll/composer.ts +246 -13
- package/src/scroll/executor.test.ts +27 -0
- package/src/scroll/executor.ts +19 -10
- package/src/scroll/hoist-fixed.test.ts +117 -0
- package/src/scroll/hoist-fixed.ts +95 -0
- package/src/scroll/hoist-sticky.test.ts +173 -0
- package/src/scroll/hoist-sticky.ts +193 -0
- package/src/scroll/pattern.test.ts +58 -1
- package/src/scroll/pattern.ts +71 -8
- package/src/tree-ops/frame-merge.test.ts +51 -0
- package/src/tree-ops/frame-merge.ts +24 -6
- package/src/tree-ops/tree-diff.ts +3 -1
- package/src/tree-ops/viewbox-culling.test.ts +42 -6
- package/src/tree-ops/viewbox-culling.ts +32 -18
|
@@ -138,6 +138,57 @@ export const createTextSegmentsHandler = ({ vp, measureFontMetrics, needsRaster
|
|
|
138
138
|
const flFsRaw = parseFloat(flStyle.fontSize) || 0;
|
|
139
139
|
const firstLetterStyled = flFsRaw > 0 && Math.abs(flFsRaw - elFsRaw) > 0.5;
|
|
140
140
|
let firstCharSeen = false;
|
|
141
|
+
// DM-747 / DM-791: MathML `<mi>` with a single token character is
|
|
142
|
+
// automatically painted with the Mathematical Italic alphabet (the
|
|
143
|
+
// mathvariant=italic mapping from MathML 4 / Core). Chrome paints
|
|
144
|
+
// `<mi>a</mi>` using U+1D44E (𝑎), `<mi>α</mi>` using U+1D6FC (𝛼), etc.
|
|
145
|
+
// The computed `font-style` stays `normal` and `font-family` stays
|
|
146
|
+
// `math`, so we can't detect this through CSS — we apply the mapping
|
|
147
|
+
// ourselves at capture time so the downstream text-shaping pipeline
|
|
148
|
+
// picks up the right glyphs from whatever math font the system has.
|
|
149
|
+
const elTag = el.tagName != null ? el.tagName.toLowerCase() : '';
|
|
150
|
+
const _miText = (el.textContent || '').trim();
|
|
151
|
+
const mathItalicizeMi = elTag === 'mi'
|
|
152
|
+
&& [..._miText].length === 1
|
|
153
|
+
&& /^[a-zA-ZΑ-ΩΆΈΉΊΌΎΏα-ωϐϑϕϖϗϰϱϵ∂∇]$/u.test(_miText);
|
|
154
|
+
const mathItalicChar = (ch) => {
|
|
155
|
+
const code = ch.codePointAt(0);
|
|
156
|
+
if (code == null)
|
|
157
|
+
return ch;
|
|
158
|
+
// Latin Mathematical Italic Capital A..Z = U+1D434..U+1D44D.
|
|
159
|
+
if (code >= 0x41 && code <= 0x5A)
|
|
160
|
+
return String.fromCodePoint(0x1D434 + (code - 0x41));
|
|
161
|
+
// Latin Mathematical Italic Small a..z = U+1D44E..U+1D467, except
|
|
162
|
+
// U+1D455 ("h") is reserved — Chrome paints U+210E (ℎ, PLANCK CONSTANT).
|
|
163
|
+
if (code >= 0x61 && code <= 0x7A) {
|
|
164
|
+
if (code === 0x68)
|
|
165
|
+
return 'ℎ';
|
|
166
|
+
return String.fromCodePoint(0x1D44E + (code - 0x61));
|
|
167
|
+
}
|
|
168
|
+
// Greek Mathematical Italic Capital Α..Ω = U+1D6E2..U+1D6FA.
|
|
169
|
+
// The block is dense: 25 codepoints for Α (U+0391) through Ω (U+03A9),
|
|
170
|
+
// skipping nothing in the source range.
|
|
171
|
+
if (code >= 0x0391 && code <= 0x03A9)
|
|
172
|
+
return String.fromCodePoint(0x1D6E2 + (code - 0x0391));
|
|
173
|
+
// Greek Mathematical Italic Small α..ω = U+1D6FC..U+1D71B.
|
|
174
|
+
// U+03C2 (final sigma ς) maps to the regular U+1D70D position; the
|
|
175
|
+
// block is contiguous.
|
|
176
|
+
if (code >= 0x03B1 && code <= 0x03C9)
|
|
177
|
+
return String.fromCodePoint(0x1D6FC + (code - 0x03B1));
|
|
178
|
+
// Greek symbol variants — these alternate forms get their own italic
|
|
179
|
+
// codepoints at the tail of the lowercase Greek block.
|
|
180
|
+
switch (code) {
|
|
181
|
+
case 0x2202: return String.fromCodePoint(0x1D715); // ∂ → italic partial differential
|
|
182
|
+
case 0x03F5: return String.fromCodePoint(0x1D716); // ϵ (lunate) → italic epsilon symbol
|
|
183
|
+
case 0x03D1: return String.fromCodePoint(0x1D717); // ϑ (theta sym) → italic theta symbol
|
|
184
|
+
case 0x03F0: return String.fromCodePoint(0x1D718); // ϰ (kappa sym) → italic kappa symbol
|
|
185
|
+
case 0x03D5: return String.fromCodePoint(0x1D719); // ϕ (phi sym) → italic phi symbol
|
|
186
|
+
case 0x03F1: return String.fromCodePoint(0x1D71A); // ϱ (rho sym) → italic rho symbol
|
|
187
|
+
case 0x03D6: return String.fromCodePoint(0x1D71B); // ϖ (pi sym) → italic pi symbol
|
|
188
|
+
case 0x2207: return String.fromCodePoint(0x1D6FB); // ∇ (nabla) → italic nabla (upper-block tail)
|
|
189
|
+
default: return ch;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
141
192
|
for (const node of el.childNodes) {
|
|
142
193
|
if (node.nodeType !== Node.TEXT_NODE)
|
|
143
194
|
continue;
|
|
@@ -152,7 +203,13 @@ export const createTextSegmentsHandler = ({ vp, measureFontMetrics, needsRaster
|
|
|
152
203
|
raw = raw.replace(/\b\p{L}/gu, (ch) => ch.toUpperCase());
|
|
153
204
|
if (!raw.trim())
|
|
154
205
|
continue;
|
|
155
|
-
|
|
206
|
+
// DM-747: when `<mi>` math-italic substitution applies, the element's
|
|
207
|
+
// aggregate `text` field should carry the substituted codepoint too —
|
|
208
|
+
// it's used for aria-label / accessibility and matches the painted
|
|
209
|
+
// glyph. The per-character ranges still measure against the original
|
|
210
|
+
// textContent (see below).
|
|
211
|
+
const rawForText = mathItalicizeMi ? mathItalicChar(raw.trim()) : raw.trim();
|
|
212
|
+
text += rawForText + ' ';
|
|
156
213
|
// Group characters by their laid-out line (matching rect.top).
|
|
157
214
|
const lines = [];
|
|
158
215
|
let cur = null;
|
|
@@ -169,7 +226,15 @@ export const createTextSegmentsHandler = ({ vp, measureFontMetrics, needsRaster
|
|
|
169
226
|
i += step - 1;
|
|
170
227
|
continue;
|
|
171
228
|
}
|
|
172
|
-
|
|
229
|
+
let ch = raw.slice(i, i + step);
|
|
230
|
+
// DM-747: see `mathItalicizeMi` block above — apply the mathvariant=
|
|
231
|
+
// italic substitution AFTER the Range-based measurement so the Range
|
|
232
|
+
// offsets stay valid against the original textContent (`a`, 1 code
|
|
233
|
+
// unit) while the captured glyph string carries the surrogate-pair
|
|
234
|
+
// math-italic codepoint (`𝑎`, 2 code units) that the downstream
|
|
235
|
+
// shaping pipeline picks up.
|
|
236
|
+
if (mathItalicizeMi)
|
|
237
|
+
ch = mathItalicChar(ch);
|
|
173
238
|
const charRec = { ch, left: cr.left, top: cr.top, right: cr.right, bottom: cr.bottom };
|
|
174
239
|
if (cur == null || Math.abs(cr.top - cur.top) > 1) {
|
|
175
240
|
if (cur != null)
|
|
@@ -250,13 +315,42 @@ export const createTextSegmentsHandler = ({ vp, measureFontMetrics, needsRaster
|
|
|
250
315
|
if (isFirstLetter)
|
|
251
316
|
firstCharSeen = true;
|
|
252
317
|
if ((cp != null && needsRaster(cp, nextCp)) || isFirstLetter) {
|
|
318
|
+
// DM-823: for floated ::first-letter drop caps (initial-letter:
|
|
319
|
+
// N M), `Range.getBoundingClientRect()` returns the line-box of
|
|
320
|
+
// the first character in the NORMAL flow — but Chrome paints the
|
|
321
|
+
// float at a much larger box (~N × parent-line-height tall). The
|
|
322
|
+
// bitmap captured at the Range rect gets vertically truncated,
|
|
323
|
+
// visible as a clipped W / B / T drop cap in the rendered SVG.
|
|
324
|
+
// Expand the rasterRect downward by the difference between
|
|
325
|
+
// (N × parent-line-height) and the natural-flow Range height so
|
|
326
|
+
// the screenshot includes the full painted glyph. Width stays
|
|
327
|
+
// unchanged — Chrome's drop cap matches the natural-flow
|
|
328
|
+
// first-char width.
|
|
329
|
+
let rasterTop = cRec.top - vp.y;
|
|
330
|
+
let rasterHeight = cRec.bottom - cRec.top;
|
|
331
|
+
if (isFirstLetter) {
|
|
332
|
+
const ilRaw = flStyle.initialLetter || flStyle.webkitInitialLetter || '';
|
|
333
|
+
const ilN = parseFloat(ilRaw);
|
|
334
|
+
const parentLineHeight = parseFloat(cs.lineHeight);
|
|
335
|
+
if (Number.isFinite(ilN) && ilN > 1 && Number.isFinite(parentLineHeight) && parentLineHeight > 0) {
|
|
336
|
+
const expectedHeight = ilN * parentLineHeight;
|
|
337
|
+
if (expectedHeight > rasterHeight) {
|
|
338
|
+
// Extend downward only — Chrome aligns the cap-top at the
|
|
339
|
+
// first line's cap-height position; extra space the
|
|
340
|
+
// expansion captures past the actual ink is empty (will
|
|
341
|
+
// raster as transparent, with `omitBackground: true`) and
|
|
342
|
+
// doesn't paint anything visible.
|
|
343
|
+
rasterHeight = expectedHeight;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
253
347
|
rasterGlyphs.push({
|
|
254
348
|
charIndex: utf16Idx,
|
|
255
349
|
rect: {
|
|
256
350
|
x: cRec.left - vp.x,
|
|
257
|
-
y:
|
|
351
|
+
y: rasterTop,
|
|
258
352
|
width: cRec.right - cRec.left,
|
|
259
|
-
height:
|
|
353
|
+
height: rasterHeight,
|
|
260
354
|
},
|
|
261
355
|
// ::first-letter drop caps: suppress the path glyph so
|
|
262
356
|
// only the rasterized big letter paints (DM-439).
|
|
@@ -130,6 +130,22 @@ export const createTransformsHandler = () => {
|
|
|
130
130
|
? frozenTransform
|
|
131
131
|
: 'none',
|
|
132
132
|
transformOrigin: cs.transformOrigin,
|
|
133
|
+
// DM-751: extract `matrix3d` translateZ so the paint-order sort can
|
|
134
|
+
// honor 3D Z position when the parent element has
|
|
135
|
+
// `transform-style: preserve-3d` (which sorts children by Z in 3D
|
|
136
|
+
// space, not by z-index per CSS Transforms 2 §6). We can't represent
|
|
137
|
+
// perspective / actual 3D rendering in SVG; this is paint-order only.
|
|
138
|
+
translateZ: (function () {
|
|
139
|
+
const tt = cs.transform;
|
|
140
|
+
if (tt == null || tt === 'none' || tt === '')
|
|
141
|
+
return undefined;
|
|
142
|
+
const m3 = /^matrix3d\(([^)]+)\)/.exec(tt);
|
|
143
|
+
if (m3 == null)
|
|
144
|
+
return undefined;
|
|
145
|
+
const parts = m3[1].split(',').map((s) => parseFloat(s.trim()));
|
|
146
|
+
const tz = parts[14];
|
|
147
|
+
return Number.isFinite(tz) && tz !== 0 ? tz : undefined;
|
|
148
|
+
})(),
|
|
133
149
|
// DM-587: separately flag elements that ORIGINALLY had a non-none
|
|
134
150
|
// transform — even though we discard the value to suppress the wrap,
|
|
135
151
|
// CSS Transforms 2 §4 says any non-none transform creates a stacking
|