domotion-svg 0.3.3 → 0.4.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/animation/animator.d.ts +7 -0
- package/dist/animation/animator.js +2 -2
- package/dist/capture/index.js +6 -1
- package/dist/cli/animate.js +11 -3
- package/dist/cli/capture.js +2 -1
- package/dist/render/element-tree-to-svg.d.ts +11 -1
- package/dist/render/element-tree-to-svg.js +19 -3
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +5 -1
- package/dist/render/text-to-path.d.ts +26 -0
- package/dist/render/text-to-path.js +386 -10
- package/dist/render/text-to-path.test.js +176 -86
- package/dist/render/text.test.js +6 -1
- package/dist/scroll/composer.js +6 -5
- package/package.json +1 -1
- package/src/animation/animator.ts +9 -2
- package/src/capture/index.ts +6 -1
- package/src/cli/animate.ts +12 -2
- package/src/cli/capture.ts +2 -0
- package/src/render/element-tree-to-svg.ts +18 -2
- package/src/render/index.ts +8 -0
- package/src/render/text-to-path.test.ts +186 -86
- package/src/render/text-to-path.ts +419 -23
- package/src/render/text.test.ts +7 -1
- package/src/scroll/composer.ts +6 -4
|
@@ -155,6 +155,13 @@ export interface AnimationConfig {
|
|
|
155
155
|
* them in every frame's local defs.
|
|
156
156
|
*/
|
|
157
157
|
sharedDefs?: string;
|
|
158
|
+
/**
|
|
159
|
+
* DM-839: embedded-font `@font-face` rules collected once across all frames
|
|
160
|
+
* (the caller renders each frame with `includeEmbeddedFontCss=false` and
|
|
161
|
+
* passes the accumulated `getEmbeddedFontFaceCss()` here). Injected into the
|
|
162
|
+
* top-level `<style>` so the base64 font bytes appear once, not per frame.
|
|
163
|
+
*/
|
|
164
|
+
fontFaceCss?: string;
|
|
158
165
|
/**
|
|
159
166
|
* Optional cursor / click overlay (DM-277). Renders a macOS-style cursor
|
|
160
167
|
* moving along the script timeline with QuickTime-style click pulses.
|
|
@@ -222,7 +222,7 @@ export function generateAnimatedSvg(config) {
|
|
|
222
222
|
<clipPath id="viewport-clip"><rect width="${width}" height="${height}" /></clipPath>${sharedDefsMarkup}
|
|
223
223
|
</defs>
|
|
224
224
|
<style>
|
|
225
|
-
:root { --scene-dur: ${totalSec.toFixed(2)}s; }
|
|
225
|
+
${config.fontFaceCss != null && config.fontFaceCss !== "" ? config.fontFaceCss + "\n" : ""} :root { --scene-dur: ${totalSec.toFixed(2)}s; }
|
|
226
226
|
.f { opacity: 0; visibility: hidden; }
|
|
227
227
|
${keyframes.join("\n")}${animationCss}${cullCss === "" ? "" : "\n" + cullCss}
|
|
228
228
|
</style>
|
|
@@ -444,7 +444,7 @@ function composeMergedSvg(config, frameTiming, totalSec) {
|
|
|
444
444
|
<clipPath id="viewport-clip"><rect width="${width}" height="${height}" /></clipPath>${sharedDefsMarkup}
|
|
445
445
|
</defs>
|
|
446
446
|
<style>
|
|
447
|
-
:root { --scene-dur: ${totalSec.toFixed(2)}s; }
|
|
447
|
+
${config.fontFaceCss != null && config.fontFaceCss !== "" ? config.fontFaceCss + "\n" : ""} :root { --scene-dur: ${totalSec.toFixed(2)}s; }
|
|
448
448
|
${css}${animationCss}${cullCss === "" ? "" : "\n" + cullCss}
|
|
449
449
|
</style>
|
|
450
450
|
<g clip-path="url(#viewport-clip)">
|
package/dist/capture/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { elementTreeToSvg } from "../render/element-tree-to-svg.js";
|
|
|
10
10
|
import { embedRemoteImages } from "./embed.js";
|
|
11
11
|
import { resizeEmbeddedImages } from "../tree-ops/resize-embedded-images.js";
|
|
12
12
|
import { rasterizeConicGradients } from "../render/conic-raster.js";
|
|
13
|
-
import { registerLocalFontAlias, registerWebfont } from "../render/text-to-path.js";
|
|
13
|
+
import { clearEmbeddedFonts, registerLocalFontAlias, registerWebfont } from "../render/text-to-path.js";
|
|
14
14
|
import { CAPTURE_SCRIPT } from "./script.generated.js";
|
|
15
15
|
import { rasterizeBitmapGlyphs } from "./emoji.js";
|
|
16
16
|
import { _resetLastCaptureWarnings } from "./warnings.js";
|
|
@@ -141,6 +141,10 @@ export class DemoRecorder {
|
|
|
141
141
|
// tree contains no conic content; otherwise the renderer (DM-550) emits
|
|
142
142
|
// <pattern><image href="data:..."/></pattern> instead of dropping the layer.
|
|
143
143
|
await rasterizeConicGradients(tree, { hiDPIFactor: this.embedRemoteImagesHiDPIFactor });
|
|
144
|
+
// DM-839: reset the embedded-font builder so this capture's `@font-face`
|
|
145
|
+
// block contains only its own fonts (the renderer repopulates it during
|
|
146
|
+
// elementTreeToSvg, which emits the CSS into this frame's <defs>).
|
|
147
|
+
clearEmbeddedFonts();
|
|
144
148
|
return elementTreeToSvg(tree, this.width, this.height, idPrefix, true, this.embedRemoteImagesHiDPIFactor ?? 2);
|
|
145
149
|
}
|
|
146
150
|
/**
|
|
@@ -165,6 +169,7 @@ export class DemoRecorder {
|
|
|
165
169
|
}
|
|
166
170
|
// DM-549: rasterize conic-gradient layers — see captureCurrent above.
|
|
167
171
|
await rasterizeConicGradients(tree, { hiDPIFactor: this.embedRemoteImagesHiDPIFactor });
|
|
172
|
+
clearEmbeddedFonts(); // DM-839: see captureCurrent
|
|
168
173
|
const svgContent = elementTreeToSvg(tree, this.width, pageHeight, idPrefix, true, this.embedRemoteImagesHiDPIFactor ?? 2);
|
|
169
174
|
return { svgContent, pageHeight };
|
|
170
175
|
}
|
package/dist/cli/animate.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
9
|
import { dirname, resolve } from "node:path";
|
|
10
10
|
import { parseArgs } from "node:util";
|
|
11
|
-
import { captureElementTree, clearWebfonts, composeScrollSvg, cullElementsOutsideViewBox, elementTreeToSvg, executeScrollPattern, generateAnimatedSvg, launchChromium, optimizeSvg, parseScrollPattern, } from "../index.js";
|
|
11
|
+
import { captureElementTree, clearEmbeddedFonts, clearWebfonts, composeScrollSvg, cullElementsOutsideViewBox, elementTreeToSvg, executeScrollPattern, generateAnimatedSvg, getEmbeddedFontFaceCss, launchChromium, optimizeSvg, parseScrollPattern, } from "../index.js";
|
|
12
12
|
import { attachWebfontTracker, discoverAndRegisterWebfonts } from "../capture/index.js";
|
|
13
13
|
import { applyReadyWaits, isSvgzPath, loadInputIntoPage, makeLogger, resolveOutputPath, timed, writeOutput, } from "./common.js";
|
|
14
14
|
export async function runAnimate(args, help) {
|
|
@@ -61,6 +61,11 @@ export async function runAnimate(args, help) {
|
|
|
61
61
|
// same registry. Multiple frames declaring the same family register
|
|
62
62
|
// multiple variants and the resolver picks the closest weight/style.
|
|
63
63
|
clearWebfonts();
|
|
64
|
+
// DM-839: embedded-font is the default render mode. Reset the builder once
|
|
65
|
+
// here; each frame renders with includeEmbeddedFontCss=false (below) and we
|
|
66
|
+
// collect the deduped @font-face block once into the animator's top-level
|
|
67
|
+
// <style> after the loop — so the base64 font bytes appear once, not per frame.
|
|
68
|
+
clearEmbeddedFonts();
|
|
64
69
|
// One tracker for the whole animate run — fonts fetched by any frame
|
|
65
70
|
// get accumulated, and we deduplicate URLs inside discoverAndRegister.
|
|
66
71
|
const tracker = attachWebfontTracker(page);
|
|
@@ -156,7 +161,7 @@ export async function runAnimate(args, help) {
|
|
|
156
161
|
const totalDurationMs = cfg.frames.reduce((sum, f) => sum + f.duration + (f.transition?.type === "cut" ? 0 : (f.transition?.duration ?? 300)), 0);
|
|
157
162
|
const result = cullElementsOutsideViewBox(tree, cfg.width, cfg.height, resolvedAnimations, frameStartMs, totalDurationMs);
|
|
158
163
|
frameCullCss = result.css;
|
|
159
|
-
svgContent = elementTreeToSvg(tree, cfg.width, cfg.height, `f${i}
|
|
164
|
+
svgContent = elementTreeToSvg(tree, cfg.width, cfg.height, `f${i}-`, true, 2, false);
|
|
160
165
|
}
|
|
161
166
|
// Resolve SVG-kind overlays: read each `src` from disk, namespace its
|
|
162
167
|
// ids, and replace with `innerSvg`. Other overlay kinds pass through
|
|
@@ -172,7 +177,10 @@ export async function runAnimate(args, help) {
|
|
|
172
177
|
});
|
|
173
178
|
}
|
|
174
179
|
tracker.detach();
|
|
175
|
-
|
|
180
|
+
// DM-839: collect the embedded-font @font-face rules accumulated across all
|
|
181
|
+
// frames once, for the animator's top-level <style>.
|
|
182
|
+
const fontFaceCss = getEmbeddedFontFaceCss();
|
|
183
|
+
let svg = await timed(log, `Composed animated SVG (${cfg.frames.length} frames)`, () => Promise.resolve(generateAnimatedSvg({ width: cfg.width, height: cfg.height, frames, fontFaceCss })));
|
|
176
184
|
// svgz is auto-detected from the output filename; implies --optimize
|
|
177
185
|
// unless --no-optimize was passed.
|
|
178
186
|
const outputArg = values.output ?? cfg.output;
|
package/dist/cli/capture.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* scroll machinery.
|
|
7
7
|
*/
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
|
-
import { captureElementTree, clearWebfonts, composeScrollSvg, cullElementsOutsideViewBox, elementTreeToSvg, executeScrollPattern, launchChromium, logCaptureWarnings, optimizeSvg, parseScrollPattern, wrapSvg, } from "../index.js";
|
|
9
|
+
import { captureElementTree, clearEmbeddedFonts, clearWebfonts, composeScrollSvg, cullElementsOutsideViewBox, elementTreeToSvg, executeScrollPattern, launchChromium, logCaptureWarnings, optimizeSvg, parseScrollPattern, wrapSvg, } from "../index.js";
|
|
10
10
|
import { attachWebfontTracker, discoverAndRegisterWebfonts } from "../capture/index.js";
|
|
11
11
|
import { applyReadyWaits, isSvgzPath, loadInputIntoPage, makeLogger, parseColorScheme, parseIntFlag, parseTuple, resolveOutputPath, timed, writeOutput, } from "./common.js";
|
|
12
12
|
export async function runCapture(args, help) {
|
|
@@ -139,6 +139,7 @@ export async function runCapture(args, help) {
|
|
|
139
139
|
// so this is a defense-in-depth pass for the position:fixed-descendant
|
|
140
140
|
// escape cases where an off-viewport ancestor still gets captured.
|
|
141
141
|
cullElementsOutsideViewBox(tree, clip[2], clip[3], undefined, 0, 1);
|
|
142
|
+
clearEmbeddedFonts(); // DM-839: reset embedded-font builder before this single-frame render
|
|
142
143
|
const inner = elementTreeToSvg(tree, clip[2], clip[3]);
|
|
143
144
|
svg = wrapSvg(inner, clip[2], clip[3]);
|
|
144
145
|
}
|
|
@@ -67,7 +67,17 @@ includeGlyphDefs?: boolean,
|
|
|
67
67
|
* `resizeEmbeddedImages` for the same tree, or the lookup misses and
|
|
68
68
|
* the renderer falls back to the source-resolution data URI. Default 2.
|
|
69
69
|
*/
|
|
70
|
-
hiDPIFactor?: number
|
|
70
|
+
hiDPIFactor?: number,
|
|
71
|
+
/**
|
|
72
|
+
* DM-839: when true, emit the embedded-font `@font-face` rules
|
|
73
|
+
* (`getEmbeddedFontFaceCss()`) as a `<style>` inside this frame's `<defs>`.
|
|
74
|
+
* Defaults to `includeGlyphDefs` — single-frame standalone producers
|
|
75
|
+
* (capture, CLI, the test harness) own their top-level defs and so should
|
|
76
|
+
* carry their font CSS here. Multi-frame producers (animator, scroll
|
|
77
|
+
* composer) pass `false` and collect the CSS once at the top level instead,
|
|
78
|
+
* so the (potentially large) base64 font bytes aren't duplicated per frame.
|
|
79
|
+
*/
|
|
80
|
+
includeEmbeddedFontCss?: boolean): string;
|
|
71
81
|
/**
|
|
72
82
|
* Rewrite a captured `<mask>` element's `outerHTML` so it can be safely
|
|
73
83
|
* inlined in the output SVG's `<defs>`. The mask's own `id` becomes
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Uses Playwright to inspect DOM elements and recreate them as native SVG.
|
|
5
5
|
*/
|
|
6
6
|
import { renderSingleLineText, renderMultiSegmentText, renderMultiLineText, renderInputText } from "./text.js";
|
|
7
|
-
import { getGlyphDefs, measureLastGlyphRsb } from "./text-to-path.js";
|
|
7
|
+
import { getEmbeddedFontFaceCss, getGlyphDefs, measureLastGlyphRsb } from "./text-to-path.js";
|
|
8
8
|
import { renderFormControl } from "./form-controls.js";
|
|
9
9
|
import { r, esc, stopFmt } from "./format.js";
|
|
10
10
|
import { parseColor, colorStr, sameColor } from "./colors.js";
|
|
@@ -104,7 +104,17 @@ includeGlyphDefs = true,
|
|
|
104
104
|
* `resizeEmbeddedImages` for the same tree, or the lookup misses and
|
|
105
105
|
* the renderer falls back to the source-resolution data URI. Default 2.
|
|
106
106
|
*/
|
|
107
|
-
hiDPIFactor = 2
|
|
107
|
+
hiDPIFactor = 2,
|
|
108
|
+
/**
|
|
109
|
+
* DM-839: when true, emit the embedded-font `@font-face` rules
|
|
110
|
+
* (`getEmbeddedFontFaceCss()`) as a `<style>` inside this frame's `<defs>`.
|
|
111
|
+
* Defaults to `includeGlyphDefs` — single-frame standalone producers
|
|
112
|
+
* (capture, CLI, the test harness) own their top-level defs and so should
|
|
113
|
+
* carry their font CSS here. Multi-frame producers (animator, scroll
|
|
114
|
+
* composer) pass `false` and collect the CSS once at the top level instead,
|
|
115
|
+
* so the (potentially large) base64 font bytes aren't duplicated per frame.
|
|
116
|
+
*/
|
|
117
|
+
includeEmbeddedFontCss = includeGlyphDefs) {
|
|
108
118
|
setActiveHiDPIFactor(hiDPIFactor);
|
|
109
119
|
const svgParts = [];
|
|
110
120
|
const defsParts = [];
|
|
@@ -2956,7 +2966,13 @@ hiDPIFactor = 2) {
|
|
|
2956
2966
|
// `getEmbeddedFontFaceCss()` themselves once at the top level (see how
|
|
2957
2967
|
// `composeScrollSvg` injects it into the outer <style>).
|
|
2958
2968
|
const glyphDefsMarkup = includeGlyphDefs ? getGlyphDefs() : "";
|
|
2959
|
-
|
|
2969
|
+
// DM-839: embedded-font `@font-face` rules for the text runs rendered above
|
|
2970
|
+
// (empty in paths mode, or when no run was embeddable). A `<style>` inside
|
|
2971
|
+
// `<defs>` is valid SVG. Single-frame producers emit it here; multi-frame
|
|
2972
|
+
// producers pass includeEmbeddedFontCss=false and inject once at the top.
|
|
2973
|
+
const embeddedFontCss = includeEmbeddedFontCss ? getEmbeddedFontFaceCss() : "";
|
|
2974
|
+
const fontStyleMarkup = embeddedFontCss !== "" ? `<style>${embeddedFontCss}</style>` : "";
|
|
2975
|
+
const allDefs = defsParts.join("") + glyphDefsMarkup + fontStyleMarkup;
|
|
2960
2976
|
const defs = allDefs !== "" ? ` <defs>${allDefs}</defs>\n` : "";
|
|
2961
2977
|
return defs + svgParts.join("\n");
|
|
2962
2978
|
}
|
package/dist/render/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { elementTreeToSvg, wrapSvg, } from "./element-tree-to-svg.js";
|
|
2
|
-
export { getGlyphDefs, clearGlyphDefs, registerWebfont, clearWebfonts, } from "./text-to-path.js";
|
|
2
|
+
export { getGlyphDefs, clearGlyphDefs, registerWebfont, clearWebfonts, setRenderTextMode, getRenderTextMode, clearEmbeddedFonts, getEmbeddedFontFaceCss, type RenderTextMode, } from "./text-to-path.js";
|
package/dist/render/index.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// Render-side public surface. Pure functions that take a captured element
|
|
2
2
|
// tree (or fragments of one) and emit SVG markup.
|
|
3
3
|
export { elementTreeToSvg, wrapSvg, } from "./element-tree-to-svg.js";
|
|
4
|
-
export { getGlyphDefs, clearGlyphDefs, registerWebfont, clearWebfonts,
|
|
4
|
+
export { getGlyphDefs, clearGlyphDefs, registerWebfont, clearWebfonts,
|
|
5
|
+
// DM-839: embedded-font is the default render mode; expose the controls so
|
|
6
|
+
// consumers can opt back into per-pixel-faithful `paths` mode and manage the
|
|
7
|
+
// embedded-font lifecycle.
|
|
8
|
+
setRenderTextMode, getRenderTextMode, clearEmbeddedFonts, getEmbeddedFontFaceCss, } from "./text-to-path.js";
|
|
@@ -109,6 +109,12 @@ export declare function __pickWebfontVariantMetaForCodepointForTest(family: stri
|
|
|
109
109
|
/** Drop all registered webfonts. Call at the start of a fresh capture run. */
|
|
110
110
|
export declare function clearWebfonts(): void;
|
|
111
111
|
export declare function registerLocalFontAlias(family: string, resolvedKey: string, weight?: number, italic?: boolean): void;
|
|
112
|
+
/** Test-only window into the platform path resolver (DM-258). */
|
|
113
|
+
export declare function __resolveFontSpecForTest(key: string): {
|
|
114
|
+
path: string;
|
|
115
|
+
postscriptName?: string;
|
|
116
|
+
extractor?: string;
|
|
117
|
+
} | null;
|
|
112
118
|
/**
|
|
113
119
|
* Ordered list of fallback font keys to try when the primary font lacks a
|
|
114
120
|
* glyph for `codepoint`. Caller iterates the chain and picks the first font
|
|
@@ -139,7 +145,27 @@ export declare function registerLocalFontAlias(family: string, resolvedKey: stri
|
|
|
139
145
|
* "ja" / "ja-JP" → hiragino-jp (PingFang has no JP subfont on macOS)
|
|
140
146
|
*/
|
|
141
147
|
export declare function pingfangKeyForLang(lang: string | undefined): string | null;
|
|
148
|
+
/**
|
|
149
|
+
* Linux fallback chain (DM-259) — calibrated to what Chromium-on-Linux paints
|
|
150
|
+
* in the Playwright `*-noble` CI image, measured via CDP
|
|
151
|
+
* `CSS.getPlatformFontsForNode` (tools/probe-fallbacks-linux.mjs). Returns
|
|
152
|
+
* logical keys that `LINUX_FONT_PATHS` maps to the image's real faces
|
|
153
|
+
* (Liberation / WenQuanYi / FreeFont / Loma / IPAGothic). As on macOS, the
|
|
154
|
+
* caller has already tried the primary font, so what reaches here is the
|
|
155
|
+
* residue the primary lacks. The comment after each branch names the face the
|
|
156
|
+
* probe showed Chromium using for that block.
|
|
157
|
+
*/
|
|
158
|
+
export declare function linuxFallbackChain(codepoint: number, primaryKey?: string, _lang?: string): string[];
|
|
142
159
|
export declare function fallbackFontChain(codepoint: number, primaryKey?: string, lang?: string): string[];
|
|
160
|
+
/**
|
|
161
|
+
* macOS (CoreText) fallback chain — reverse-engineered from Chromium-on-macOS
|
|
162
|
+
* painted widths (DM-241 / DM-256 / DM-257 / …). Exported so the macOS-
|
|
163
|
+
* calibration unit tests assert it directly: the suite runs on Linux in CI,
|
|
164
|
+
* where `fallbackFontChain` dispatches to `linuxFallbackChain`, so those tests
|
|
165
|
+
* must call this function (not `fallbackFontChain`) to validate macOS routing
|
|
166
|
+
* regardless of the host platform (DM-842).
|
|
167
|
+
*/
|
|
168
|
+
export declare function darwinFallbackChain(codepoint: number, primaryKey?: string, lang?: string): string[];
|
|
143
169
|
/** @deprecated Single-key wrapper for back-compat — prefer `fallbackFontChain`. */
|
|
144
170
|
export declare function fallbackFontKey(codepoint: number): string | null;
|
|
145
171
|
export declare function resolveFontKey(fontFamily: string): string;
|