domotion-svg 0.29.0 → 0.30.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 +12 -0
- package/README.md +29 -3
- package/assets/fonts/fixture/README.md +4 -4
- package/dist/animation/caret-metrics.js +8 -4
- package/dist/animation/caret-track.js +16 -8
- package/dist/animation/composite.js +23 -12
- package/dist/animation/compressed-run.d.ts +11 -0
- package/dist/animation/compressed-run.js +134 -33
- package/dist/animation/cursor-glyphs.js +24 -5
- package/dist/animation/cursor-overlay.js +6 -6
- package/dist/animation/embed-namespace.js +6 -1
- package/dist/animation/embed-timeline.js +54 -10
- package/dist/animation/index.d.ts +3 -3
- package/dist/animation/index.js +3 -3
- package/dist/animation/magic-move.js +22 -16
- package/dist/animation/motion-presets.js +35 -9
- package/dist/animation/overlay-schema.js +9 -4
- package/dist/animation/resolve-overlays.js +14 -5
- package/dist/animation/svg-generator.d.ts +1 -1
- package/dist/animation/svg-generator.js +283 -133
- package/dist/animation/text-address.js +13 -4
- package/dist/animation/transition-schema.js +264 -60
- package/dist/capture/animated-image-static-frame.js +86 -35
- package/dist/capture/animation-frame.js +26 -11
- package/dist/capture/authenticated-animated-image-bytes.js +106 -44
- package/dist/capture/backdrop-composite-raster.js +46 -25
- package/dist/capture/backdrop-effect-neutralization.js +17 -11
- package/dist/capture/backdrop-effect-space.js +18 -19
- package/dist/capture/backdrop-isolation.js +7 -8
- package/dist/capture/background-image-sizing.js +84 -43
- package/dist/capture/broken-image-fallback.js +43 -34
- package/dist/capture/broken-image-icon-raster.js +8 -4
- package/dist/capture/collapsed-border-fragment-cdp.js +94 -75
- package/dist/capture/collapsed-border-fragment-record.js +111 -91
- package/dist/capture/effective-appearance-cdp.js +5 -5
- package/dist/capture/effective-appearance.js +70 -25
- package/dist/capture/embed.d.ts +4 -2
- package/dist/capture/embed.js +27 -14
- package/dist/capture/emoji.js +91 -40
- package/dist/capture/frame-scroll-state.js +114 -80
- package/dist/capture/generic-font-probe.js +23 -13
- package/dist/capture/index.d.ts +6 -4
- package/dist/capture/index.js +220 -115
- package/dist/capture/input-value-geometry.js +4 -4
- package/dist/capture/line-clamp.d.ts +2 -2
- package/dist/capture/line-clamp.js +63 -58
- package/dist/capture/native-control-decoration-raster.js +124 -77
- package/dist/capture/native-control-decoration.d.ts +1 -1
- package/dist/capture/native-control-decoration.js +21 -14
- package/dist/capture/native-control-raster.js +54 -25
- package/dist/capture/native-scrollbar-raster.js +24 -23
- package/dist/capture/projective-owner.d.ts +1 -10
- package/dist/capture/pseudo-backdrop-isolation.js +5 -4
- package/dist/capture/pseudo-fragment-cdp.js +111 -68
- package/dist/capture/pseudo-fragment-protocol.js +45 -24
- package/dist/capture/pseudo-style-cdp.d.ts +1 -1
- package/dist/capture/pseudo-style-cdp.js +59 -46
- package/dist/capture/raf-clock.js +28 -18
- package/dist/capture/replaced-element-raster.js +42 -31
- package/dist/capture/replaced-media-frame.js +51 -23
- package/dist/capture/replaced-snapshot-geometry.js +19 -13
- package/dist/capture/script/color-norm.js +15 -13
- package/dist/capture/script/dotted-circle-detect.js +9 -9
- package/dist/capture/script/emoji-detect.js +18 -14
- package/dist/capture/script/font-family-default.js +70 -39
- package/dist/capture/script/font-feature-values.js +13 -9
- package/dist/capture/script/font-metrics.js +26 -21
- package/dist/capture/script/font-palette.js +17 -9
- package/dist/capture/script/index.js +459 -420
- package/dist/capture/script/line-clamp.d.ts +4 -4
- package/dist/capture/script/line-clamp.js +91 -95
- package/dist/capture/script/placeholder-shown.js +11 -7
- package/dist/capture/script/pseudo-rules.js +16 -18
- package/dist/capture/script/utils.js +24 -25
- package/dist/capture/script/walker/background-attachment.d.ts +1 -1
- package/dist/capture/script/walker/background-attachment.js +59 -48
- package/dist/capture/script/walker/borders-backgrounds.d.ts +1 -1
- package/dist/capture/script/walker/borders-backgrounds.js +204 -120
- package/dist/capture/script/walker/capture-phases.js +58 -53
- package/dist/capture/script/walker/collapsed-border.js +45 -10
- package/dist/capture/script/walker/counter-prewalk.js +33 -19
- package/dist/capture/script/walker/counter-style-resolver.js +58 -46
- package/dist/capture/script/walker/fieldset-legend.js +3 -3
- package/dist/capture/script/walker/form-controls.d.ts +1 -1
- package/dist/capture/script/walker/form-controls.js +176 -147
- package/dist/capture/script/walker/fragmentation.js +53 -37
- package/dist/capture/script/walker/inline-svg-decisions.d.ts +8 -0
- package/dist/capture/script/walker/inline-svg-decisions.js +37 -0
- package/dist/capture/script/walker/inline-svg.js +212 -187
- package/dist/capture/script/walker/input-value.d.ts +1 -1
- package/dist/capture/script/walker/input-value.js +70 -62
- package/dist/capture/script/walker/lists-counters.d.ts +1 -1
- package/dist/capture/script/walker/lists-counters.js +17 -15
- package/dist/capture/script/walker/masks-clips-decisions.d.ts +15 -0
- package/dist/capture/script/walker/masks-clips-decisions.js +105 -0
- package/dist/capture/script/walker/masks-clips.js +172 -224
- package/dist/capture/script/walker/pseudo-content.d.ts +59 -3
- package/dist/capture/script/walker/pseudo-content.js +493 -470
- package/dist/capture/script/walker/pseudo-inject.js +36 -24
- package/dist/capture/script/walker/replaced-elements.js +18 -13
- package/dist/capture/script/walker/resize-handle.js +21 -21
- package/dist/capture/script/walker/text-segments.d.ts +139 -0
- package/dist/capture/script/walker/text-segments.js +607 -540
- package/dist/capture/script/walker/transforms.js +23 -22
- package/dist/capture/script/warnings.js +7 -7
- package/dist/capture/script.generated.js +1 -1
- package/dist/capture/scrollbar-capture.js +119 -87
- package/dist/capture/summary-marker-cdp.js +32 -16
- package/dist/capture/svg-affine-freeze.js +12 -8
- package/dist/capture/text-fragment-geometry.js +9 -9
- package/dist/capture/text-fragment-spans.js +87 -40
- package/dist/capture/text-line-origin.js +4 -6
- package/dist/capture/text-paint-geometry-cdp.js +70 -45
- package/dist/capture/tree-envelope.js +15 -18
- package/dist/capture/types.d.ts +6 -24
- package/dist/capture/warnings.d.ts +8 -7
- package/dist/capture/warnings.js +11 -8
- package/dist/cli/animate-artifact.js +1 -3
- package/dist/cli/animate-capture-session.js +1 -3
- package/dist/cli/animate-captured-frame-stage.d.ts +42 -0
- package/dist/cli/animate-captured-frame-stage.js +29 -0
- package/dist/cli/animate-command.js +23 -0
- package/dist/cli/animate-compression.d.ts +42 -0
- package/dist/cli/animate-compression.js +307 -0
- package/dist/cli/animate-debug.js +8 -8
- package/dist/cli/animate-frame-capture.d.ts +5 -0
- package/dist/cli/animate-frame-capture.js +1 -1
- package/dist/cli/animate-orchestrator.d.ts +4 -44
- package/dist/cli/animate-orchestrator.js +442 -523
- package/dist/cli/animate-states-run-stage.d.ts +7 -0
- package/dist/cli/animate-states-run-stage.js +6 -0
- package/dist/cli/capture.js +36 -103
- package/dist/cli/common.js +16 -10
- package/dist/cli/composite.js +36 -13
- package/dist/cli/hover-detect.js +18 -7
- package/dist/cli/index.js +14 -22
- package/dist/cli/mutation-detect.d.ts +1 -0
- package/dist/cli/mutation-detect.js +31 -10
- package/dist/cli/review.d.ts +1 -1
- package/dist/cli/review.js +20 -8
- package/dist/cli/scrubber.js +6 -2
- package/dist/cli/storyboard.js +8 -2
- package/dist/cli/studio.js +6 -2
- package/dist/cli/svg-to-image.js +1 -1
- package/dist/cli/svg-to-video-core.js +15 -10
- package/dist/cli/template.js +11 -11
- package/dist/cli/term.js +3 -5
- package/dist/cli/type-resample.js +36 -10
- package/dist/font-family-stack.js +8 -8
- package/dist/font-feature-values-cascade.js +1 -2
- package/dist/index.d.ts +5 -17
- package/dist/index.js +4 -11
- package/dist/post-processing/hoist-image-payloads.js +11 -11
- package/dist/post-processing/optimize.js +5 -2
- package/dist/render/advanced-gradient-raster.js +4 -7
- package/dist/render/background-attachment.js +7 -5
- package/dist/render/background-inline-paint.d.ts +97 -0
- package/dist/render/background-inline-paint.js +616 -0
- package/dist/render/border-paint.d.ts +44 -0
- package/dist/render/border-paint.js +894 -0
- package/dist/render/borders.d.ts +4 -4
- package/dist/render/borders.js +230 -86
- package/dist/render/broken-image-fallback.js +1 -2
- package/dist/render/clip-path.js +47 -13
- package/dist/render/cluster-fallback.js +96 -37
- package/dist/render/colors.js +9 -3
- package/dist/render/conic-raster.js +1 -1
- package/dist/render/culling-geometry.js +101 -75
- package/dist/render/custom-scrollbar.js +1 -3
- package/dist/render/decoration-fragment-ownership.js +19 -11
- package/dist/render/device-chrome.js +19 -15
- package/dist/render/element-tree-to-svg.d.ts +100 -12
- package/dist/render/element-tree-to-svg.js +575 -2452
- package/dist/render/embedded-font-builder.d.ts +1 -1
- package/dist/render/embedded-font-builder.js +50 -35
- package/dist/render/embolden-outline.js +10 -12
- package/dist/render/emoji-presentation-priority.js +6 -2
- package/dist/render/font-resolution.d.ts +9 -4
- package/dist/render/font-resolution.js +826 -526
- package/dist/render/form-controls.js +93 -57
- package/dist/render/generic-script-families.js +446 -125
- package/dist/render/glyph-helper-font.js +70 -28
- package/dist/render/glyph-helper-outline.js +1 -1
- package/dist/render/glyph-helper-transport.js +42 -18
- package/dist/render/glyph-helper.d.ts +3 -3
- package/dist/render/glyph-helper.js +133 -88
- package/dist/render/gradient-defs.js +51 -26
- package/dist/render/gradients.js +47 -18
- package/dist/render/harfbuzz-shaper.d.ts +1 -1
- package/dist/render/harfbuzz-shaper.js +91 -27
- package/dist/render/hb-subset.js +53 -21
- package/dist/render/helper-acquire.js +1 -2
- package/dist/render/helper-availability-contract.js +1 -2
- package/dist/render/host-platform.js +1 -1
- package/dist/render/icu-helper-acquire.js +24 -14
- package/dist/render/icu-helper.js +7 -5
- package/dist/render/image-pattern.js +21 -25
- package/dist/render/index.d.ts +4 -4
- package/dist/render/index.js +3 -3
- package/dist/render/linux-target-strike.js +16 -9
- package/dist/render/list-marker-geometry.js +43 -13
- package/dist/render/mask-origin-clip.js +2 -4
- package/dist/render/mask-position.js +12 -14
- package/dist/render/mask.js +84 -66
- package/dist/render/outline-paint.d.ts +20 -0
- package/dist/render/outline-paint.js +255 -0
- package/dist/render/overflow-clip.js +28 -17
- package/dist/render/paint-order.js +2 -5
- package/dist/render/pseudo-filter.js +1 -4
- package/dist/render/pseudo-fragments.js +69 -36
- package/dist/render/real-text-layer.js +14 -8
- package/dist/render/render-state-collectors.d.ts +11 -0
- package/dist/render/render-state-collectors.js +87 -0
- package/dist/render/resize-handle.js +9 -16
- package/dist/render/script-segmentation.d.ts +1 -1
- package/dist/render/script-segmentation.js +17 -11
- package/dist/render/stacking.js +66 -36
- package/dist/render/svg-inline.d.ts +17 -0
- package/dist/render/svg-inline.js +178 -5
- package/dist/render/svg-viewport-matrix.d.ts +72 -0
- package/dist/render/svg-viewport-matrix.js +139 -0
- package/dist/render/synchronous-scope.js +3 -3
- package/dist/render/synth-test-fonts.js +20 -4
- package/dist/render/synthesis-decision.js +4 -4
- package/dist/render/text-affine.js +31 -27
- package/dist/render/text-paint.d.ts +42 -0
- package/dist/render/text-paint.js +478 -0
- package/dist/render/text-run-provenance.js +42 -15
- package/dist/render/text-to-path.d.ts +3 -3
- package/dist/render/text-to-path.js +375 -271
- package/dist/render/text.js +318 -144
- package/dist/render/unicode-classification.d.ts +2 -2
- package/dist/render/unicode-classification.js +327 -130
- package/dist/render/unicode-font-routing.darwin.generated.js +1 -1
- package/dist/render/vertical-text.d.ts +11 -0
- package/dist/render/vertical-text.js +143 -50
- package/dist/render/win-font-fallback.js +163 -84
- package/dist/review/client.bundle.generated.js +1 -1
- package/dist/review/client.js +3 -1
- package/dist/review/compare-pngs.js +1 -2
- package/dist/review/glyph-compare.js +69 -48
- package/dist/review/linux-unicode-evidence.d.ts +1 -1
- package/dist/review/linux-unicode-evidence.js +180 -27
- package/dist/review/region-overlay.js +10 -2
- package/dist/review/roll-differential.js +48 -12
- package/dist/review/server.d.ts +1 -1
- package/dist/review/server.js +5 -3
- package/dist/review/side-digest.js +6 -1
- package/dist/review/source-drift-gate.js +25 -8
- package/dist/review/stage-evidence.js +10 -9
- package/dist/scroll/composer.d.ts +10 -0
- package/dist/scroll/composer.js +72 -46
- package/dist/scroll/executor.js +30 -17
- package/dist/scroll/hoist-fixed.d.ts +1 -1
- package/dist/scroll/hoist-fixed.js +1 -1
- package/dist/scroll/hoist-sticky.js +1 -3
- package/dist/scroll/pattern.js +18 -7
- package/dist/scrubber/client.bundle.generated.js +1 -1
- package/dist/scrubber/client.js +170 -45
- package/dist/scrubber/crop.js +2 -2
- package/dist/scrubber/embed.js +21 -23
- package/dist/scrubber/server.js +48 -16
- package/dist/scrubber/trim.d.ts +1 -1
- package/dist/scrubber/trim.js +6 -3
- package/dist/studio/agent-tools.js +77 -23
- package/dist/studio/annotations.js +41 -26
- package/dist/studio/app-projects.js +10 -6
- package/dist/studio/authoring.js +137 -96
- package/dist/studio/client.bundle.generated.js +1 -1
- package/dist/studio/client.js +343 -167
- package/dist/studio/compile.js +5 -3
- package/dist/studio/cursor-choreography.js +22 -8
- package/dist/studio/healing.js +59 -12
- package/dist/studio/index.d.ts +1 -1
- package/dist/studio/index.js +1 -1
- package/dist/studio/interaction-observer.js +157 -33
- package/dist/studio/interactions.js +88 -57
- package/dist/studio/interactive-compile.js +42 -15
- package/dist/studio/project-json-schema.js +1 -1
- package/dist/studio/project-schema.js +102 -31
- package/dist/studio/project.js +4 -2
- package/dist/studio/recording.js +141 -38
- package/dist/studio/server.js +17 -9
- package/dist/studio/timeline.d.ts +1 -1
- package/dist/studio/timeline.js +68 -40
- package/dist/studio/treatment-schema.js +12 -3
- package/dist/studio/treatments.js +56 -17
- package/dist/studio/video-review.d.ts +1 -1
- package/dist/studio/video-review.js +18 -6
- package/dist/templates/builtin/background-loop.d.ts +6 -6
- package/dist/templates/builtin/background-loop.js +70 -35
- package/dist/templates/builtin/caption.js +40 -5
- package/dist/templates/builtin/chart.js +247 -47
- package/dist/templates/builtin/chat.js +56 -12
- package/dist/templates/builtin/compare.js +34 -17
- package/dist/templates/builtin/counter.js +11 -7
- package/dist/templates/builtin/cta.js +14 -4
- package/dist/templates/builtin/device-mockup.js +30 -6
- package/dist/templates/builtin/kinetic-text.js +69 -11
- package/dist/templates/builtin/lower-third.js +11 -7
- package/dist/templates/builtin/odometer.js +12 -6
- package/dist/templates/builtin/quote.js +5 -2
- package/dist/templates/builtin/stat.js +26 -11
- package/dist/templates/builtin/subscribe.js +79 -11
- package/dist/templates/builtin/title-card.js +5 -2
- package/dist/templates/formats.js +5 -1
- package/dist/templates/index.d.ts +7 -7
- package/dist/templates/index.js +7 -7
- package/dist/templates/registry.js +22 -13
- package/dist/templates/render.js +4 -1
- package/dist/terminal/emulator.js +9 -2
- package/dist/terminal/incremental.js +24 -6
- package/dist/terminal/index.d.ts +4 -0
- package/dist/terminal/index.js +9 -3
- package/dist/terminal/pty.js +7 -5
- package/dist/terminal/render.js +19 -4
- package/dist/terminal/theme.js +48 -6
- package/dist/tree-ops/decoration-propagation.js +9 -12
- package/dist/tree-ops/resize-embedded-images.js +11 -5
- package/dist/tree-ops/swept-transform-bounds.js +16 -11
- package/dist/tree-ops/tree-diff.js +1 -3
- package/dist/tree-ops/viewbox-culling.js +9 -7
- package/dist/utils/escapeHtml.js +1 -5
- package/dist/utils/local-server.js +7 -2
- package/dist/utils/region-feedback.js +1 -4
- package/dist/utils/transparent-background.js +4 -1
- package/dist/vertical-orientation.js +2 -5
- package/llms.txt +22 -6
- package/package.json +19 -20
- package/schemas/animate-config.schema.json +3 -0
- package/dist/capture/paged-capture-bundle-json-schema.d.ts +0 -4
- package/dist/capture/paged-capture-bundle-json-schema.js +0 -21
- package/dist/capture/paged-capture-bundle.d.ts +0 -295
- package/dist/capture/paged-capture-bundle.js +0 -283
- package/dist/capture/paged-capture-helper-json-schema.d.ts +0 -4
- package/dist/capture/paged-capture-helper-json-schema.js +0 -21
- package/dist/capture/paged-capture-helper.d.ts +0 -254
- package/dist/capture/paged-capture-helper.js +0 -907
- package/dist/capture/paged-capture.d.ts +0 -40
- package/dist/capture/paged-capture.js +0 -299
- package/dist/capture/paged-collapsed-table-cdp.d.ts +0 -67
- package/dist/capture/paged-collapsed-table-cdp.js +0 -88
- package/dist/capture/paged-collapsed-table-record.d.ts +0 -197
- package/dist/capture/paged-collapsed-table-record.js +0 -616
- package/dist/capture/paged-page-record.d.ts +0 -183
- package/dist/capture/paged-page-record.js +0 -857
- package/dist/render/paged-page-svg.d.ts +0 -48
- package/dist/render/paged-page-svg.js +0 -187
- package/schemas/paged-capture-bundle.schema.json +0 -628
- package/schemas/paged-capture-helper-bundle.schema.json +0 -384
package/FEATURES.md
CHANGED
|
@@ -5,6 +5,7 @@ Each feature has a visual regression test that compares HTML-to-PNG with SVG-to-
|
|
|
5
5
|
## Rendering Features
|
|
6
6
|
|
|
7
7
|
### Text
|
|
8
|
+
|
|
8
9
|
- [x] **text-basic**: Plain text rendering (font-size, font-family, color)
|
|
9
10
|
- [x] **text-bold**: Bold/weight text
|
|
10
11
|
- [x] **text-center**: Centered text (text-align: center)
|
|
@@ -18,6 +19,7 @@ Each feature has a visual regression test that compares HTML-to-PNG with SVG-to-
|
|
|
18
19
|
- [x] **text-font-stretch-underline**: CSS `font-stretch` end to end — condensed/expanded cut selection for declared families (Blink's trait-based style matcher), the `wdth` variation axis on the macOS `system-ui` face and variable webfonts, and decoration metrics resolved on the same width-matched face as the glyphs
|
|
19
20
|
|
|
20
21
|
### Backgrounds & Colors
|
|
22
|
+
|
|
21
23
|
- [x] **bg-solid**: Solid background colors
|
|
22
24
|
- [x] **bg-transparent**: Semi-transparent backgrounds (rgba)
|
|
23
25
|
- [x] **bg-nested**: Nested backgrounds (child on top of parent)
|
|
@@ -26,12 +28,14 @@ Each feature has a visual regression test that compares HTML-to-PNG with SVG-to-
|
|
|
26
28
|
- [ ] **bg-dark-mode** (DM-455, doc 29): Dark-mode capture support — caller-chosen `colorScheme`, dark form-control palette, scheme-aware transparent-root fallback
|
|
27
29
|
|
|
28
30
|
### Borders
|
|
31
|
+
|
|
29
32
|
- [x] **border-solid**: Solid borders with color
|
|
30
33
|
- [x] **border-radius**: Rounded corners
|
|
31
34
|
- [x] **border-radius-pill**: Fully rounded (pill shape)
|
|
32
35
|
- [x] **inline-box-decoration-break**: wrapped inline backgrounds / borders paint per line-fragment (`slice` + `clone`); first/last fragment own the start/end edges in slice mode, every fragment paints a full box in clone mode
|
|
33
36
|
|
|
34
37
|
### Layout
|
|
38
|
+
|
|
35
39
|
- [x] **layout-flex-row**: Horizontal flex layout with gap
|
|
36
40
|
- [x] **layout-flex-col**: Vertical flex layout
|
|
37
41
|
- [x] **layout-flex-center**: Centered content (justify-content/align-items)
|
|
@@ -43,6 +47,7 @@ Each feature has a visual regression test that compares HTML-to-PNG with SVG-to-
|
|
|
43
47
|
- [x] **animated-projective-frame-state** (DM-2359/DM-2356/DM-2492/DM-2493, docs 186/189): `animationTimeMs` exactly pauses document timelines before all capture prepasses and exposes the CDP quad, composed 3D state, residual, and selected atomic raster owner without a fitted 2D approximation. Same-frame used-preserve facts select Blink's direct-parent rendering-context root; a strict 25-family/four-profile native three-OS DPR-1/2 gate rejects larger crops, warnings, restoration drift, incomplete evidence, and nine ownership mutations.
|
|
44
48
|
|
|
45
49
|
### Components
|
|
50
|
+
|
|
46
51
|
- [x] **comp-button**: Button with background, border, padding, text
|
|
47
52
|
- [x] **comp-badge**: Small badge with colored background and text
|
|
48
53
|
- [x] **comp-card**: Card with background, border, radius, content
|
|
@@ -54,17 +59,21 @@ Each feature has a visual regression test that compares HTML-to-PNG with SVG-to-
|
|
|
54
59
|
- [ ] **comp-nav**: Navigation bar with logo and links
|
|
55
60
|
|
|
56
61
|
### SVG
|
|
62
|
+
|
|
57
63
|
- [x] **svg-inline**: Inline SVG elements are cloned as self-contained vectors; CSS-only clip/mask effects preserve native fill-box, stroke-box, and view-box resolution (DM-2328)
|
|
58
64
|
- [x] **clip-path-url-reference-ownership** (DM-2362, docs 39/130): bare same-document/external URL clips preserve HTML border/object-bounding-box and SVG forced-fill ownership; Blink-invalid URL-plus-box declarations stay inactive, including stale captured trees and nested frames.
|
|
59
65
|
|
|
60
66
|
### Masks
|
|
67
|
+
|
|
61
68
|
- [x] **mask-contain-cover-arbitrary-position** (DM-2379, doc 20): URL masks capture Chromium-decoded intrinsic ratios and emit concrete Blink-fitted rectangles for percentage, length, and calc positions; zoom/DPR, physical writing axes, multilayer, and slice/clone fragment controls reject the retired SVG Min/Mid/Max buckets.
|
|
62
69
|
|
|
63
70
|
### Text effects
|
|
71
|
+
|
|
64
72
|
- [x] **text-bg-clip-gradient** (DM-462): `background-clip: text` + `-webkit-text-fill-color: transparent` — gradient fills the glyph shapes via SVG `<mask>` over the bg-color rect
|
|
65
73
|
- [x] **pseudo-filter-functions** (DM-2367, doc 38): generated empty/text/image paint supports blur, drop-shadow, brightness, contrast, grayscale, hue-rotate, invert, opacity, saturate, sepia, and ordered lists through Chromium's native SVG CSS-filter pipeline; identity/none, transform, clip, stack slot, zoom, DPR, and filter-stripped mutation controls are browser-gated.
|
|
66
74
|
|
|
67
75
|
### Replaced elements (rasterized as static snapshot — DM-457)
|
|
76
|
+
|
|
68
77
|
- [x] **replaced-canvas-shape**: `<canvas>` with drawn shapes — bitmap survives via `page.screenshot`
|
|
69
78
|
- [x] **replaced-video-poster**: `<video poster=…>` paused — poster image captured
|
|
70
79
|
- [x] **replaced-canvas-overlay**: `<canvas>` under a positioned `<div z-index:10>` overlay — overlay does NOT bleed into the canvas snapshot
|
|
@@ -79,11 +88,13 @@ Each feature has a visual regression test that compares HTML-to-PNG with SVG-to-
|
|
|
79
88
|
- [x] **snapshot-isolation-pseudo-overlay** (DM-458, `tests/snapshot-isolation.tsx`): canvas covered by a sibling's `::after` pseudo overlay. Inspection-style — decodes the captured snapshot's PNG data URI and asserts no overlay-color pixels leaked through. Catches regressions in the hide-everything-else stylesheet that a comparison-style fixture wouldn't.
|
|
80
89
|
|
|
81
90
|
### Showcase Integration Tests
|
|
91
|
+
|
|
82
92
|
- [x] **showcase-typography**: Full-page layout with headings, badges, code blocks, inline code, status text
|
|
83
93
|
- [x] **showcase-cards**: Card list with badges, buttons, metadata
|
|
84
94
|
- [x] **showcase-forms**: Publish form with labels, inputs, pre block, button
|
|
85
95
|
|
|
86
96
|
### Composed Parity Integration Tests
|
|
97
|
+
|
|
87
98
|
- [x] **composed-multilingual-flex-grid**: Platform fallback and bidi isolation inside flex/grid messaging UI, with neutral-wrapper and node-split relations
|
|
88
99
|
- [x] **composed-responsive-fragmented-controls**: Native controls inside responsive multicol layout with equivalent grid syntax
|
|
89
100
|
- [x] **composed-gradient-mask-clip-stacking**: Gradients, masks, and clips under explicit stacking ownership and reversed DOM order
|
|
@@ -97,6 +108,7 @@ Each feature has a visual regression test that compares HTML-to-PNG with SVG-to-
|
|
|
97
108
|
Tests are defined in `tests/features.ts` (26 feature tests) and `tests/showcase.ts` (3 integration tests). Both use the shared runner in `tests/runner.ts`.
|
|
98
109
|
|
|
99
110
|
For each test case:
|
|
111
|
+
|
|
100
112
|
1. Define an HTML snippet (feature tests) or full-page HTML (showcase tests) exercising the feature
|
|
101
113
|
2. Render the HTML in Playwright (Chromium) and capture as PNG ("expected")
|
|
102
114
|
3. Capture the DOM via `captureElementTree` on the Playwright page
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
**Domotion turns real HTML/CSS into one self-contained, animated SVG** — an accurate reproduction of the rendered page, with optional animation and simulated interaction built in. It embeds the exact captured glyph data and positioning, so playback never falls back to a viewer's system fonts; the output scales crisply at any size and embeds anywhere with a plain `<img>`, no external assets.
|
|
16
16
|
|
|
17
|
-
Beyond raw capture it ships a **template library** that turns a few flags into a polished animated SVG
|
|
17
|
+
Beyond raw capture it ships **Domotion Studio**, a local visual workspace for authoring and reviewing versioned projects; a **template library** that turns a few flags into a polished animated SVG; **terminal-session capture** (a recording → an animated terminal); **scroll capture** (a long page replayed as one self-contained scrolling SVG); multi-frame **animation** with parameterized and custom transitions, overlays, and simulated interaction; **scene storyboarding** (sequence distinct scenes end-to-end); **brand kits** and **social-format presets** (reel / square / portrait / landscape); **device-chrome** framing; **nested compositing** (animated layers inside animated layers); one-command **SVG → MP4/WebM**; and fidelity **review** tools.
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
20
|
<img src="examples/output/hero-product-demo.svg" alt="An analytics dashboard assembling itself inside a browser window — KPI cards rise in, a bar chart grows with its peak highlighted, a search query types itself, and a nav item is clicked — all in one self-contained animated SVG" width="760">
|
|
@@ -65,6 +65,8 @@ predictable. For a zero-install trial, run
|
|
|
65
65
|
[animation guide](https://brianwestphal.github.io/domotion/usage/animate/).
|
|
66
66
|
- **Generate a polished asset from flags:** use `domotion template`; browse the
|
|
67
67
|
[template guide](https://brianwestphal.github.io/domotion/usage/templates/).
|
|
68
|
+
- **Author and review a versioned project visually:** use `domotion-studio`; see
|
|
69
|
+
the [Studio guide](https://brianwestphal.github.io/domotion/usage/studio/).
|
|
68
70
|
|
|
69
71
|
The [Quick start](https://brianwestphal.github.io/domotion/start/quickstart/)
|
|
70
72
|
walks through the first capture and animation end to end.
|
|
@@ -97,6 +99,12 @@ never become idle.
|
|
|
97
99
|
|
|
98
100
|
Same-origin `<iframe>` content is recursed into the capture as native SVG rather than flattened to a screenshot; opt into cross-origin frames you trust with `--cross-origin-frames "<hosts>"`.
|
|
99
101
|
|
|
102
|
+
Text defaults to self-contained, pixel-faithful embedded fonts. For inline SVG,
|
|
103
|
+
`--real-text` adds a paintless authored-text layer for search, selection, copy,
|
|
104
|
+
and assistive technology. `--text-mode system-font` instead paints authored
|
|
105
|
+
`<text>` using the viewer's installed fonts; it can be smaller, but it is not
|
|
106
|
+
pixel-faithful and should be used only when those fonts are guaranteed.
|
|
107
|
+
|
|
100
108
|
For a multi-frame animated SVG, write a JSON config and run:
|
|
101
109
|
|
|
102
110
|
```bash
|
|
@@ -126,6 +134,24 @@ domotion template kinetic-text --text "Ship it" --variant pop --by char -o title
|
|
|
126
134
|
|
|
127
135
|
Built-ins (14): **lower-third** (broadcast banner) · **kinetic-text** (animated typography) · **chart** (column / bar / line / pie / donut) · **chat** (message thread) · **subscribe** (follow pop-up) · **background-loop** (seamless looping background) · **device-mockup** (wrap a page in a phone / browser / window bezel) · and a **creative-template pack** of full-bleed text/number cards: **title-card**, **quote**, **caption**, **cta**, **counter**, **stat**, **compare**. Every template adapts to a `--format` social preset (reel / square / portrait / landscape) and a `--brand` kit (palette / type / logo). Third-party templates are npm packages named `domotion-template-<name>`.
|
|
128
136
|
|
|
137
|
+
### Domotion Studio — visual project authoring
|
|
138
|
+
|
|
139
|
+
Domotion Studio is a local, browser-based workspace for building and reviewing
|
|
140
|
+
versioned Domotion projects. It provides story and scene authoring, a detailed
|
|
141
|
+
multitrack timeline, embedded playback and review annotations, real-interaction
|
|
142
|
+
import, and generation/review hooks while keeping project files as portable,
|
|
143
|
+
validated JSON inside an explicitly scoped workspace.
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Open a workspace, or pass an existing Studio project JSON file.
|
|
147
|
+
domotion-studio --workspace ./demo-project
|
|
148
|
+
domotion-studio ./demo-project/product-tour.json
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Studio opens locally by default. In automation, pass `--no-open` and use the
|
|
152
|
+
printed loopback URL. Run `domotion-studio --help` or read the
|
|
153
|
+
[Studio guide](https://brianwestphal.github.io/domotion/usage/studio/).
|
|
154
|
+
|
|
129
155
|
### Terminal sessions
|
|
130
156
|
|
|
131
157
|
Turn a recorded terminal session into a self-contained animated SVG — real text, real color, native SVG (no raster frames). Record with [asciinema](https://asciinema.org), then convert:
|
|
@@ -186,7 +212,7 @@ writes the final `actual.svg`, one shared HAR, and an `expected.png` plus
|
|
|
186
212
|
`captured-tree.json` for each composed frame. Use `--debug-dir <path>` to choose
|
|
187
213
|
the bundle location.
|
|
188
214
|
|
|
189
|
-
For an
|
|
215
|
+
For an _animated_ SVG, the package also ships `svg-scrubber` — a local video-style bench to play / pause / scrub / mark an in-out range, export the current frame as PNG, export the range as MP4, or trim it to a new self-contained animated SVG. Add `--review` to file a focused issue against a moment in the timeline: it writes an importable `.ticket` (frame time, range, and drawn regions) the same way `svg-review` builds a report for a still.
|
|
190
216
|
|
|
191
217
|
### Scripting API
|
|
192
218
|
|
|
@@ -238,7 +264,7 @@ npm run demos:examples # run the bundled example demo scripts
|
|
|
238
264
|
- `FEATURES.md` — per-feature support checklist with links to test fixtures.
|
|
239
265
|
- `docs/` — requirements docs covering rendering fidelity, supported CSS features, and known caveats.
|
|
240
266
|
- [`llms.txt`](llms.txt) — a concise, self-contained guide for **AI agents using Domotion as a tool** (Claude, Cursor, etc.): the CLIs, config schema, template library, API, gotchas, and a required rendered-pixel/video review gate before an SVG is handed back. Point your agent at it.
|
|
241
|
-
- `CLAUDE.md` — guidance for AI assistants working
|
|
267
|
+
- `CLAUDE.md` — guidance for AI assistants working _on this repo's_ source (different audience from `llms.txt`).
|
|
242
268
|
|
|
243
269
|
## License
|
|
244
270
|
|
|
@@ -24,10 +24,10 @@ Chrome's layout, `registerWebfont` for Domotion's outlines).
|
|
|
24
24
|
|
|
25
25
|
## Provenance
|
|
26
26
|
|
|
27
|
-
| File
|
|
28
|
-
|
|
29
|
-
| `DomotionFixtureMono-Regular.ttf`
|
|
30
|
-
| `DomotionFixtureSerif-Regular.ttf` | [IBM Plex Serif](https://github.com/IBM/plex) Regular
|
|
27
|
+
| File | Upstream | License |
|
|
28
|
+
| ---------------------------------- | -------------------------------------------------------------------- | ------------------------------------------ |
|
|
29
|
+
| `DomotionFixtureMono-Regular.ttf` | [JetBrains Mono](https://github.com/JetBrains/JetBrainsMono) Regular | SIL OFL 1.1 — `LICENSE-jetbrains-mono.txt` |
|
|
30
|
+
| `DomotionFixtureSerif-Regular.ttf` | [IBM Plex Serif](https://github.com/IBM/plex) Regular | SIL OFL 1.1 — `LICENSE-ibm-plex-serif.txt` |
|
|
31
31
|
|
|
32
32
|
Both are unmodified apart from being subsetted to the characters the fixtures
|
|
33
33
|
paint. The OFL permits redistribution of modified copies under a reserved-name
|
|
@@ -90,7 +90,13 @@ export function caretShapeRect(inp) {
|
|
|
90
90
|
case "block":
|
|
91
91
|
return { x: leftOf(cellW), y: boxTop, width: cellW, height: boxHeight, opacity: BLOCK_CARET_ALPHA };
|
|
92
92
|
case "underscore":
|
|
93
|
-
return {
|
|
93
|
+
return {
|
|
94
|
+
x: leftOf(cellW),
|
|
95
|
+
y: inp.baselineY,
|
|
96
|
+
width: cellW,
|
|
97
|
+
height: underscoreCaretThicknessPx(inp.fontSize),
|
|
98
|
+
opacity: 1,
|
|
99
|
+
};
|
|
94
100
|
case "bar":
|
|
95
101
|
default: {
|
|
96
102
|
const barW = inp.barWidthPx ?? DEFAULT_CARET_WIDTH_PX;
|
|
@@ -148,9 +154,7 @@ export function firstLineBaseline(m) {
|
|
|
148
154
|
const descentPx = fontBox > 0 ? Math.round(m.fontDescentPx) : m.fontSize * 0.25;
|
|
149
155
|
const textBoxHeight = ascentPx + descentPx;
|
|
150
156
|
const lineH = m.lineHeightPx || fontBox || m.fontSize * 1.2;
|
|
151
|
-
const lineTop = m.centerInContentBox
|
|
152
|
-
? m.contentTop + Math.max(0, (m.contentHeight - lineH) / 2)
|
|
153
|
-
: m.contentTop;
|
|
157
|
+
const lineTop = m.centerInContentBox ? m.contentTop + Math.max(0, (m.contentHeight - lineH) / 2) : m.contentTop;
|
|
154
158
|
const boxTop = lineTop + Math.floor((lineH - textBoxHeight) / 2);
|
|
155
159
|
return { baselineY: boxTop + ascentPx, ascentPx, descentPx };
|
|
156
160
|
}
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* (docs/100, Primitive 1), which owns the glyphs and can interleave layers.
|
|
40
40
|
*/
|
|
41
41
|
import { caretShapeRect, BLOCK_CARET_ALPHA, DEFAULT_CARET_WIDTH_PX } from "./caret-metrics.js";
|
|
42
|
-
import { resolveCaretPoint, resolveRangeRects, findAddressedElement } from "./text-address.js";
|
|
42
|
+
import { resolveCaretPoint, resolveRangeRects, findAddressedElement, } from "./text-address.js";
|
|
43
43
|
import { renderTextAsPath } from "../render/text-to-path.js";
|
|
44
44
|
import { withRenderTextMode } from "../render/font-resolution.js";
|
|
45
45
|
/** The standard caret blink period (ms) — the terminal cursor's 1.06 s cycle. */
|
|
@@ -169,7 +169,13 @@ function resolveCoveredGlyph(roots, target, charOffset) {
|
|
|
169
169
|
for (const run of runs) {
|
|
170
170
|
for (const ch of run.text) {
|
|
171
171
|
if (remaining === 0) {
|
|
172
|
-
return {
|
|
172
|
+
return {
|
|
173
|
+
char: ch,
|
|
174
|
+
fontFamily: run.fontFamily,
|
|
175
|
+
fontWeight: run.fontWeight,
|
|
176
|
+
fontStyle: run.fontStyle,
|
|
177
|
+
fontSize: run.fontSize,
|
|
178
|
+
};
|
|
173
179
|
}
|
|
174
180
|
remaining--;
|
|
175
181
|
}
|
|
@@ -330,8 +336,12 @@ function invertedCaretMarkup(track, uid, kf, totalDurationMs, totalSec) {
|
|
|
330
336
|
// The glyph is repainted at the CELL's left edge, which is the caret x
|
|
331
337
|
// for an LTR insertion point and `x − cellWidth` for an RTL one.
|
|
332
338
|
renderTextAsPath(cg.char, g.x, g.y, {
|
|
333
|
-
fontSize: cg.fontSize,
|
|
334
|
-
|
|
339
|
+
fontSize: cg.fontSize,
|
|
340
|
+
fontFamily: cg.fontFamily,
|
|
341
|
+
fontWeight: cg.fontWeight,
|
|
342
|
+
fill: glyphInk,
|
|
343
|
+
fontStyle: cg.fontStyle,
|
|
344
|
+
ascentOverride: wp.point.ascentPx,
|
|
335
345
|
}));
|
|
336
346
|
glyphMarkup = gm;
|
|
337
347
|
}
|
|
@@ -410,7 +420,7 @@ function selectionMarkup(sel, si, uid, kf, totalDurationMs, totalSec) {
|
|
|
410
420
|
// Sweep anchor: the rect's leading edge in reading order. `edges` are the
|
|
411
421
|
// successive trailing edges, so the swept extent is their distance from it.
|
|
412
422
|
const rtl = rect.rtl === true;
|
|
413
|
-
const anchor = vertical ? rect.y :
|
|
423
|
+
const anchor = vertical ? rect.y : rtl ? rect.x + rect.width : rect.x;
|
|
414
424
|
const sweptTo = (edge) => (rtl && !vertical ? anchor - edge : edge - anchor);
|
|
415
425
|
const stops = [`0%{${prop}:0.01px}`];
|
|
416
426
|
if (startMs > 0)
|
|
@@ -448,9 +458,7 @@ function selectionMarkup(sel, si, uid, kf, totalDurationMs, totalSec) {
|
|
|
448
458
|
const place = rtl && !vertical
|
|
449
459
|
? `x="0" y="${num(rect.y)}" transform="translate(${num(anchor)},0) scale(-1,1)"`
|
|
450
460
|
: `x="${num(rect.x)}" y="${num(rect.y)}"`;
|
|
451
|
-
const size = vertical
|
|
452
|
-
? `width="${num(rect.width)}" height="0.01"`
|
|
453
|
-
: `width="0.01" height="${num(rect.height)}"`;
|
|
461
|
+
const size = vertical ? `width="${num(rect.width)}" height="0.01"` : `width="0.01" height="${num(rect.height)}"`;
|
|
454
462
|
parts.push(` <rect class="tt-sel" ${place} ${size} fill="${sel.color}" style="animation:${name} ${totalSec.toFixed(2)}s ${timing} infinite"/>`);
|
|
455
463
|
sweptBefore += rect.edges.length;
|
|
456
464
|
}
|
|
@@ -55,10 +55,13 @@ function innerOf(svg) {
|
|
|
55
55
|
function resolveClipOriginPx(origin, base, size) {
|
|
56
56
|
switch (origin) {
|
|
57
57
|
case "left":
|
|
58
|
-
case "top":
|
|
58
|
+
case "top":
|
|
59
|
+
return base;
|
|
59
60
|
case "right":
|
|
60
|
-
case "bottom":
|
|
61
|
-
|
|
61
|
+
case "bottom":
|
|
62
|
+
return base + size;
|
|
63
|
+
case "center":
|
|
64
|
+
return base + size / 2;
|
|
62
65
|
}
|
|
63
66
|
const pct = /^([-\d.]+)%$/.exec(origin);
|
|
64
67
|
if (pct != null)
|
|
@@ -71,11 +74,16 @@ function resolveClipOriginPx(origin, base, size) {
|
|
|
71
74
|
/** Render a non-clip layer-animation endpoint to its CSS value (transform string / opacity). */
|
|
72
75
|
function renderAnimValue(property, v) {
|
|
73
76
|
switch (property) {
|
|
74
|
-
case "scale":
|
|
75
|
-
|
|
76
|
-
case "
|
|
77
|
-
|
|
78
|
-
case "
|
|
77
|
+
case "scale":
|
|
78
|
+
return `scale(${v})`;
|
|
79
|
+
case "translateX":
|
|
80
|
+
return `translateX(${typeof v === "number" ? `${v}px` : v})`;
|
|
81
|
+
case "translateY":
|
|
82
|
+
return `translateY(${typeof v === "number" ? `${v}px` : v})`;
|
|
83
|
+
case "opacity":
|
|
84
|
+
return `${v}`;
|
|
85
|
+
case "transform":
|
|
86
|
+
return `${v}`;
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
89
|
/**
|
|
@@ -100,9 +108,9 @@ export function composeAnimatedLayers(layers, opts) {
|
|
|
100
108
|
});
|
|
101
109
|
// Resolve each layer's geometry + intrinsic size first so we can size the master.
|
|
102
110
|
const resolved = layers.map((layer, i) => {
|
|
103
|
-
const intrinsic =
|
|
111
|
+
const intrinsic = layer.contentWidth != null && layer.contentHeight != null
|
|
104
112
|
? { w: layer.contentWidth, h: layer.contentHeight }
|
|
105
|
-
: parseSvgIntrinsicSize(layer.svg) ?? { w: 0, h: 0 };
|
|
113
|
+
: (parseSvgIntrinsicSize(layer.svg) ?? { w: 0, h: 0 });
|
|
106
114
|
const w = layer.width ?? intrinsic.w;
|
|
107
115
|
const h = layer.height ?? intrinsic.h;
|
|
108
116
|
const x = layer.x ?? 0;
|
|
@@ -140,8 +148,11 @@ export function composeAnimatedLayers(layers, opts) {
|
|
|
140
148
|
// 2. …then re-anchor its internal timeline to its own start within the master.
|
|
141
149
|
if (r.period > 0) {
|
|
142
150
|
content = offsetEmbeddedAnimatedSvgTimeline(content, {
|
|
143
|
-
periodMs: r.period,
|
|
144
|
-
|
|
151
|
+
periodMs: r.period,
|
|
152
|
+
startMs: r.start,
|
|
153
|
+
masterMs: master,
|
|
154
|
+
mode: r.mode,
|
|
155
|
+
windowMs: r.windowMs,
|
|
145
156
|
});
|
|
146
157
|
}
|
|
147
158
|
// 3. Strip the outer wrapper and re-nest at the layer's box (a nested <svg>
|
|
@@ -117,6 +117,17 @@ export interface CompressedRunOptions {
|
|
|
117
117
|
/** Paint a root background rect first (e.g. the captured
|
|
118
118
|
* `styles.rootBgComputed`). Omit for a transparent run. */
|
|
119
119
|
background?: string;
|
|
120
|
+
/**
|
|
121
|
+
* DM-6SQXGF: append the paintless real-text layer (doc 260) to the run. A
|
|
122
|
+
* compressed run flattens N states into ONE emission (shared content once,
|
|
123
|
+
* step-end birth/shift/recolor tracks) — there are no per-frame visibility
|
|
124
|
+
* groups to gate per-state text, and the glyph births use `opacity` (still
|
|
125
|
+
* find-in-page-visible). So the run emits ONE layer from the FINAL state's
|
|
126
|
+
* tree (the completed content — a stable deduplicated story flow), and the
|
|
127
|
+
* visible chrome+glyph runs are `aria-hidden` while it owns semantics.
|
|
128
|
+
* Intermediate typing prefixes are deliberately not exposed. Default off.
|
|
129
|
+
*/
|
|
130
|
+
realText?: boolean;
|
|
120
131
|
/** Emit the auto-caret track derived from the per-state edit points
|
|
121
132
|
* (docs/101 machinery). Default false — the config surface decides
|
|
122
133
|
* defaults later. */
|
|
@@ -105,9 +105,10 @@
|
|
|
105
105
|
import { elementTreeToSvgInner } from "../render/element-tree-to-svg.js";
|
|
106
106
|
import { paintOrderHitSequence } from "../render/paint-order.js";
|
|
107
107
|
import { clearEmbeddedFonts, clearGlyphDefs, getEmbeddedFontFaceCss } from "../render/index.js";
|
|
108
|
+
import { renderRealTextLayer, withRealTextLayerVisualSemantics } from "../render/real-text-layer.js";
|
|
108
109
|
import { IDENTITY_TEXT_AFFINE, prepareAffineTextPaint, textAffineEquals } from "../render/text-affine.js";
|
|
109
110
|
import { alignLineGlyphs } from "./glyph-align.js";
|
|
110
|
-
import { textTrackMarkup, CARET_BLINK_MS, DEFAULT_SELECTION_COLOR } from "./caret-track.js";
|
|
111
|
+
import { textTrackMarkup, CARET_BLINK_MS, DEFAULT_SELECTION_COLOR, } from "./caret-track.js";
|
|
111
112
|
import { DEFAULT_CARET_WIDTH_PX } from "./caret-metrics.js";
|
|
112
113
|
import { resolveRangeRects } from "./text-address.js";
|
|
113
114
|
// ── Small helpers ───────────────────────────────────────────────────────────
|
|
@@ -156,8 +157,10 @@ function paintsBox(el) {
|
|
|
156
157
|
if (s.backgroundImage != null && s.backgroundImage !== "none" && s.backgroundImage !== "")
|
|
157
158
|
return true;
|
|
158
159
|
const side = (w, st) => num(w) > 0 && st !== "none" && st != null;
|
|
159
|
-
if (side(s.borderTopWidth, s.borderTopStyle) ||
|
|
160
|
-
|
|
160
|
+
if (side(s.borderTopWidth, s.borderTopStyle) ||
|
|
161
|
+
side(s.borderRightWidth, s.borderRightStyle) ||
|
|
162
|
+
side(s.borderBottomWidth, s.borderBottomStyle) ||
|
|
163
|
+
side(s.borderLeftWidth, s.borderLeftStyle))
|
|
161
164
|
return true;
|
|
162
165
|
if (s.boxShadow != null && s.boxShadow !== "none" && s.boxShadow !== "")
|
|
163
166
|
return true;
|
|
@@ -165,7 +168,23 @@ function paintsBox(el) {
|
|
|
165
168
|
return true;
|
|
166
169
|
if (el.elementRaster != null)
|
|
167
170
|
return true;
|
|
168
|
-
const REPLACED = new Set([
|
|
171
|
+
const REPLACED = new Set([
|
|
172
|
+
"img",
|
|
173
|
+
"canvas",
|
|
174
|
+
"video",
|
|
175
|
+
"svg",
|
|
176
|
+
"iframe",
|
|
177
|
+
"embed",
|
|
178
|
+
"object",
|
|
179
|
+
"picture",
|
|
180
|
+
"input",
|
|
181
|
+
"textarea",
|
|
182
|
+
"select",
|
|
183
|
+
"button",
|
|
184
|
+
"progress",
|
|
185
|
+
"meter",
|
|
186
|
+
"hr",
|
|
187
|
+
]);
|
|
169
188
|
return REPLACED.has(el.tag);
|
|
170
189
|
}
|
|
171
190
|
function maxBorderRadius(el) {
|
|
@@ -212,8 +231,7 @@ function segmentEligible(seg) {
|
|
|
212
231
|
// they proved inert: no PNG was materialized and the ordinary glyph remains
|
|
213
232
|
// the sole paint. Only a real overlay or the zero-area ::first-letter
|
|
214
233
|
// suppression marker changes rendering and must stay in the chrome layer.
|
|
215
|
-
if (seg.rasterGlyphs?.some((glyph) => glyph.dataUri != null
|
|
216
|
-
|| (glyph.suppressGlyph === true && glyph.rect.width === 0 && glyph.rect.height === 0)))
|
|
234
|
+
if (seg.rasterGlyphs?.some((glyph) => glyph.dataUri != null || (glyph.suppressGlyph === true && glyph.rect.width === 0 && glyph.rect.height === 0)))
|
|
217
235
|
return false;
|
|
218
236
|
if (seg.dottedCircleMarks != null && seg.dottedCircleMarks.length > 0)
|
|
219
237
|
return false;
|
|
@@ -237,8 +255,7 @@ function textPlaneForCompression(el) {
|
|
|
237
255
|
// The compressor emits absolute glyph coordinates and its own translation
|
|
238
256
|
// tracks. A non-identity residual would need matrix-aware track composition;
|
|
239
257
|
// keep that text in the byte-safe chrome flipbook until such support exists.
|
|
240
|
-
if (prepared.residualMatrix != null
|
|
241
|
-
&& !textAffineEquals(prepared.residualMatrix, IDENTITY_TEXT_AFFINE))
|
|
258
|
+
if (prepared.residualMatrix != null && !textAffineEquals(prepared.residualMatrix, IDENTITY_TEXT_AFFINE))
|
|
242
259
|
return null;
|
|
243
260
|
return prepared.element;
|
|
244
261
|
}
|
|
@@ -269,9 +286,10 @@ function elementTextEligible(el, textEl, ctx) {
|
|
|
269
286
|
return false;
|
|
270
287
|
for (const clip of ctx.clips) {
|
|
271
288
|
const inset = clip.inset;
|
|
272
|
-
if (seg.x < clip.x + inset - 0.5 ||
|
|
273
|
-
|
|
274
|
-
|
|
289
|
+
if (seg.x < clip.x + inset - 0.5 ||
|
|
290
|
+
seg.y < clip.y + inset - 0.5 ||
|
|
291
|
+
seg.x + seg.width > clip.x + clip.w - inset + 0.5 ||
|
|
292
|
+
seg.y + seg.height > clip.y + clip.h - inset + 0.5)
|
|
275
293
|
return false;
|
|
276
294
|
}
|
|
277
295
|
}
|
|
@@ -499,16 +517,26 @@ function extractState(tree, regionRootIds, demoted) {
|
|
|
499
517
|
for (const ch of seg.text) {
|
|
500
518
|
const x = xs[u];
|
|
501
519
|
const nextU = u + ch.length;
|
|
502
|
-
let advance = nextU < seg.text.length ? xs[nextU] - x :
|
|
520
|
+
let advance = nextU < seg.text.length ? xs[nextU] - x : seg.width > 0 ? seg.x + seg.width - x : 0;
|
|
503
521
|
if (!(advance > 0))
|
|
504
522
|
advance = fontSize * 0.6;
|
|
505
523
|
const isWs = WS_RE.test(ch);
|
|
506
524
|
glyphs.push({
|
|
507
|
-
ch,
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
525
|
+
ch,
|
|
526
|
+
x,
|
|
527
|
+
advance,
|
|
528
|
+
lineKey: lineKeyOf(ctx.region, seg.y),
|
|
529
|
+
region: ctx.region,
|
|
530
|
+
segY: seg.y,
|
|
531
|
+
segHeight: seg.height,
|
|
532
|
+
fill: isWs ? "" : fillResolved,
|
|
533
|
+
isWs,
|
|
534
|
+
styleKey,
|
|
535
|
+
fontSize,
|
|
536
|
+
ascent,
|
|
537
|
+
descent,
|
|
538
|
+
srcEl: textEl,
|
|
539
|
+
srcSeg: seg,
|
|
512
540
|
});
|
|
513
541
|
u = nextU;
|
|
514
542
|
}
|
|
@@ -590,10 +618,20 @@ function lineSignature(items) {
|
|
|
590
618
|
function pairBuckets(live, next) {
|
|
591
619
|
const result = [];
|
|
592
620
|
const liveArr = [...live.entries()]
|
|
593
|
-
.map(([k, glyphs]) => ({
|
|
621
|
+
.map(([k, glyphs]) => ({
|
|
622
|
+
k,
|
|
623
|
+
glyphs,
|
|
624
|
+
y: liveBucketY(glyphs),
|
|
625
|
+
sig: lineSignature(glyphs.map((t) => ({ ch: t.rec.ch, styleKey: t.rec.styleKey, x: t.xs[t.xs.length - 1] }))),
|
|
626
|
+
}))
|
|
594
627
|
.sort((a, b) => a.y - b.y);
|
|
595
628
|
const nextArr = [...next.entries()]
|
|
596
|
-
.map(([k, recs]) => ({
|
|
629
|
+
.map(([k, recs]) => ({
|
|
630
|
+
k,
|
|
631
|
+
recs,
|
|
632
|
+
y: recs[0].segY,
|
|
633
|
+
sig: lineSignature(recs.map((g) => ({ ch: g.ch, styleKey: g.styleKey, x: g.x }))),
|
|
634
|
+
}))
|
|
597
635
|
.sort((a, b) => a.y - b.y);
|
|
598
636
|
const usedLive = new Set();
|
|
599
637
|
const usedNext = new Set();
|
|
@@ -734,7 +772,12 @@ function threadGlyphs(perState, stateCount) {
|
|
|
734
772
|
const prevList = pairing.prev.slice().sort((a, b) => a.xs[a.xs.length - 1] - b.xs[b.xs.length - 1]);
|
|
735
773
|
const nextList = pairing.next;
|
|
736
774
|
totalNext += nextList.length;
|
|
737
|
-
const prevSeq = prevList.map((t) => ({
|
|
775
|
+
const prevSeq = prevList.map((t) => ({
|
|
776
|
+
ch: t.rec.ch,
|
|
777
|
+
x: t.xs[t.xs.length - 1],
|
|
778
|
+
fill: t.fills[t.fills.length - 1],
|
|
779
|
+
styleKey: t.rec.styleKey,
|
|
780
|
+
}));
|
|
738
781
|
const nextSeq = nextList.map((g) => ({ ch: g.ch, x: g.x, fill: g.fill, styleKey: g.styleKey }));
|
|
739
782
|
const align = alignLineGlyphs(prevSeq, nextSeq);
|
|
740
783
|
const survivors = [];
|
|
@@ -803,7 +846,15 @@ function threadGlyphs(perState, stateCount) {
|
|
|
803
846
|
for (const g of placeBirths)
|
|
804
847
|
if (g.x > last.x)
|
|
805
848
|
last = g;
|
|
806
|
-
edits.push({
|
|
849
|
+
edits.push({
|
|
850
|
+
state: s,
|
|
851
|
+
x: last.x + last.advance,
|
|
852
|
+
lineTop: last.segY,
|
|
853
|
+
ascent: last.ascent,
|
|
854
|
+
descent: last.descent,
|
|
855
|
+
fontSize: last.fontSize,
|
|
856
|
+
cellWidth: last.advance,
|
|
857
|
+
});
|
|
807
858
|
}
|
|
808
859
|
else {
|
|
809
860
|
let first = placeDeaths[0];
|
|
@@ -811,7 +862,15 @@ function threadGlyphs(perState, stateCount) {
|
|
|
811
862
|
if (t.xs[t.xs.length - 1] < first.xs[first.xs.length - 1])
|
|
812
863
|
first = t;
|
|
813
864
|
const r = first.rec;
|
|
814
|
-
edits.push({
|
|
865
|
+
edits.push({
|
|
866
|
+
state: s,
|
|
867
|
+
x: first.xs[first.xs.length - 1],
|
|
868
|
+
lineTop: r.segY,
|
|
869
|
+
ascent: r.ascent,
|
|
870
|
+
descent: r.descent,
|
|
871
|
+
fontSize: r.fontSize,
|
|
872
|
+
cellWidth: r.advance,
|
|
873
|
+
});
|
|
815
874
|
}
|
|
816
875
|
}
|
|
817
876
|
}
|
|
@@ -863,7 +922,18 @@ function buildGlyphGroups(threaded, uid) {
|
|
|
863
922
|
const dxTl = t.xs.map((x) => round2(x - t.xs[0])).join(",");
|
|
864
923
|
const dyTl = t.ys.map((y) => round2(y - t.ys[0])).join(",");
|
|
865
924
|
const fillTl = t.fills.join("~");
|
|
866
|
-
const key = [
|
|
925
|
+
const key = [
|
|
926
|
+
t.birth,
|
|
927
|
+
t.death,
|
|
928
|
+
t.rec.lineKey,
|
|
929
|
+
t.rec.styleKey,
|
|
930
|
+
round2(t.rec.segY),
|
|
931
|
+
round2(t.rec.segHeight),
|
|
932
|
+
round2(t.rec.ascent),
|
|
933
|
+
dxTl,
|
|
934
|
+
dyTl,
|
|
935
|
+
fillTl,
|
|
936
|
+
].join("§");
|
|
867
937
|
const arr = byKey.get(key);
|
|
868
938
|
if (arr != null)
|
|
869
939
|
arr.push(t);
|
|
@@ -947,7 +1017,7 @@ function groupElement(group) {
|
|
|
947
1017
|
// output is byte-identical.
|
|
948
1018
|
if (el.textTop != null) {
|
|
949
1019
|
const firstSegY = first.rec.srcEl.textSegments?.[0]?.y ?? first.ys[0];
|
|
950
|
-
el.textTop +=
|
|
1020
|
+
el.textTop += first.ys[0] - firstSegY + dyTotal;
|
|
951
1021
|
}
|
|
952
1022
|
return el;
|
|
953
1023
|
}
|
|
@@ -955,7 +1025,15 @@ const shallowKeyCache = new WeakMap();
|
|
|
955
1025
|
function shallowKeyOf(el) {
|
|
956
1026
|
let k = shallowKeyCache.get(el);
|
|
957
1027
|
if (k == null) {
|
|
958
|
-
k = fnv(JSON.stringify({
|
|
1028
|
+
k = fnv(JSON.stringify({
|
|
1029
|
+
...el,
|
|
1030
|
+
children: undefined,
|
|
1031
|
+
animId: undefined,
|
|
1032
|
+
animatedProperties: undefined,
|
|
1033
|
+
magicKey: undefined,
|
|
1034
|
+
cullClass: undefined,
|
|
1035
|
+
displayNone: undefined,
|
|
1036
|
+
}));
|
|
959
1037
|
shallowKeyCache.set(el, k);
|
|
960
1038
|
}
|
|
961
1039
|
return k;
|
|
@@ -1348,8 +1426,22 @@ export function composeCompressedRun(states, opts) {
|
|
|
1348
1426
|
// Render the two layers through the production pipeline. Chrome first
|
|
1349
1427
|
// (below), glyphs above — the occlusion guard demoted anything a later
|
|
1350
1428
|
// box-painting element overlaps, so this flattening is paint-safe.
|
|
1351
|
-
|
|
1352
|
-
|
|
1429
|
+
// DM-6SQXGF: when the run owns a real-text layer, render the two VISIBLE
|
|
1430
|
+
// layers inside `withRealTextLayerVisualSemantics` so their glyph runs emit
|
|
1431
|
+
// `aria-hidden="true"` (the paintless layer owns the readable flow). The two
|
|
1432
|
+
// renders pass `includeRealTextLayer=false` — the run emits ONE layer from the
|
|
1433
|
+
// final state below, not one per layer.
|
|
1434
|
+
let chromeInner = "";
|
|
1435
|
+
let glyphInner = "";
|
|
1436
|
+
const renderVisibleLayers = () => {
|
|
1437
|
+
chromeInner = elementTreeToSvgInner(chromeTree, width, height, `${uid}c-`, true, 2, false);
|
|
1438
|
+
glyphInner = glyphEls.length > 0 ? elementTreeToSvgInner(glyphEls, width, height, `${uid}g-`, true, 2, false) : "";
|
|
1439
|
+
};
|
|
1440
|
+
if (opts.realText === true)
|
|
1441
|
+
withRealTextLayerVisualSemantics(renderVisibleLayers);
|
|
1442
|
+
else
|
|
1443
|
+
renderVisibleLayers();
|
|
1444
|
+
const realTextLayer = opts.realText === true ? renderRealTextLayer(structuredClone(states[stateCount - 1].tree)) : "";
|
|
1353
1445
|
// Behind-glyph selection: docs/101 selection rects resolved against each
|
|
1354
1446
|
// selection's appear-state captured tree, emitted into the chrome↔glyph gap
|
|
1355
1447
|
// (below `glyphInner`), so the highlight paints BEHIND the glyph ink — true
|
|
@@ -1377,8 +1469,13 @@ export function composeCompressedRun(states, opts) {
|
|
|
1377
1469
|
: {}),
|
|
1378
1470
|
};
|
|
1379
1471
|
const track = {
|
|
1380
|
-
shape: "bar",
|
|
1381
|
-
|
|
1472
|
+
shape: "bar",
|
|
1473
|
+
color: "#111111",
|
|
1474
|
+
barWidthPx: DEFAULT_CARET_WIDTH_PX,
|
|
1475
|
+
blinkMs: CARET_BLINK_MS,
|
|
1476
|
+
waypoints: [],
|
|
1477
|
+
hides: [],
|
|
1478
|
+
selections: [sel],
|
|
1382
1479
|
};
|
|
1383
1480
|
parts.push(textTrackMarkup(track, totalMs, 100 + i));
|
|
1384
1481
|
}
|
|
@@ -1398,8 +1495,12 @@ export function composeCompressedRun(states, opts) {
|
|
|
1398
1495
|
selections: [],
|
|
1399
1496
|
};
|
|
1400
1497
|
const pointOf = (e) => ({
|
|
1401
|
-
x: e.x,
|
|
1402
|
-
|
|
1498
|
+
x: e.x,
|
|
1499
|
+
baselineY: e.lineTop + e.ascent,
|
|
1500
|
+
ascentPx: e.ascent,
|
|
1501
|
+
descentPx: e.descent,
|
|
1502
|
+
fontSize: e.fontSize,
|
|
1503
|
+
cellWidthPx: e.cellWidth,
|
|
1403
1504
|
});
|
|
1404
1505
|
// Parked at the first upcoming edit point from t=0 (reads as "about to
|
|
1405
1506
|
// type here"), then stepping to each edit's after-position at its boundary.
|
|
@@ -1413,8 +1514,8 @@ export function composeCompressedRun(states, opts) {
|
|
|
1413
1514
|
const trackCss = css.css();
|
|
1414
1515
|
const styleCss = `${fontFaceCss !== "" ? fontFaceCss + "\n" : ""}${trackCss}`;
|
|
1415
1516
|
const bgRect = opts.background != null ? `<rect width="${width}" height="${height}" fill="${esc(opts.background)}"/>` : "";
|
|
1416
|
-
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`
|
|
1417
|
-
|
|
1517
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">` +
|
|
1518
|
+
`<style>${styleCss}</style>${bgRect}${chromeInner}${selectionMarkup}${glyphInner}${caretMarkup}${realTextLayer}</svg>`;
|
|
1418
1519
|
const pairingStats = {
|
|
1419
1520
|
states: stateCount,
|
|
1420
1521
|
glyphs: plan.thread.totalNext,
|