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.
Files changed (119) hide show
  1. package/FEATURES.md +102 -0
  2. package/LICENSE +21 -0
  3. package/README.md +66 -0
  4. package/dist/animator.d.ts +158 -0
  5. package/dist/animator.js +424 -0
  6. package/dist/animator.test.d.ts +5 -0
  7. package/dist/animator.test.js +169 -0
  8. package/dist/border-radius.test.d.ts +1 -0
  9. package/dist/border-radius.test.js +148 -0
  10. package/dist/capture.d.ts +193 -0
  11. package/dist/capture.js +786 -0
  12. package/dist/chrome.d.ts +45 -0
  13. package/dist/chrome.js +107 -0
  14. package/dist/cli.d.ts +16 -0
  15. package/dist/cli.js +512 -0
  16. package/dist/client/dom.d.ts +10 -0
  17. package/dist/client/dom.js +17 -0
  18. package/dist/conic-raster.d.ts +58 -0
  19. package/dist/conic-raster.js +292 -0
  20. package/dist/conic-raster.test.d.ts +1 -0
  21. package/dist/conic-raster.test.js +187 -0
  22. package/dist/coretext-extractor.test.d.ts +1 -0
  23. package/dist/coretext-extractor.test.js +94 -0
  24. package/dist/coretext-helper.d.ts +60 -0
  25. package/dist/coretext-helper.js +205 -0
  26. package/dist/cross-origin-font-face.test.d.ts +1 -0
  27. package/dist/cross-origin-font-face.test.js +107 -0
  28. package/dist/cursor-overlay.d.ts +123 -0
  29. package/dist/cursor-overlay.js +207 -0
  30. package/dist/cursor-overlay.test.d.ts +1 -0
  31. package/dist/cursor-overlay.test.js +88 -0
  32. package/dist/dark-mode-capture.test.d.ts +1 -0
  33. package/dist/dark-mode-capture.test.js +158 -0
  34. package/dist/dark-mode-form-controls.test.d.ts +1 -0
  35. package/dist/dark-mode-form-controls.test.js +218 -0
  36. package/dist/dom-to-svg.d.ts +1016 -0
  37. package/dist/dom-to-svg.js +7717 -0
  38. package/dist/embed-remote-images.test.d.ts +1 -0
  39. package/dist/embed-remote-images.test.js +424 -0
  40. package/dist/form-controls.d.ts +70 -0
  41. package/dist/form-controls.js +1151 -0
  42. package/dist/frame-merge.d.ts +95 -0
  43. package/dist/frame-merge.js +374 -0
  44. package/dist/frame-merge.test.d.ts +6 -0
  45. package/dist/frame-merge.test.js +144 -0
  46. package/dist/gradients.d.ts +184 -0
  47. package/dist/gradients.js +937 -0
  48. package/dist/gradients.test.d.ts +1 -0
  49. package/dist/gradients.test.js +150 -0
  50. package/dist/index.d.ts +12 -0
  51. package/dist/index.js +7 -0
  52. package/dist/jsx-runtime.d.ts +27 -0
  53. package/dist/jsx-runtime.js +96 -0
  54. package/dist/jsx-runtime.test.d.ts +1 -0
  55. package/dist/jsx-runtime.test.js +41 -0
  56. package/dist/kerfjs-imports.test.d.ts +1 -0
  57. package/dist/kerfjs-imports.test.js +36 -0
  58. package/dist/mask.test.d.ts +1 -0
  59. package/dist/mask.test.js +206 -0
  60. package/dist/optimize.d.ts +12 -0
  61. package/dist/optimize.js +32 -0
  62. package/dist/preserve-aspect-ratio.test.d.ts +1 -0
  63. package/dist/preserve-aspect-ratio.test.js +38 -0
  64. package/dist/resize-embedded-images.d.ts +33 -0
  65. package/dist/resize-embedded-images.js +164 -0
  66. package/dist/resize-embedded-images.test.d.ts +9 -0
  67. package/dist/resize-embedded-images.test.js +255 -0
  68. package/dist/stacking-context.test.d.ts +1 -0
  69. package/dist/stacking-context.test.js +927 -0
  70. package/dist/text-renderer.d.ts +42 -0
  71. package/dist/text-renderer.js +608 -0
  72. package/dist/text-renderer.test.d.ts +1 -0
  73. package/dist/text-renderer.test.js +150 -0
  74. package/dist/text-to-path.d.ts +265 -0
  75. package/dist/text-to-path.js +1800 -0
  76. package/dist/text-to-path.test.d.ts +1 -0
  77. package/dist/text-to-path.test.js +570 -0
  78. package/dist/utils/escapeHtml.d.ts +2 -0
  79. package/dist/utils/escapeHtml.js +15 -0
  80. package/dist/webfont-unicode-range.test.d.ts +1 -0
  81. package/dist/webfont-unicode-range.test.js +174 -0
  82. package/package.json +55 -0
  83. package/src/animator.test.ts +179 -0
  84. package/src/animator.ts +660 -0
  85. package/src/border-radius.test.ts +160 -0
  86. package/src/capture.ts +810 -0
  87. package/src/cli.ts +582 -0
  88. package/src/conic-raster.test.ts +213 -0
  89. package/src/conic-raster.ts +309 -0
  90. package/src/coretext-extractor.test.ts +130 -0
  91. package/src/coretext-helper.ts +256 -0
  92. package/src/cross-origin-font-face.test.ts +119 -0
  93. package/src/cursor-overlay.test.ts +95 -0
  94. package/src/cursor-overlay.ts +297 -0
  95. package/src/dark-mode-capture.test.ts +177 -0
  96. package/src/dark-mode-form-controls.test.ts +228 -0
  97. package/src/dom-to-svg.ts +8376 -0
  98. package/src/embed-remote-images.test.ts +461 -0
  99. package/src/form-controls.ts +1174 -0
  100. package/src/frame-merge.test.ts +157 -0
  101. package/src/frame-merge.ts +447 -0
  102. package/src/globals.d.ts +2 -0
  103. package/src/gradients.test.ts +175 -0
  104. package/src/gradients.ts +955 -0
  105. package/src/index.ts +12 -0
  106. package/src/kerf-jsx-augmentation.d.ts +36 -0
  107. package/src/kerfjs-imports.test.tsx +45 -0
  108. package/src/mask.test.ts +274 -0
  109. package/src/optimize.ts +34 -0
  110. package/src/preserve-aspect-ratio.test.ts +49 -0
  111. package/src/resize-embedded-images.test.ts +292 -0
  112. package/src/resize-embedded-images.ts +180 -0
  113. package/src/stacking-context.test.ts +967 -0
  114. package/src/text-renderer.test.ts +162 -0
  115. package/src/text-renderer.ts +623 -0
  116. package/src/text-to-path.test.ts +639 -0
  117. package/src/text-to-path.ts +1810 -0
  118. package/src/utils/escapeHtml.ts +16 -0
  119. package/src/webfont-unicode-range.test.ts +207 -0
@@ -0,0 +1,1016 @@
1
+ /**
2
+ * DOM-to-SVG Converter
3
+ *
4
+ * Uses Playwright to inspect DOM elements and recreate them as native SVG.
5
+ */
6
+ import type { Page } from "@playwright/test";
7
+ /**
8
+ * Convert a `file://` (or absolute filesystem) URL to a base64 data URI so the
9
+ * generated SVG is self-contained and renders in tools that block local-file
10
+ * references for security (most online SVG viewers, embeds, etc.). Pass-through
11
+ * for http(s):// and existing data: URLs. Returns the original string on any
12
+ * error so failures degrade gracefully.
13
+ */
14
+ /**
15
+ * @internal — exposed for `resize-embedded-images.ts` (DM-539). The resize
16
+ * pre-pass reads source bytes from this cache (populated by `embedRemoteImages`)
17
+ * and writes per-(URL, size) resized variants into `_resizedDataUriCache`.
18
+ */
19
+ export declare const _dataUriCache: Map<string, string>;
20
+ /**
21
+ * @internal — DM-539. Per-source-URL map of sizeKey → resized data URI. The
22
+ * sizeKey is `${ceil(targetW * hiDPI)}x${ceil(targetH * hiDPI)}`. Populated by
23
+ * `resizeEmbeddedImages` (only when `embedRemoteImagesResize: true`); read by
24
+ * `embedResizedDataUri` (DM-540) when the renderer emits an `<image href>`.
25
+ * Empty at module load — first capture with the resize flag fills it.
26
+ */
27
+ export declare const _resizedDataUriCache: Map<string, Map<string, string>>;
28
+ /**
29
+ * @internal — DM-549. Per-conic-gradient-layer-text map of `${tileW}x${tileH}` →
30
+ * data URI containing rasterized PNG bytes. Populated by `rasterizeConicGradients`
31
+ * (the conic raster pre-pass) and read by `buildConicGradientDef` (DM-550) when
32
+ * the renderer emits a `<pattern><image>` for a conic background layer. Empty
33
+ * at module load — first capture with conic content fills it.
34
+ */
35
+ export declare const _conicTileCache: Map<string, Map<string, string>>;
36
+ /**
37
+ * DM-540 — renderer-side lookup for the image-resize-on-embed pipeline.
38
+ * Returns the resized PNG data URI for `(url, ceil(w * hiDPI), ceil(h * hiDPI))`
39
+ * when the resize pre-pass populated `_resizedDataUriCache` for that key;
40
+ * otherwise falls back to `embedAsDataUri(url)` so the renderer behaves
41
+ * identically to today when resize is disabled (no entries in the resized
42
+ * cache → source-resolution data URI returned).
43
+ */
44
+ export declare function embedResizedDataUri(url: string, consumerW: number, consumerH: number, hiDPIFactor?: number): string;
45
+ export interface EmbedRemoteImagesOptions {
46
+ /**
47
+ * DM-527: append per-URL fetch failures here as `CaptureWarning` entries.
48
+ * If omitted, warnings push to the module-global `lastCaptureWarnings`
49
+ * (visible via `getLastCaptureWarnings` / `logCaptureWarnings`). Concurrent
50
+ * captures should pass an explicit array to avoid racing on the global.
51
+ */
52
+ warnings?: CaptureWarning[];
53
+ /**
54
+ * DM-528: per-URL fetch timeout in ms. A stalled CDN host (slow DNS, slow
55
+ * first-byte, unresponsive origin) would otherwise hang the capture
56
+ * indefinitely — the parallel `Promise.all` won't resolve until every
57
+ * fetch settles. With this timeout the slowest fetch caps total pre-pass
58
+ * time at ~`timeoutMs * (retries + 1) + retryBackoffMs * retries` (since
59
+ * fetches run in parallel). Timed-out fetches produce a `remote-image`
60
+ * warning and the URL stays as-is in the SVG. Default 10000.
61
+ */
62
+ timeoutMs?: number;
63
+ /**
64
+ * DM-529: number of retry attempts for transient failures (5xx response,
65
+ * network error, or timeout). 4xx responses are not retried — those are
66
+ * deterministic and a retry would just consume time. The originating
67
+ * fetch + retries together can take up to
68
+ * `(retries + 1) * timeoutMs + retries * retryBackoffMs` per URL, so keep
69
+ * this small. Default 1.
70
+ */
71
+ retries?: number;
72
+ /**
73
+ * DM-529: delay (ms) between attempts when retrying a transient failure.
74
+ * Default 500.
75
+ */
76
+ retryBackoffMs?: number;
77
+ }
78
+ /**
79
+ * DM-512: fetch every http(s) image URL referenced by the captured tree and
80
+ * stash the resolved bytes as a data: URI in the renderer's data-URI cache.
81
+ * Subsequent calls to `embedAsDataUri` for those URLs return the cached data
82
+ * URI instead of passing the URL through verbatim — yielding a self-contained
83
+ * SVG that loads correctly in image viewers (Preview, Finder QuickLook, etc.)
84
+ * that don't fetch remote resources from local files.
85
+ *
86
+ * Walks the captured tree once collecting URLs from: `imageSrc` (for `<img>`),
87
+ * `pseudoImages[].url` (for `::before`/`::after` content: url(...)), and CSS
88
+ * `url(...)` tokens inside `styles.backgroundImage` / `.maskImage` /
89
+ * `.borderImageSource` / `.listStyleImage`. Dedupes per call.
90
+ *
91
+ * Per-URL fetch failures (network error, non-2xx, missing Content-Type) leave
92
+ * the URL in the tree as-is, so the SVG still references it. DM-527: each
93
+ * failure is surfaced as a `CaptureWarning` (feature: `remote-image`) carrying
94
+ * the URL and the HTTP status / error class, so callers can trace which images
95
+ * didn't inline. The warning is appended to `options.warnings` if supplied,
96
+ * otherwise to `getLastCaptureWarnings()`.
97
+ */
98
+ export declare function embedRemoteImages(tree: CapturedElement[], options?: EmbedRemoteImagesOptions): Promise<void>;
99
+ export interface TextSegment {
100
+ text: string;
101
+ x: number;
102
+ y: number;
103
+ width: number;
104
+ height: number;
105
+ /**
106
+ * Per-character viewport-absolute x offsets (one entry per visible char in
107
+ * `text`). When provided, renderers anchor each glyph at xOffsets[i] instead
108
+ * of summing fontkit advances — closing the per-char drift that accumulates
109
+ * over long paragraphs. May be undefined when capture couldn't produce it
110
+ * (e.g. input/textarea values).
111
+ */
112
+ xOffsets?: number[];
113
+ /** Override color for this segment (e.g. ::before / ::after pseudos whose
114
+ * CSS `color` differs from the element's). Renderer uses parent fill when
115
+ * undefined. */
116
+ color?: string;
117
+ /** Override font-size in CSS pixels (e.g. abbr[title]::after { font-size: 0.9em }).
118
+ * Undefined means inherit from the element. */
119
+ fontSize?: number;
120
+ /** Override font-weight (e.g. li[data-badge]::before { font-weight: bold }).
121
+ * Undefined means inherit from the element. */
122
+ fontWeight?: string;
123
+ /** Override font-style (e.g. ::first-line { font-style: italic }). */
124
+ fontStyle?: string;
125
+ /** Override font-family (e.g. `.icon-angle-right { font-family: 'sdicon' }`
126
+ * with `::before { content: '\\e87a' }` — the icon glyph routes through
127
+ * the icon webfont, not the parent element's body font). DM-513. */
128
+ fontFamily?: string;
129
+ /** Override font-variant (e.g. ::first-line { font-variant: small-caps }).
130
+ * When 'small-caps', renderer applies the OpenType `smcp` feature so
131
+ * lowercase letters shape as small uppercase forms — Chrome paints them
132
+ * the same way and the captured xOffsets already encode the small-caps
133
+ * advance widths, so the only thing the renderer needs to change is the
134
+ * glyph variant per character. (DM-294) */
135
+ fontVariant?: string;
136
+ /**
137
+ * Override fontBoundingBoxAscent (px) when the segment uses a font/size
138
+ * different from the element (::before / ::after with custom font-size).
139
+ * Renderer falls back to the element's fontAscent when this is undefined.
140
+ */
141
+ fontAscent?: number;
142
+ /**
143
+ * Viewport-relative rectangle (CSS pixels) to screenshot when the WHOLE
144
+ * segment is raster-worthy — used for ::before / ::after pseudos whose
145
+ * entire text is a color-bitmap run. Populated by CAPTURE_SCRIPT;
146
+ * captureElementTree fills in rasterDataUri. Renderer emits one <image>
147
+ * covering rasterRect and skips the text pipeline entirely. See SK-1058.
148
+ */
149
+ rasterRect?: {
150
+ x: number;
151
+ y: number;
152
+ width: number;
153
+ height: number;
154
+ };
155
+ /** data:image/png;base64,… populated by Node-side raster. Renderer checks this. */
156
+ rasterDataUri?: string;
157
+ /**
158
+ * Per-character raster overlays for emoji / color-bitmap codepoints mixed
159
+ * into an otherwise path-rendered text run (SK-1090). Each entry pins the
160
+ * char's exact viewport-relative rect; rasterizeBitmapGlyphs fills in
161
+ * dataUri, and the renderer stamps an <image> on top of the path text at
162
+ * that rect. Path emission for the underlying glyph is left intact — the
163
+ * <image> overlays wherever needed, and for pure-emoji codepoints the path
164
+ * pipeline emits nothing anyway (zero-contour glyph from fontkit).
165
+ */
166
+ rasterGlyphs?: Array<{
167
+ charIndex: number;
168
+ rect: {
169
+ x: number;
170
+ y: number;
171
+ width: number;
172
+ height: number;
173
+ };
174
+ dataUri?: string;
175
+ /** When true, the underlying text-path emit for this charIndex must be
176
+ * suppressed: the raster image is the ONLY paint for this codepoint
177
+ * (e.g. ::first-letter drop caps where the styled big-letter raster
178
+ * would otherwise sit on top of the body-size path glyph). DM-439. */
179
+ suppressGlyph?: boolean;
180
+ }>;
181
+ /**
182
+ * Pseudo-element box paint (DM-497): when this segment came from a
183
+ * `::before` / `::after` whose computed style sets a non-trivial
184
+ * `background-color` or `border-radius`, the renderer emits a `<rect>` of
185
+ * these dimensions BEHIND the segment's text glyphs to honor the pseudo
186
+ * box paint (badge / pill / chip pattern). Coordinates are viewport-relative
187
+ * and already include padding + border insets so the rect surrounds the
188
+ * captured text segment with the author-specified inflation.
189
+ */
190
+ pseudoBox?: {
191
+ x: number;
192
+ y: number;
193
+ width: number;
194
+ height: number;
195
+ /** Pre-resolved srgb-form CSS color (or "transparent"). Renderer parses
196
+ * via parseColor and emits as the rect fill. */
197
+ backgroundColor?: string;
198
+ /** Resolved px border-radius (CSS shorthand — single value, four-corner
199
+ * symmetric). */
200
+ borderRadius?: number;
201
+ /** Uniform border (single side-width + color). Per-side variation isn't
202
+ * supported in this pass; mixed-style borders fall through. */
203
+ borderWidth?: number;
204
+ borderColor?: string;
205
+ };
206
+ }
207
+ export interface CapturedElement {
208
+ tag: string;
209
+ text: string;
210
+ x: number;
211
+ y: number;
212
+ width: number;
213
+ height: number;
214
+ /**
215
+ * If the source DOM had `data-domotion-anim="<id>"` on this element, the id
216
+ * is captured here. The renderer surfaces it as `class="anim-<id>"` on the
217
+ * rendered group so an intra-frame animation (DM-209) can target it via CSS
218
+ * keyframes. The animator (or CLI wrapper) is responsible for setting the
219
+ * data attribute on the live DOM before capture.
220
+ */
221
+ animId?: string;
222
+ styles: {
223
+ backgroundColor: string;
224
+ borderColor: string;
225
+ borderWidth: string;
226
+ borderRadius: string;
227
+ /**
228
+ * Resolved per-corner border-radius (top-left, top-right, bottom-right,
229
+ * bottom-left). Chrome returns longhand corner values in pixels even when
230
+ * the author used percentages, so capturing these lets the renderer pick
231
+ * a circle radius for `border-radius: 50%` instead of treating the "50"
232
+ * from the shorthand as a 50-px corner. See SK-1093.
233
+ */
234
+ borderTopLeftRadius?: string;
235
+ borderTopRightRadius?: string;
236
+ borderBottomRightRadius?: string;
237
+ borderBottomLeftRadius?: string;
238
+ /** Per-side border widths as strings (e.g. "6px"). All set together so we can decide uniform-vs-split in the renderer. */
239
+ borderTopWidth: string;
240
+ borderRightWidth: string;
241
+ borderBottomWidth: string;
242
+ borderLeftWidth: string;
243
+ borderTopStyle: string;
244
+ borderRightStyle: string;
245
+ borderBottomStyle: string;
246
+ borderLeftStyle: string;
247
+ borderTopColor: string;
248
+ borderRightColor: string;
249
+ borderBottomColor: string;
250
+ borderLeftColor: string;
251
+ /**
252
+ * For table cells: `"collapse"` means the parent table sets
253
+ * `border-collapse: collapse` so adjacent cells share a single painted
254
+ * border instead of stacking two adjacent ones. The renderer paints
255
+ * cell borders centered on the cell edge (no half-inset) in this mode
256
+ * so that two cells'\'' shared edges overlap exactly into one line
257
+ * instead of doubling.
258
+ */
259
+ borderCollapse: string;
260
+ overflowX: string;
261
+ overflowY: string;
262
+ scrollbarGutter: string;
263
+ /** el.scrollHeight / scrollWidth vs client* — used to decide whether to paint a scrollbar. */
264
+ scrollWidth: number;
265
+ scrollHeight: number;
266
+ clientWidth: number;
267
+ clientHeight: number;
268
+ scrollTop: number;
269
+ scrollLeft: number;
270
+ objectFit: string;
271
+ objectPosition: string;
272
+ filter: string;
273
+ backdropFilter: string;
274
+ /**
275
+ * DM-476 frosted-glass fallback: when the element has a non-trivial
276
+ * `backdrop-filter` AND a near-transparent `background-color` (alpha
277
+ * ≤ 0.1), CAPTURE_SCRIPT reads `document.body`'s computed background
278
+ * colour and stores it here as a normalised `rgb(...)` / `rgba(...)`
279
+ * string so the renderer can paint a synthesised opaque fill where
280
+ * Chromium would have painted blurred underlying pixels. Undefined
281
+ * when the element doesn't trigger the frosted condition. See
282
+ * `docs/19-frosted-backdrop-fallback.md`.
283
+ */
284
+ frostedBgFallback?: string;
285
+ mixBlendMode: string;
286
+ clipPath: string;
287
+ mask: string;
288
+ maskImage: string;
289
+ maskMode: string;
290
+ maskSize: string;
291
+ maskPosition: string;
292
+ maskRepeat: string;
293
+ maskComposite: string;
294
+ listStyleType: string;
295
+ listStyleImage: string;
296
+ listStylePosition: string;
297
+ /** CSS `display` (e.g. `block`, `list-item`, `flex`). Used by the
298
+ * renderer to detect display:list-item on non-li tags. DM-451. */
299
+ display?: string;
300
+ backgroundImage: string;
301
+ backgroundSize: string;
302
+ backgroundPosition: string;
303
+ backgroundRepeat: string;
304
+ backgroundClip: string;
305
+ backgroundOrigin: string;
306
+ backgroundAttachment: string;
307
+ /**
308
+ * CSS `-webkit-text-fill-color`. When `backgroundClip` is `text` the
309
+ * common pattern is `webkit-text-fill-color: transparent` so the
310
+ * background-image (gradient / url) shows through the glyph shapes —
311
+ * a "gradient headline" effect (Stripe / Resend / Linear hero copy).
312
+ * Captured separately because `color` may still report a normal value
313
+ * when the rendered text is actually transparent. DM-462.
314
+ */
315
+ webkitTextFillColor?: string;
316
+ paddingTop: string;
317
+ paddingRight: string;
318
+ paddingBottom: string;
319
+ paddingLeft: string;
320
+ /** Intrinsic dimensions per background-image layer (same order as splitTopLevelCommas). */
321
+ backgroundIntrinsic?: Array<{
322
+ w: number;
323
+ h: number;
324
+ } | null>;
325
+ borderImageSource: string;
326
+ borderImageSlice: string;
327
+ borderImageWidth: string;
328
+ borderImageOutset: string;
329
+ borderImageRepeat: string;
330
+ /** Intrinsic pixel dimensions of a border-image url() source, resolved at capture time. */
331
+ borderImageIntrinsicWidth?: number;
332
+ borderImageIntrinsicHeight?: number;
333
+ zIndex: string;
334
+ position: string;
335
+ float: string;
336
+ /** CSS `order` (flex/grid items). Per CSS Flexbox 1 §5.4.1 and CSS Grid 1
337
+ * §17, paint order is order-modified document order — items are painted
338
+ * in ascending `order` value, ties broken by source (DOM) order. The
339
+ * property has no effect on non-flex/non-grid items but Chrome still
340
+ * reports it on every computed style, so we capture unconditionally. */
341
+ order: string;
342
+ /** CSS `flex-direction` on a flex container (`row` / `row-reverse` /
343
+ * `column` / `column-reverse`). Empirically Chrome paints children of a
344
+ * `*-reverse` flex container in REVERSE of their order-modified document
345
+ * order, so the rightmost (or bottommost) item paints LAST in every
346
+ * flex-direction — matching what users expect from a visually-reordered
347
+ * layout. Captured on the parent so the child sorter can read it. */
348
+ flexDirection: string;
349
+ /** For <td>/<th> with empty-cells: hide — suppress bg + border. */
350
+ emptyCellsHidden?: boolean;
351
+ /** Form-control state captured so we can synthesize native chrome. */
352
+ inputType?: string;
353
+ /** CSS `appearance` / `-webkit-appearance` longhand. 'none' means the
354
+ * author has opted out of UA chrome — the renderer should let the host
355
+ * rect (background / border / border-radius) show through and only
356
+ * overlay the :checked indicator. DM-285. */
357
+ inputAppearance?: string;
358
+ checked?: boolean;
359
+ indeterminate?: boolean;
360
+ disabled?: boolean;
361
+ progressValue?: number;
362
+ progressMax?: number;
363
+ /** Custom CSS on ::-webkit-progress-bar (the track) — when set, overrides the default track. */
364
+ progressBarBg?: string;
365
+ progressBarBgImage?: string;
366
+ progressBarRadius?: string;
367
+ /** Custom CSS on ::-webkit-progress-value (the fill) — when set, overrides the synthesized fill. */
368
+ progressValueBg?: string;
369
+ progressValueBgImage?: string;
370
+ progressValueRadius?: string;
371
+ meterValue?: number;
372
+ meterMin?: number;
373
+ meterMax?: number;
374
+ meterLow?: number;
375
+ meterHigh?: number;
376
+ meterOptimum?: number;
377
+ /** Custom CSS on ::-webkit-meter-bar / ::-webkit-meter-*-value pseudo-elements. */
378
+ meterBarBg?: string;
379
+ meterBarBgImage?: string;
380
+ meterBarRadius?: string;
381
+ meterOptimumBg?: string;
382
+ meterOptimumBgImage?: string;
383
+ meterSuboptimumBg?: string;
384
+ meterSuboptimumBgImage?: string;
385
+ meterEvenLessGoodBg?: string;
386
+ meterEvenLessGoodBgImage?: string;
387
+ detailsOpen?: boolean;
388
+ /** True when the `<summary>`'s `::marker` was hidden by author CSS
389
+ * (e.g. `::marker { color: transparent }` or
390
+ * `::-webkit-details-marker { color: transparent }`), so the renderer
391
+ * should NOT paint its own UA disclosure triangle on top of the
392
+ * author's custom marker. DM-448. */
393
+ summaryMarkerSuppressed?: boolean;
394
+ selectChevron?: boolean;
395
+ /** Text of the currently-selected option, rendered inside the `<select>`
396
+ * content rect for closed dropdowns (DM-246). For listbox-mode selects
397
+ * (`size > 1` or `multiple`) this is undefined and per-option rendering
398
+ * flows through the listbox path instead. */
399
+ selectDisplayText?: string;
400
+ /** Captured option list for listbox-mode `<select>` (size > 1 or
401
+ * multiple). Each entry is one row the renderer paints inside the
402
+ * select's content rect. DM-282. */
403
+ selectListboxOptions?: Array<{
404
+ text: string;
405
+ selected: boolean;
406
+ disabled: boolean;
407
+ /** Optgroup label row (italic + bold; not user-selectable). */
408
+ isOptgroupLabel?: boolean;
409
+ /** Indented child of an optgroup. */
410
+ isOptgroupChild?: boolean;
411
+ }>;
412
+ accentColor?: string;
413
+ caretColor?: string;
414
+ /** For <input type=range/color/date/time/...> — the current value. */
415
+ inputValue?: string;
416
+ inputMin?: string;
417
+ inputMax?: string;
418
+ inputStep?: string;
419
+ /** True for inputs with the `multiple` attribute (file / email / select). DM-271. */
420
+ inputMultiple?: boolean;
421
+ /** For <input type=file> — name of the first selected file, or empty. */
422
+ inputFileName?: string;
423
+ /** Custom-styled <input type=range> ::-webkit-slider-runnable-track / ::-webkit-slider-thumb (SK-1131). */
424
+ rangeTrackBg?: string;
425
+ rangeTrackHeight?: string;
426
+ rangeTrackRadius?: string;
427
+ /** Resolved gradient text for the slider track (SK-1224). Captured when the rule sets background or background-image to a *-gradient(). */
428
+ rangeTrackBgImage?: string;
429
+ rangeThumbBg?: string;
430
+ rangeThumbWidth?: string;
431
+ rangeThumbHeight?: string;
432
+ rangeThumbRadius?: string;
433
+ /** Resolved gradient text for the slider thumb (SK-1224). */
434
+ rangeThumbBgImage?: string;
435
+ /** ::-webkit-slider-runnable-track border shorthand (DM-273). */
436
+ rangeTrackBorder?: string;
437
+ /** ::-webkit-slider-thumb border shorthand (DM-273). */
438
+ rangeThumbBorder?: string;
439
+ /** ::-webkit-slider-thumb box-shadow (DM-319). Used for the donut-ring
440
+ * effect: `box-shadow: 0 0 0 Npx <color>` paints an outer ring of width
441
+ * N around the thumb. We only render the spread-only form. */
442
+ rangeThumbBoxShadow?: string;
443
+ /** ::-webkit-color-swatch pseudo styles (SK-1223). */
444
+ colorSwatchBg?: string;
445
+ colorSwatchBgImage?: string;
446
+ colorSwatchBorder?: string;
447
+ colorSwatchRadius?: string;
448
+ colorSwatchWrapperPadding?: string;
449
+ /** ::-webkit-inner-spin-button pseudo styles (SK-1223; capture only — renderer pickup is a follow-up). */
450
+ numberSpinButtonBg?: string;
451
+ numberSpinButtonBorder?: string;
452
+ numberSpinButtonRadius?: string;
453
+ /** ::-webkit-search-cancel-button pseudo styles (SK-1223; capture only — renderer pickup is a follow-up). */
454
+ searchCancelButtonBg?: string;
455
+ searchCancelButtonBorder?: string;
456
+ searchCancelButtonRadius?: string;
457
+ /** For <input type=file> — captured ::file-selector-button pseudo styles. */
458
+ fileButtonBg?: string;
459
+ fileButtonColor?: string;
460
+ fileButtonBorder?: string;
461
+ fileButtonBorderRadius?: string;
462
+ fileButtonPadding?: string;
463
+ fileButtonFontWeight?: string;
464
+ fileButtonFontSize?: string;
465
+ fileButtonFontFamily?: string;
466
+ fileButtonMarginRight?: string;
467
+ /** Canvas-measureText width of the button label at the captured font (DM-288).
468
+ * Lets the renderer position the trailing 'No file chosen' placeholder at
469
+ * exactly Chrome's painted x rather than overestimating via a per-char ratio. */
470
+ fileButtonLabelWidth?: number;
471
+ /** CSS outline (drawn outside the border-box, doesn't take layout space). */
472
+ outlineStyle?: string;
473
+ outlineWidth?: string;
474
+ outlineColor?: string;
475
+ outlineOffset?: string;
476
+ /** CSS box-shadow (raw value: outset/inset, x y blur spread color, comma-separated). */
477
+ boxShadow?: string;
478
+ /** CSS text-shadow (raw value: x y blur color, comma-separated; no inset/spread). */
479
+ textShadow?: string;
480
+ /** CSS transform (e.g. `rotate(30deg)`, `scale(1.5)`, `matrix(1,0,0,1,0,0)`). 'none' → no transform. */
481
+ transform?: string;
482
+ /** CSS will-change (DM-498): a comma-separated hint listing properties the
483
+ * author intends to animate. When the list contains a property whose
484
+ * non-initial value WOULD create a stacking context (transform, opacity,
485
+ * filter, mask, clip-path, perspective, top/right/bottom/left, position,
486
+ * z-index, isolation, mix-blend-mode), the element itself becomes a
487
+ * stacking-context root regardless of whether that property has a
488
+ * non-initial value applied. Used by `establishesStackingContext`. */
489
+ willChange?: string;
490
+ /** CSS contain (DM-498): `layout`, `paint`, `strict`, `content`, `size`,
491
+ * or combinations. `paint` / `strict` / `content` (which includes paint)
492
+ * create a stacking context per spec. */
493
+ contain?: string;
494
+ /** CSS isolation (DM-498): `isolate` creates a stacking context. */
495
+ isolation?: string;
496
+ /**
497
+ * DM-552: page-level color-scheme inferred from the capture context.
498
+ * Sourced from `matchMedia('(prefers-color-scheme: dark)').matches` at
499
+ * capture time and ONLY populated on the captured tree's root element
500
+ * (other elements omit it). Renderer reads this to decide whether to
501
+ * emit `color-scheme="dark"` on the root `<svg>`.
502
+ */
503
+ rootColorScheme?: "light" | "dark";
504
+ /**
505
+ * DM-552: `getComputedStyle(document.documentElement).backgroundColor`
506
+ * resolved by Chromium at capture time. Covers the transparent-root
507
+ * case where the page declares no background and Chromium fills its
508
+ * UA default per scheme (`#ffffff` for light, `rgb(28, 28, 28)`-ish
509
+ * for dark). ONLY populated on the captured tree's root element.
510
+ */
511
+ rootBgComputed?: string;
512
+ /** CSS transform-origin resolved to pixel pair (e.g. `60px 30px`). Defaults to '50% 50%' = bbox center. */
513
+ transformOrigin?: string;
514
+ /** CSS writing-mode (`horizontal-tb` | `vertical-rl` | `vertical-lr` | `sideways-rl` | `sideways-lr`). */
515
+ writingMode?: string;
516
+ /** CSS text-orientation (`mixed` | `upright` | `sideways`). Used in vertical writing-modes. */
517
+ textOrientation?: string;
518
+ /** CSS resize. Non-none on textareas paints the bottom-right resize handle. */
519
+ resize?: string;
520
+ /** CSS text-overflow ('clip' | 'ellipsis' | "<string>" | …). Renderer paints
521
+ * the truncation marker at the visible right edge when overflow is hidden
522
+ * and white-space prevents wrapping. (DM-373) */
523
+ textOverflow?: string;
524
+ /** Whitespace handling — needed alongside text-overflow to determine if the
525
+ * truncation marker should paint. */
526
+ whiteSpace?: string;
527
+ color: string;
528
+ fontSize: string;
529
+ fontFamily: string;
530
+ fontWeight: string;
531
+ /** 'italic' | 'oblique' | 'normal' — drives the SF Pro slnt axis in
532
+ * text-to-path so <em>, <i>, [style=font-style:italic] etc. render
533
+ * slanted instead of upright. */
534
+ fontStyle?: string;
535
+ opacity: string;
536
+ lineHeight: string;
537
+ letterSpacing: string;
538
+ fontKerning: string;
539
+ fontStretch: string;
540
+ fontVariationSettings: string;
541
+ fontFeatureSettings: string;
542
+ /** CSS font-variant-caps — 'normal' | 'small-caps' | 'all-small-caps' |
543
+ * 'petite-caps' | 'all-petite-caps' | 'unicase' | 'titling-caps'.
544
+ * Renderer applies the matching OpenType feature (smcp / c2sc / etc.).
545
+ * DM-361. */
546
+ fontVariantCaps?: string;
547
+ /** CSS direction ('ltr' / 'rtl'). Drives BiDi reordering on RTL paragraphs. */
548
+ direction?: string;
549
+ /** BCP-47 language tag inherited from `el.lang` / nearest ancestor `[lang]` /
550
+ * `<html lang>`. Routes Han fallback to the matching PingFang regional
551
+ * variant (TC / HK / MO) or Hiragino Kaku for `ja`. (DM-394) */
552
+ lang?: string;
553
+ /** `text-decoration-line` — 'underline', 'line-through', 'overline', or
554
+ * combinations. 'none' means no decoration; renderer draws an SVG line
555
+ * below / through / above the text when present. */
556
+ textDecorationLine?: string;
557
+ /** `text-decoration-color` — color for the decoration line. Falls back to
558
+ * the element's text color when undefined. */
559
+ textDecorationColor?: string;
560
+ /** `text-decoration-style` — 'solid' / 'dashed' / 'dotted' / 'double' /
561
+ * 'wavy'. Undefined or 'solid' = plain line. */
562
+ textDecorationStyle?: string;
563
+ /** `text-decoration-thickness` — explicit length (e.g. `5px`) or `auto`.
564
+ * When set to a length, overrides the auto thickness in
565
+ * `getDecorationMetrics`. DM-431. */
566
+ textDecorationThickness?: string;
567
+ /** `text-underline-offset` — extra distance below the baseline for the
568
+ * underline stroke. Adds to the auto offset. DM-431. */
569
+ textUnderlineOffset?: string;
570
+ /** `text-decoration-skip-ink` — 'auto' (default; break around descenders),
571
+ * 'none' (always solid), or 'all'. Per Chromium's
572
+ * `decoration_line_painter.cc`, only solid + double underlines honor
573
+ * skip-ink; dashed / dotted / wavy ignore it. DM-446. */
574
+ textDecorationSkipInk?: string;
575
+ };
576
+ children: CapturedElement[];
577
+ imageSrc?: string;
578
+ /** Intrinsic pixel dimensions of <img>, used for object-fit: none. */
579
+ imageIntrinsic?: {
580
+ w: number;
581
+ h: number;
582
+ };
583
+ /** True when the <img> failed to load (`complete && naturalWidth === 0`).
584
+ * Renderer paints the broken-image fallback (icon + alt text). DM-372. */
585
+ imageBroken?: boolean;
586
+ /** <img alt> attribute, painted next to the broken-image icon. DM-372. */
587
+ imageAlt?: string;
588
+ /** Intrinsic pixel dimensions of list-style-image on <li>. */
589
+ listMarkerIntrinsic?: {
590
+ w: number;
591
+ h: number;
592
+ };
593
+ /** 1-based list-item counter value used to format numeric/alpha markers. */
594
+ listItemIndex?: number;
595
+ /** Computed ::marker pseudo styles when set on an <li>. CSS lets authors
596
+ * recolor / re-weight / resize the marker independent of the list item
597
+ * text (e.g. li::marker { color: #ea580c; font-weight: bold }). See
598
+ * SK-1115. */
599
+ markerColor?: string;
600
+ markerFontWeight?: string;
601
+ markerFontSize?: string;
602
+ /** Computed `::marker { content: ... }`. When non-default ("normal" or
603
+ * empty), Chrome paints this string in place of the list-style-type
604
+ * bullet/number. Captured as the raw computed value (may include
605
+ * surrounding quotes). DM-447. */
606
+ markerContent?: string;
607
+ /** Computed `::marker { font-family }`. Defaults to inherit, but authors
608
+ * can override (e.g. `font-family: monospace` on numeric markers). */
609
+ markerFontFamily?: string;
610
+ /** ::before / ::after pseudo-element image content (content: url(...)). */
611
+ pseudoImages?: Array<{
612
+ url: string;
613
+ x: number;
614
+ y: number;
615
+ width: number;
616
+ height: number;
617
+ }>;
618
+ svgContent?: string;
619
+ /** Individual text node segments (for mixed content with interleaved child elements) */
620
+ textSegments?: TextSegment[];
621
+ /** Bounding box of all text (union of segments) */
622
+ textTop?: number;
623
+ textLeft?: number;
624
+ textHeight?: number;
625
+ textWidth?: number;
626
+ /**
627
+ * Chrome's `canvas.measureText().fontBoundingBoxAscent` for the element's
628
+ * computed font (px, integer-rounded). This is the exact distance Chrome
629
+ * paints from line-box top to baseline — reading it from the browser
630
+ * sidesteps the fontkit-vs-Chrome metric divergence (Helvetica/Arial/Times
631
+ * etc on macOS use winAscent, not hhea, while SF Pro has equal metrics; the
632
+ * "right metric per font" is fragile to derive but trivial to measure).
633
+ */
634
+ fontAscent?: number;
635
+ fontDescent?: number;
636
+ /**
637
+ * Per-char x positions for input/textarea value text (SK-1234), measured
638
+ * via a hidden DOM probe with the same font/value. Lets the renderer
639
+ * anchor each glyph at the position Chromium would paint instead of
640
+ * trusting fontkit's native advances (which can drift up to ~0.5px per
641
+ * char vs HarfBuzz). One entry per UTF-16 code unit; viewport-relative.
642
+ */
643
+ inputXOffsets?: number[];
644
+ /** Rasterized text as PNG data URI (rendered via canvas for cross-browser consistency) */
645
+ textImageUri?: string;
646
+ /** Scale factor used for text rasterization (e.g. 2 for retina) */
647
+ textImageScale?: number;
648
+ /**
649
+ * True when `text` came from an input/textarea `placeholder=…` attribute
650
+ * (value was empty). Renderer paints the text in placeholderColor instead
651
+ * of the normal text color. See SK-1097 / SK-1100.
652
+ */
653
+ isPlaceholderText?: boolean;
654
+ /** Computed ::placeholder color (normalized). Set when isPlaceholderText. */
655
+ placeholderColor?: string;
656
+ /** Computed ::placeholder font-style (e.g. italic). Set when isPlaceholderText. */
657
+ placeholderFontStyle?: string;
658
+ /** Computed ::placeholder font-weight. Set when isPlaceholderText. */
659
+ placeholderFontWeight?: string;
660
+ /**
661
+ * Viewport-relative rect of the element's internal content area (minus
662
+ * borders + padding) that rasterizeBitmapGlyphs should screenshot and
663
+ * stash on dataUri for the renderer to stamp in place of path text. Used
664
+ * for <textarea> whose soft-wrap text layout is too involved to replicate
665
+ * in the path pipeline (see SK-1108) — rely on Chrome's own render of the
666
+ * content region. Detection happens in CAPTURE_SCRIPT.
667
+ */
668
+ elementRaster?: {
669
+ x: number;
670
+ y: number;
671
+ width: number;
672
+ height: number;
673
+ dataUri?: string;
674
+ };
675
+ /**
676
+ * For <canvas> / <video> / <iframe> / <object> / <embed>: a viewport-relative
677
+ * content-box rect (border-box minus border + padding) that
678
+ * rasterizeReplacedElements should screenshot via Playwright and stash on
679
+ * dataUri. The renderer then emits an <image> at the same rect to cover what
680
+ * Chrome actually painted in that element's content area, so these element
681
+ * types stop leaving blank holes in real-world captures. The capture warning
682
+ * is still emitted because these types are out of the spirit of the path-
683
+ * based rendering contract — the snapshot is a frozen raster, not a faithful
684
+ * re-render. See docs/17-replaced-element-snapshots.md (DM-457).
685
+ */
686
+ replacedSnapshot?: {
687
+ x: number;
688
+ y: number;
689
+ width: number;
690
+ height: number;
691
+ rid: string;
692
+ dataUri?: string;
693
+ };
694
+ /**
695
+ * Image-replacement icon (DM-506): a CSS-sprite icon whose accessible label
696
+ * is hidden via `text-indent: -9999px; overflow: hidden` (or the modern
697
+ * `text-indent: <neg>; overflow: hidden; white-space: nowrap` variant). The
698
+ * element's painted box is rasterised through the `replacedSnapshot` path so
699
+ * the sprite slice is captured as Chrome painted it; capture also clears
700
+ * `text` / `textSegments` / `styles.backgroundImage` so the renderer doesn't
701
+ * double-paint the sliced sprite or leak the off-screen text into the SVG.
702
+ * `titleText` is the suppressed author text — emitted as an SVG `<title>`
703
+ * child of the rasterised `<image>` so screen readers and tooltips still
704
+ * surface the label. See `docs/23-css-sprite-icons.md`.
705
+ */
706
+ imageReplacement?: {
707
+ titleText: string;
708
+ };
709
+ /**
710
+ * <fieldset> with a top-aligned <legend>: Chrome's UA paints the fieldset's
711
+ * top border at the legend's vertical center (not at fs.y) and notches the
712
+ * border across the legend's horizontal extent. The captured x/y/width/
713
+ * height already encode the inset (y shifted down by legend.height/2,
714
+ * height reduced to match), and this field carries the legend's full
715
+ * absolute bbox so the renderer can clip the top border behind it via an
716
+ * even-odd clipPath. DM-342 / DM-343.
717
+ */
718
+ fieldsetLegendNotch?: {
719
+ x: number;
720
+ y: number;
721
+ w: number;
722
+ h: number;
723
+ };
724
+ /**
725
+ * Top-level (root only) collection of `<mask>` definitions referenced by
726
+ * fragment URLs (`mask-image: url("#id")`) anywhere in the captured tree.
727
+ * CAPTURE_SCRIPT resolves each fragment id to the corresponding inline
728
+ * `<mask>` element via `document.getElementById` and serialises its
729
+ * `outerHTML` here. The renderer copies these into the output `<defs>`
730
+ * with id rewriting so a captured `<mask id="m1">` becomes a
731
+ * domotion-prefixed mask def referenced by elements that point at `#m1`.
732
+ * Same-document only — external `.svg#fragment` refs are deferred (DM-496).
733
+ * See `docs/21-mask-fragment-references.md`.
734
+ */
735
+ maskDefs?: MaskFragmentDef[];
736
+ /**
737
+ * DM-494: Raster snapshots of elements referenced by `mask-image:
738
+ * element(#id)`. Top-level (root only) — same-document only (cross-document
739
+ * `element()` is not in scope; CSS spec doesn't define it). Each raster is
740
+ * the actual painted output of the referenced element captured via
741
+ * `page.screenshot({ clip: rect, omitBackground: true })` after a hide-
742
+ * everything-else stylesheet, encoded as `data:image/png;base64,…`.
743
+ * Renderer's `buildMaskDef` looks up the entry by `id` and emits an
744
+ * `<image>` inside the `<mask>` with `mask-type="luminance"` per Chrome's
745
+ * `mask-mode: match-source` default for element() references. See
746
+ * `docs/22-mask-element-paint-references.md`.
747
+ */
748
+ maskRasters?: MaskRasterRef[];
749
+ }
750
+ export interface MaskFragmentDef {
751
+ /** Original DOM id of the captured `<mask>` element. */
752
+ id: string;
753
+ /** Verbatim `outerHTML` of the captured `<mask>` element. */
754
+ outerHTML: string;
755
+ }
756
+ export interface MaskRasterRef {
757
+ /** DOM id referenced by `mask-image: element(#id)` — used by the renderer
758
+ * to look up the raster from the layer reference. */
759
+ id: string;
760
+ /** `data:image/png;base64,…` of the referenced element's painted box.
761
+ * Populated by `rasterizeMaskSources`; may be undefined if the screenshot
762
+ * failed (e.g. clip went off-page) — renderer falls back to no mask
763
+ * emission and warns. */
764
+ dataUri?: string;
765
+ /** Captured element rect (viewport-relative px). Used by the post-capture
766
+ * pass to drive page.screenshot's clip. Renderer doesn't consume this
767
+ * directly; the `<image>` placement comes from the consuming element's
768
+ * mask-position / mask-size. */
769
+ width: number;
770
+ height: number;
771
+ /** `data-domotion-rid` value attached to the live DOM target so the
772
+ * hide-everything-else stylesheet has a unique selector. */
773
+ rid: string;
774
+ /** Viewport-relative rect of the referenced element, used by the post-
775
+ * capture pass. */
776
+ rect: {
777
+ x: number;
778
+ y: number;
779
+ width: number;
780
+ height: number;
781
+ };
782
+ }
783
+ export interface CaptureWarning {
784
+ /** Short selector path identifying the element that tripped the warning. */
785
+ selector: string;
786
+ /** Feature name (e.g. 'transform', 'backdrop-filter', '<iframe>'). */
787
+ feature: string;
788
+ /** Short detail about what's not supported and/or a tracking ticket. */
789
+ detail: string;
790
+ }
791
+ export declare function getLastCaptureWarnings(): CaptureWarning[];
792
+ /**
793
+ * Print the last capture's warnings to stderr in a compact format. Useful in
794
+ * CLI / test scripts that want a one-line-per-warning summary.
795
+ */
796
+ export declare function logCaptureWarnings(label?: string): void;
797
+ /**
798
+ * Capture the visual tree of elements within a viewport region. Warnings about
799
+ * unsupported features encountered during capture are stored and accessible
800
+ * via getLastCaptureWarnings() / logCaptureWarnings().
801
+ */
802
+ export declare function captureElementTree(page: Page, selector: string | undefined, viewport: {
803
+ x: number;
804
+ y: number;
805
+ width: number;
806
+ height: number;
807
+ }): Promise<CapturedElement[]>;
808
+ /**
809
+ * Same capture as `captureElementTree` but returns the warnings inline so
810
+ * callers running multiple captures concurrently don't race on the
811
+ * `lastCaptureWarnings` module global. The global is still updated so
812
+ * single-capture callers using `getLastCaptureWarnings()` keep working.
813
+ */
814
+ export declare function captureElementTreeWithWarnings(page: Page, selector: string | undefined, viewport: {
815
+ x: number;
816
+ y: number;
817
+ width: number;
818
+ height: number;
819
+ }, opts?: {
820
+ /** DM-562: when provided, `rasterizeReplacedElements` crops replaced-
821
+ * element rects from this PNG instead of taking fresh page.screenshot
822
+ * calls. Eliminates timing drift between the expected screenshot and
823
+ * rotating cross-origin-iframe content. Caller is responsible for
824
+ * ensuring the source covers the same coordinate space as `viewport`. */
825
+ rasterizeFromImagePath?: string;
826
+ }): Promise<{
827
+ tree: CapturedElement[];
828
+ warnings: CaptureWarning[];
829
+ }>;
830
+ /**
831
+ * Calibrate `fontAscent` on text-bearing elements by scanning a reference
832
+ * PNG (Chrome's actual paint) for each element's painted ink top, then
833
+ * back-solving the sub-pixel baseline as
834
+ * `inkTop - textTop + actualBoundingBoxAscent(text)` where the cap-height
835
+ * comes from `canvas.measureText(text).actualBoundingBoxAscent` (sub-pixel
836
+ * accurate, unlike `fontBoundingBoxAscent` which Chrome integer-rounds).
837
+ *
838
+ * Closes the residual ±0.6 px text-baseline drift documented in DM-397 /
839
+ * DM-418. Empirical extraction at capture time — uses Chrome's actual
840
+ * painted output as ground truth instead of trying to mirror Chromium's
841
+ * per-platform LayoutNG strut math.
842
+ *
843
+ * Caller must provide PNG bytes from a Chrome screenshot of the same
844
+ * viewport that was captured. For the test pipeline this is the same
845
+ * `expected.png` we already write to disk for diffing.
846
+ */
847
+ export declare function calibrateBaselines(page: Page, elements: CapturedElement[], pngBytes: Buffer | Uint8Array): Promise<void>;
848
+ /**
849
+ * Wrap inner SVG markup (as returned by `elementTreeToSvg`) in a complete
850
+ * `<svg>` document with the standard namespace, viewBox, and intrinsic size.
851
+ * This is the boilerplate every standalone-capture user would otherwise write
852
+ * themselves — call this when you want a self-contained SVG file.
853
+ */
854
+ export declare function wrapSvg(inner: string, width: number, height: number, opts?: {
855
+ tree?: CapturedElement[];
856
+ }): string;
857
+ /**
858
+ * DM-552: returns ` color-scheme="dark"` (with leading space, suitable for
859
+ * direct concatenation into an `<svg ...>` opening tag) when the captured
860
+ * tree's root reports `rootColorScheme === "dark"`. Returns an empty string
861
+ * for the light / no-scheme case so today's SVG output is byte-identical at
862
+ * the default. Use from any code path that emits a root `<svg>` outside of
863
+ * `wrapSvg` (e.g., `tests/runner.tsx` and `tests/real-world.tsx` build their
864
+ * own opening tags so they can inject a body-bg `<rect>` underneath the
865
+ * captured content).
866
+ */
867
+ export declare function rootSvgColorSchemeAttr(elements: CapturedElement[]): string;
868
+ /**
869
+ * DM-554: returns a body-bg `<rect>` markup string for the canvas when the
870
+ * captured tree's root provides a `rootBgComputed` (set by CAPTURE_SCRIPT
871
+ * from `getComputedStyle(document.documentElement).backgroundColor`).
872
+ * Returns an empty string when the field is missing (pre-DM-552 captures)
873
+ * or the resolved bg is transparent (pages that intend a transparent SVG).
874
+ *
875
+ * Used by `wrapSvg` and exported so external consumers that build their
876
+ * own `<svg>` opening tag (because they need to inject a body-bg rect at a
877
+ * specific position relative to other root-level rects, e.g. a frame
878
+ * overlay) can produce the same markup without re-implementing the
879
+ * fallback chain.
880
+ */
881
+ export declare function transparentRootBgRect(elements: CapturedElement[], width: number, height: number): string;
882
+ /**
883
+ * Convert a CapturedElement tree into SVG markup.
884
+ */
885
+ export declare function elementTreeToSvg(elements: CapturedElement[], width: number, height: number,
886
+ /** Prefix for clipPath IDs — required when combining multiple frames in one SVG to avoid ID collisions */
887
+ idPrefix?: string,
888
+ /**
889
+ * When true (default), include the shared glyph definitions in this frame's
890
+ * <defs>. Multi-frame animated SVGs should pass false so glyph defs can be
891
+ * hoisted to the top-level SVG <defs> and shared across frames.
892
+ */
893
+ includeGlyphDefs?: boolean,
894
+ /**
895
+ * DM-540: hiDPI multiplier the renderer uses when looking up resized
896
+ * variants in `_resizedDataUriCache`. Must match the value passed to
897
+ * `resizeEmbeddedImages` for the same tree, or the lookup misses and
898
+ * the renderer falls back to the source-resolution data URI. Default 2.
899
+ */
900
+ hiDPIFactor?: number): string;
901
+ export interface RGBA {
902
+ r: number;
903
+ g: number;
904
+ b: number;
905
+ a: number;
906
+ }
907
+ export declare function parseColor(css: string): RGBA | null;
908
+ /** Per-corner border-radius axis-pair (h = horizontal, v = vertical).
909
+ * An elliptical corner has h ≠ v; a circular corner has h = v. */
910
+ export type CornerRadiusPair = {
911
+ h: number;
912
+ v: number;
913
+ };
914
+ /** Resolved per-corner border-radius for the four corners of a rect, with
915
+ * CSS-spec corner-overlap scaling already applied. `uniform` is true when
916
+ * all four corners are circular AND share the same radius, which lets the
917
+ * renderer emit a single `<rect rx>` instead of a per-corner `<path>`. */
918
+ export type CornerRadii = {
919
+ tl: CornerRadiusPair;
920
+ tr: CornerRadiusPair;
921
+ br: CornerRadiusPair;
922
+ bl: CornerRadiusPair;
923
+ uniform: boolean;
924
+ };
925
+ /** Resolve the four per-corner radii for a captured element, applying CSS's
926
+ * corner-overlap scale-down (https://www.w3.org/TR/css-backgrounds-3/#corner-overlap):
927
+ * if any edge's two corner radii would together exceed the edge length, all
928
+ * four corners are scaled down uniformly. This is what produces the pill
929
+ * shape for `border-radius:999px` on a short element — without the spec
930
+ * clamp, two adjacent 999-px corners would overlap visibly. Falls back to
931
+ * the legacy single `borderRadius` shorthand when the per-corner longhands
932
+ * weren't captured (older snapshots). */
933
+ export declare function parseCornerRadii(styles: {
934
+ borderTopLeftRadius?: string;
935
+ borderTopRightRadius?: string;
936
+ borderBottomRightRadius?: string;
937
+ borderBottomLeftRadius?: string;
938
+ borderRadius?: string;
939
+ }, width: number, height: number): CornerRadii;
940
+ /** Inset each corner radius by the matching border-side widths, clamping to 0.
941
+ * Use this to derive the inner radii used by background clips (where the
942
+ * border has eaten into the corner) and inner border strokes. CSS specifies
943
+ * that the inner corner is the outer corner pulled in by the adjacent border
944
+ * widths (top + left for TL, top + right for TR, etc.). */
945
+ export declare function insetCornerRadii(c: CornerRadii, top: number, right: number, bottom: number, left: number): CornerRadii;
946
+ /** Emit an SVG path `d` attribute for a rounded rectangle with per-corner radii.
947
+ * Path goes clockwise from the top-left, using elliptical arc commands at each
948
+ * corner. Zero-radius corners collapse to a sharp 90° join. */
949
+ export declare function roundedRectPath(x: number, y: number, w: number, h: number, c: CornerRadii): string;
950
+ /** Emit a rounded-rect SVG element. Uses `<rect rx>` when the corners are
951
+ * uniform (cheaper, less markup); falls back to `<path>` for asymmetric or
952
+ * elliptical corners. The `attrs` string is injected verbatim — pass in
953
+ * fill/stroke/etc. */
954
+ export declare function roundedRectSvg(x: number, y: number, w: number, h: number, c: CornerRadii, attrs: string): string;
955
+ /**
956
+ * Rewrite a captured `<mask>` element's `outerHTML` so it can be safely
957
+ * inlined in the output SVG's `<defs>`. The mask's own `id` becomes
958
+ * `outputId`, and every other DOM id referenced inside the subtree gets
959
+ * prefixed with `idPrefix` so it can't collide with ids elsewhere in the
960
+ * output (multi-frame animated SVGs reuse the same prefix model). Every
961
+ * `url(#X)` reference inside the subtree is updated to point at the
962
+ * rewritten id. DM-493.
963
+ */
964
+ export declare function rewriteFragmentMaskDef(outerHTML: string, outputId: string, idPrefix: string): string;
965
+ /**
966
+ * Reposition a (previously rewritten) `<mask>` outerHTML so its content lives
967
+ * in the masked element's absolute user-space. CSS `mask-image: url("#id")`
968
+ * positions the mask source at the masked element's content-box origin, but
969
+ * SVG `<mask>` with `maskUnits="userSpaceOnUse"` interprets its content
970
+ * absolutely against the root SVG — so the captured mask coords (which are
971
+ * relative to the original `<mask>` element) need shifting by `(elX, elY)`.
972
+ * We do this by:
973
+ * 1. Forcing `maskUnits="userSpaceOnUse"` on the outer `<mask>`.
974
+ * 2. Replacing the mask's `x/y/width/height` with the masked element's
975
+ * absolute box (so the mask region matches the element).
976
+ * 3. Wrapping the mask's children in `<g transform="translate(elX, elY)">`.
977
+ * DM-493.
978
+ */
979
+ export declare function positionFragmentMaskDef(rewrittenOuterHTML: string, elX: number, elY: number, elW: number, elH: number): string;
980
+ /**
981
+ * Translate a CSS mask-image value + mask-* siblings into an SVG <mask>.
982
+ * Handles single-layer gradients and url() sources. Position/size/repeat are
983
+ * applied via an internal <pattern> for url sources; gradients use direct
984
+ * gradient fills sized to the element box.
985
+ *
986
+ * SVG <mask> uses luminance by default (bright pixels visible). CSS mask-mode
987
+ * 'alpha' makes the alpha channel control visibility. We set mask-type on the
988
+ * <mask> element accordingly. Note: Chromium may render mask-mode:'match-source'
989
+ * differently depending on the source; we pick alpha for gradients and url()
990
+ * (common case) and respect explicit mask-mode when given.
991
+ */
992
+ export declare function buildMaskDef(id: string, maskImage: string, elX: number, elY: number, w: number, h: number, maskMode: string, sizeCss: string, posCss: string, repeatCss: string, compositeCss: string,
993
+ /** DM-494: lookup table for `mask-image: element(#id)` references. Optional —
994
+ * callers without element() refs can omit it. The renderer's main caller
995
+ * threads through `elementMaskRasters` (collected from tree[0].maskRasters);
996
+ * unit tests can pass undefined to exercise the non-element() branches. */
997
+ elementRasters?: ReadonlyMap<string, MaskRasterRef>): {
998
+ id: string;
999
+ def: string;
1000
+ };
1001
+ /**
1002
+ * Translate CSS object-fit + object-position to an SVG preserveAspectRatio
1003
+ * attribute. Best-effort — SVG does not cover every CSS combination exactly.
1004
+ *
1005
+ * Mapping:
1006
+ * fill -> 'none' (stretch both axes, may distort)
1007
+ * contain -> '<align> meet' (fit inside, letterbox)
1008
+ * cover -> '<align> slice' (fill, crop)
1009
+ * none -> not representable without intrinsic size info; 'none' stretch is the least-bad default
1010
+ * scale-down -> treated as 'contain' (common case; exact rule requires intrinsic size)
1011
+ *
1012
+ * object-position keywords (top/right/bottom/left/center) and '0% 0%' / '100% 100%'
1013
+ * map to xMin/xMid/xMax + yMin/yMid/yMax. Percentages are bucketed to thirds
1014
+ * since SVG has no finer-grained alignment.
1015
+ */
1016
+ export declare function preserveAspectRatioFor(fit: string | undefined, pos: string | undefined): string;