@zumer/snapdom-plugins 3.0.0-beta.0 → 4.0.0
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/CHANGELOG.md +13 -0
- package/README.md +54 -20
- package/agent-map.d.ts +32 -0
- package/agent-map.js +5 -3
- package/ascii-export.d.ts +10 -0
- package/ascii-export.js +21 -6
- package/capture-frames.js +5 -5
- package/color-tint.js +4 -0
- package/context-export.d.ts +23 -0
- package/context-export.js +4 -1
- package/filter.js +1 -1
- package/gif-export.d.ts +6 -0
- package/gif-export.js +3 -3
- package/html-export.d.ts +8 -0
- package/html-export.js +6 -4
- package/index.d.ts +2 -1
- package/index.js +2 -1
- package/package.json +17 -6
- package/pdf/LICENSE +21 -0
- package/pdf/README.md +22 -0
- package/pdf/REFERENCE.md +520 -0
- package/pdf/src/frontmatter.js +200 -0
- package/pdf/src/furniture.js +398 -0
- package/pdf/src/index.js +2415 -0
- package/pdf/src/mega/README.md +47 -0
- package/pdf/src/mega/diag.js +148 -0
- package/pdf/src/mega/flow.js +56 -0
- package/pdf/src/mega/fonts.js +287 -0
- package/pdf/src/mega/forms.js +479 -0
- package/pdf/src/mega/labels.js +43 -0
- package/pdf/src/mega/meta.js +145 -0
- package/pdf/src/nav.js +357 -0
- package/pdf/src/paginate.js +583 -0
- package/pdf/src/tagged.js +355 -0
- package/pdf/src/text-layer.js +1771 -0
- package/pdf/src/writer/encrypt.js +218 -0
- package/pdf/src/writer/font.js +507 -0
- package/pdf/src/writer/index.js +1135 -0
- package/pdf/src/writer/subset.js +387 -0
- package/pdf.d.ts +294 -0
- package/pdf.js +1 -0
- package/redact-inputs.js +6 -6
- package/vector/CONTRACT.md +906 -0
- package/vector/LICENSE +21 -0
- package/vector/README.md +35 -0
- package/vector/src/adapters/snapdom.js +795 -0
- package/vector/src/collect/box.js +1676 -0
- package/vector/src/collect/font.js +487 -0
- package/vector/src/collect/image.js +1458 -0
- package/vector/src/collect/mathml.js +272 -0
- package/vector/src/collect/text.js +3027 -0
- package/vector/src/css/color.js +823 -0
- package/vector/src/css/gradient.js +1126 -0
- package/vector/src/css/matrix.js +398 -0
- package/vector/src/css/radius.js +124 -0
- package/vector/src/css/shadow.js +201 -0
- package/vector/src/emit/figma-json.js +1995 -0
- package/vector/src/emit/h2d-vector.js +215 -0
- package/vector/src/emit/h2d.js +1147 -0
- package/vector/src/emit/pdf.js +2571 -0
- package/vector/src/emit/svg-flat.js +4842 -0
- package/vector/src/exclusion.js +13 -0
- package/vector/src/index.js +2910 -0
- package/vector/src/paint.js +2908 -0
- package/vector/src/public.js +57 -0
- package/vector/src/writer/encrypt.js +227 -0
- package/vector/src/writer/font.js +507 -0
- package/vector/src/writer/index.js +1148 -0
- package/vector/src/writer/subset.js +387 -0
- package/vector/svd/index.js +72 -0
- package/vector/svd/schema.js +985 -0
- package/vector.d.ts +28 -0
- package/vector.js +1 -0
- package/video-export.d.ts +6 -0
- package/video-export.js +7 -7
- package/pdf-image.d.ts +0 -18
- package/pdf-image.js +0 -114
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Plugin changes
|
|
2
|
+
|
|
3
|
+
## 4.0.0 prepared locally
|
|
4
|
+
|
|
5
|
+
- Replace pdf-image with the full PDF exporter previously distributed as PDF Pro. Import pdf from @zumer/snapdom-plugins/pdf; toPdf returns a Blob, with optional download, selectable text, links and pagination.
|
|
6
|
+
- Add the MIT-licensed Vector plugin with internal SVD, editable SVG and Figma clipboard exports.
|
|
7
|
+
- Add typed HTML, context, map, ASCII and recording result methods.
|
|
8
|
+
- Add escaped title and language metadata to HTML snapshots.
|
|
9
|
+
- Add optional colored HTML ASCII previews and contrast control while preserving plain-text defaults.
|
|
10
|
+
- Handle Unicode code points in ASCII ramps and reject empty ramps.
|
|
11
|
+
- Reject invalid semantic budgets, map widths and tint opacity with explicit errors.
|
|
12
|
+
|
|
13
|
+
Requires SnapDOM 3.x. This version is prepared on a local branch and has not been published.
|
package/README.md
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Official plugins for [SnapDOM](https://github.com/zumerlab/snapdom), a capture engine for
|
|
4
4
|
web apps. Core exports images and canvases; this package adds HTML, structured context,
|
|
5
|
-
element maps, PDF, ASCII, visual transforms and live GIF/video recording.
|
|
5
|
+
element maps, PDF, editable vectors, ASCII, visual transforms and live GIF/video recording.
|
|
6
6
|
|
|
7
7
|
Image, HTML and structured exports use captured state; GIF and video start a sequence of
|
|
8
8
|
live captures when their export method is called.
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
12
|
-
Install the plugins next to a
|
|
12
|
+
Install the plugins next to a core supported by their peer dependency:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
npm install @zumer/snapdom @zumer/snapdom-plugins
|
|
15
|
+
npm install @zumer/snapdom@latest @zumer/snapdom-plugins@latest
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Plugins
|
|
18
|
+
Plugins 4.x in this checkout require a 3.x core. The 4.x release replaces pdf-image with the full PDF exporter and adds Vector. Both are MIT-licensed; this release has not been published to npm, so the `@latest` command above still installs the published plugins. Run `npm run compile` and `npm run site` from the monorepo root to try the local implementations. See the [migration guide](https://github.com/zumerlab/snapdom#migrating-from-v2) when upgrading from v2.
|
|
19
19
|
|
|
20
20
|
## Usage
|
|
21
21
|
|
|
@@ -41,8 +41,8 @@ import { filter, timestampOverlay, replaceText } from '@zumer/snapdom-plugins';
|
|
|
41
41
|
CDN (no install):
|
|
42
42
|
|
|
43
43
|
```js
|
|
44
|
-
import { snapdom } from 'https://esm.sh/@zumer/snapdom';
|
|
45
|
-
import { filter } from 'https://esm.sh/@zumer/snapdom-plugins/filter';
|
|
44
|
+
import { snapdom } from 'https://esm.sh/@zumer/snapdom@latest';
|
|
45
|
+
import { filter } from 'https://esm.sh/@zumer/snapdom-plugins@latest/filter';
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
---
|
|
@@ -169,7 +169,7 @@ Use `blocks` to hide the subtree that paints visible content. A `value` attribut
|
|
|
169
169
|
an input or textarea clears its displayed value and omits its structured `state.value`.
|
|
170
170
|
Existing default field masks still apply when you add block or attribute rules.
|
|
171
171
|
|
|
172
|
-
HTML exports use the
|
|
172
|
+
HTML exports use the prepared clone with the selected redaction rules. `agent-map` and `context-export` apply the same
|
|
173
173
|
block, attribute and field rules to their source-derived data, regardless of official
|
|
174
174
|
plugin order. The rules do not search arbitrary text or images for sensitive content.
|
|
175
175
|
|
|
@@ -206,7 +206,7 @@ snapdom(el, { plugins: [colorTint({ color: 'royalblue', opacity: 0.4 })] });
|
|
|
206
206
|
|
|
207
207
|
### `ascii-export`
|
|
208
208
|
|
|
209
|
-
Adds a `toAscii()` export method that converts captures to ASCII art.
|
|
209
|
+
Adds a `toAscii()` export method that converts captures to ASCII art. Plain text remains the default. Use `toAscii({ format: 'html', contrast: 0.8 })` for a colored, compact HTML preview; contrast defaults to zero and does not change existing text exports.
|
|
210
210
|
|
|
211
211
|
```js
|
|
212
212
|
import { asciiExport } from '@zumer/snapdom-plugins/ascii-export';
|
|
@@ -226,23 +226,33 @@ console.log(art);
|
|
|
226
226
|
|
|
227
227
|
---
|
|
228
228
|
|
|
229
|
-
### `pdf
|
|
229
|
+
### `pdf`
|
|
230
230
|
|
|
231
|
-
Adds `
|
|
232
|
-
|
|
231
|
+
Adds `toPdf()`, returning a PDF Blob with the capture's appearance, selectable text,
|
|
232
|
+
links and pagination. This is the MIT-licensed full exporter formerly distributed as PDF Pro.
|
|
233
|
+
It replaces `pdf-image`; there is no second image-only PDF implementation.
|
|
233
234
|
|
|
234
235
|
```js
|
|
235
|
-
import {
|
|
236
|
+
import { pdf } from '@zumer/snapdom-plugins/pdf';
|
|
236
237
|
|
|
237
|
-
const result = await snapdom(el, { plugins: [
|
|
238
|
-
await result.
|
|
238
|
+
const result = await snapdom(el, { plugins: [pdf()] });
|
|
239
|
+
const blob = await result.toPdf({ page: 'a4', margin: 36 });
|
|
240
|
+
await result.toPdf({ page: 'letter', download: 'report.pdf' });
|
|
239
241
|
```
|
|
240
242
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
Configure capture-time measurement on `pdf({ forms, outline, breakAvoid, shadow,
|
|
244
|
+
formValues })`. Configure paper size, orientation, margins, tagging, metadata,
|
|
245
|
+
headers, footers, compression or encryption on `toPdf()`. Downloads are opt-in;
|
|
246
|
+
ordinary exports return a Blob. See [the PDF overview](pdf/README.md) and [full reference](pdf/REFERENCE.md).
|
|
247
|
+
|
|
248
|
+
**Migration:** import `pdf` from `/pdf`, replace `pdfImage()` with `pdf()`, and
|
|
249
|
+
replace `toPdfImage()` with `toPdf({ download: 'capture.pdf' })`. Put orientation
|
|
250
|
+
on the export. The old temporary object-URL return becomes a persistent Blob.
|
|
251
|
+
The default page sizing and pagination differ: choose `page: 'a4'` explicitly.
|
|
252
|
+
|
|
253
|
+
Tagged output alone is not a PDF/UA conformance guarantee. The exporter reports
|
|
254
|
+
eligibility and blockers. Redact source text structurally; covering pixels does
|
|
255
|
+
not remove text from a searchable document.
|
|
246
256
|
|
|
247
257
|
---
|
|
248
258
|
|
|
@@ -385,6 +395,8 @@ await result.toHtml({ download: 'snapshot.html' });
|
|
|
385
395
|
|--------|------|---------|-------------|
|
|
386
396
|
| `fullDocument` | `boolean` | `true` | Wrap output in `<!DOCTYPE html>…`; if `false`, return just `<style>` + the fragment |
|
|
387
397
|
| `filename` | `string` | `'capture.html'` | Download filename when `opts.download` is `true` |
|
|
398
|
+
| `title` | `string` | `'Captured interface'` | Escaped document title |
|
|
399
|
+
| `lang` | `string` | `'en'` | Escaped document language |
|
|
388
400
|
|
|
389
401
|
Per-call `opts` accepts `fullDocument`, `filename` and `download`. Set `download: true` to
|
|
390
402
|
download, or pass a filename string. The method returns the HTML string either way.
|
|
@@ -393,6 +405,24 @@ The document retains source markup, including event-handler attributes; it is no
|
|
|
393
405
|
|
|
394
406
|
---
|
|
395
407
|
|
|
408
|
+
### `vector`
|
|
409
|
+
|
|
410
|
+
Adds `toVector()` for standalone SVG with editable text and supported shapes, and
|
|
411
|
+
`toFigma()` for Figma clipboard export. Its source and internal SVD format live in
|
|
412
|
+
`vector/` under the MIT license. The updated package has not been published yet.
|
|
413
|
+
|
|
414
|
+
```js
|
|
415
|
+
import { vector } from '@zumer/snapdom-plugins/vector';
|
|
416
|
+
const result = await snapdom(el, { plugins: [vector()] });
|
|
417
|
+
const svg = await result.toVector({ exclude: '.private' });
|
|
418
|
+
await result.toFigma(); // HTTPS or localhost, from a user action
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Register per capture. Unsupported paint is diagnosed; the connected source and
|
|
422
|
+
its styles must remain available during export. See [Vector](./vector/README.md).
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
396
426
|
### `gif-export`
|
|
397
427
|
|
|
398
428
|
Adds `toGif()`, which records the live element and returns an `image/gif` Blob. Its built-in
|
|
@@ -490,7 +520,7 @@ Start from the plugin template:
|
|
|
490
520
|
|
|
491
521
|
```bash
|
|
492
522
|
npx degit zumerlab/snapdom/packages/plugin-template my-plugin
|
|
493
|
-
cd my-plugin && npm install --save-dev @zumer/snapdom
|
|
523
|
+
cd my-plugin && npm install --save-dev @zumer/snapdom@latest
|
|
494
524
|
```
|
|
495
525
|
|
|
496
526
|
See [PLUGIN_SPEC.md](../../PLUGIN_SPEC.md) for the full hook specification and [CONTRIBUTING_PLUGINS.md](../../CONTRIBUTING_PLUGINS.md) to get your plugin listed on the community page.
|
|
@@ -500,3 +530,7 @@ See [PLUGIN_SPEC.md](../../PLUGIN_SPEC.md) for the full hook specification and [
|
|
|
500
530
|
## License
|
|
501
531
|
|
|
502
532
|
MIT
|
|
533
|
+
|
|
534
|
+
## Live demos
|
|
535
|
+
|
|
536
|
+
Run `npm run compile` and `npm run site` from the monorepo root, then open `/playground/`, `/pro/pdf/` or `/pro/vector/`. The demos generate actual files from editable DOM samples; the `/pro/` paths are retained for existing links and do not denote paid plugins.
|
package/agent-map.d.ts
CHANGED
|
@@ -29,3 +29,35 @@ export interface AgentMapOptions {
|
|
|
29
29
|
* of the interactive elements and their bounding boxes. Published as `prompt-export` until 2.12.0 renamed it.
|
|
30
30
|
*/
|
|
31
31
|
export declare function agentMap(options?: AgentMapOptions): SnapdomPlugin;
|
|
32
|
+
|
|
33
|
+
export interface AgentMapEntry {
|
|
34
|
+
i: number;
|
|
35
|
+
n: string;
|
|
36
|
+
r: string;
|
|
37
|
+
b: [number, number, number, number];
|
|
38
|
+
s?: Record<string, string | number | boolean>;
|
|
39
|
+
t?: string;
|
|
40
|
+
a?: Record<string, string>;
|
|
41
|
+
isSemanticOnly?: true;
|
|
42
|
+
}
|
|
43
|
+
export interface AgentMapResult {
|
|
44
|
+
image?: string;
|
|
45
|
+
map: AgentMapEntry[];
|
|
46
|
+
dimensions: { width: number; height: number };
|
|
47
|
+
}
|
|
48
|
+
export interface ToAgentMapOptions {
|
|
49
|
+
image?: 'annotated' | 'raw' | false;
|
|
50
|
+
imageFormat?: 'png' | 'jpg' | 'webp';
|
|
51
|
+
imageQuality?: number;
|
|
52
|
+
maxImageWidth?: number;
|
|
53
|
+
width?: number;
|
|
54
|
+
height?: number;
|
|
55
|
+
scale?: number;
|
|
56
|
+
dpr?: number;
|
|
57
|
+
backgroundColor?: string;
|
|
58
|
+
}
|
|
59
|
+
declare module '@zumer/snapdom' {
|
|
60
|
+
interface CaptureResult {
|
|
61
|
+
toAgentMap(options?: ToAgentMapOptions): Promise<AgentMapResult>;
|
|
62
|
+
}
|
|
63
|
+
}
|
package/agent-map.js
CHANGED
|
@@ -64,12 +64,12 @@ export function agentMap(options = {}) {
|
|
|
64
64
|
// The map is read in afterClone: with no clone there is no map, only a plausible-looking
|
|
65
65
|
// empty one.
|
|
66
66
|
if (needs !== 'clone' && needs !== 'render') {
|
|
67
|
-
throw new Error(`[
|
|
67
|
+
throw new Error(`[SnapDOM] agent-map cannot run at stage '${needs}': it supports clone, render`);
|
|
68
68
|
}
|
|
69
69
|
// A capture with no render can never carry an image, and asking for both is a
|
|
70
70
|
// contradiction the caller should hear now, not at export time.
|
|
71
71
|
if (needs === 'clone' && image !== false) {
|
|
72
|
-
throw new Error("[
|
|
72
|
+
throw new Error("[SnapDOM] agent-map: needs: 'clone' makes no image; pass image: false, or use needs: 'render'.");
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
return {
|
|
@@ -97,11 +97,13 @@ export function agentMap(options = {}) {
|
|
|
97
97
|
agentMap: async (ctx, opts = {}) => {
|
|
98
98
|
const meta = ctx.__agentMapMeta;
|
|
99
99
|
const wantImage = opts.image !== undefined ? opts.image : image;
|
|
100
|
-
if (!meta) throw new Error('[
|
|
100
|
+
if (!meta) throw new Error('[SnapDOM] agent-map: this capture has no map to export.');
|
|
101
101
|
|
|
102
102
|
const format = opts.imageFormat || imageFormat;
|
|
103
103
|
const quality = opts.imageQuality ?? imageQuality;
|
|
104
104
|
const maxWidth = opts.maxImageWidth ?? maxImageWidth;
|
|
105
|
+
if (!Number.isFinite(maxWidth) || maxWidth < 1) throw new RangeError('[SnapDOM] agent-map: maxImageWidth must be positive');
|
|
106
|
+
if (wantImage !== false && wantImage !== 'raw' && wantImage !== 'annotated') throw new TypeError('[SnapDOM] agent-map: image must be annotated, raw or false');
|
|
105
107
|
const geometry = ctx.meta;
|
|
106
108
|
const sourceW = geometry?.vbW || meta.dimensions.width || 1;
|
|
107
109
|
const sourceH = geometry?.vbH || meta.dimensions.height || 1;
|
package/ascii-export.d.ts
CHANGED
|
@@ -14,7 +14,17 @@ export interface AsciiExportOptions {
|
|
|
14
14
|
charset?: string;
|
|
15
15
|
/** Invert the luminance mapping. */
|
|
16
16
|
invert?: boolean;
|
|
17
|
+
/** Plain text (default) or colored HTML with preserved character spacing. */
|
|
18
|
+
format?: 'text' | 'html';
|
|
19
|
+
/** Non-negative luminance contrast boost. */
|
|
20
|
+
contrast?: number;
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
/** Adds `result.toAscii()`. */
|
|
20
24
|
export declare function asciiExport(options?: AsciiExportOptions): SnapdomPlugin;
|
|
25
|
+
|
|
26
|
+
declare module '@zumer/snapdom' {
|
|
27
|
+
interface CaptureResult {
|
|
28
|
+
toAscii(options?: AsciiExportOptions): Promise<string>;
|
|
29
|
+
}
|
|
30
|
+
}
|
package/ascii-export.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* @param {number} [options.width=80] - Character width of output
|
|
9
9
|
* @param {string} [options.charset=' .:-=+*#%@'] - Characters from lightest to darkest
|
|
10
10
|
* @param {boolean} [options.invert=false] - Invert luminance mapping
|
|
11
|
+
* @param {'text'|'html'} [options.format='text'] - Plain text or colored HTML preview
|
|
12
|
+
* @param {number} [options.contrast=0] - Non-negative luminance contrast boost
|
|
11
13
|
* @returns {Object} SnapDOM plugin
|
|
12
14
|
*/
|
|
13
15
|
export function asciiExport(options = {}) {
|
|
@@ -15,6 +17,8 @@ export function asciiExport(options = {}) {
|
|
|
15
17
|
width: charWidth = 80,
|
|
16
18
|
charset = ' .:-=+*#%@',
|
|
17
19
|
invert = false,
|
|
20
|
+
format = 'text',
|
|
21
|
+
contrast = 0,
|
|
18
22
|
} = options;
|
|
19
23
|
|
|
20
24
|
return {
|
|
@@ -26,12 +30,17 @@ export function asciiExport(options = {}) {
|
|
|
26
30
|
// Capture width is a pixel size, not a character count. v3 merges it into opts.
|
|
27
31
|
const width = Object.hasOwn(ctx.export.requestedOptions, 'width') || opts.width !== ctx.width
|
|
28
32
|
? opts.width : charWidth;
|
|
29
|
-
if (!Number.isFinite(width) || width < 1) throw new RangeError('[
|
|
33
|
+
if (!Number.isFinite(width) || width < 1) throw new RangeError('[SnapDOM] ascii-export: width must be a positive number');
|
|
30
34
|
const cols = Math.floor(width);
|
|
31
|
-
const
|
|
35
|
+
const value = opts.charset ?? charset;
|
|
36
|
+
if (typeof value !== 'string' || !value.length) throw new RangeError('[SnapDOM] ascii-export: charset must be a nonempty string');
|
|
37
|
+
const chars = Array.from(value);
|
|
32
38
|
const inv = opts.invert ?? invert;
|
|
39
|
+
const outputFormat = ctx.export.requestedOptions.format ?? format;
|
|
40
|
+
if (outputFormat !== 'text' && outputFormat !== 'html') throw new TypeError('[SnapDOM] ascii-export: format must be text or html');
|
|
41
|
+
const gain = opts.contrast ?? contrast;
|
|
42
|
+
if (!Number.isFinite(gain) || gain < 0) throw new RangeError('[SnapDOM] ascii-export: contrast must be non-negative');
|
|
33
43
|
|
|
34
|
-
if (!chars.length) throw new RangeError('[snapdom] ascii-export: charset must not be empty');
|
|
35
44
|
// Keep core's decoded-image/Safari path and captured originals without firing hooks again.
|
|
36
45
|
const source = await exports.canvas({
|
|
37
46
|
...opts, width: ctx.width, height: ctx.height, scale: 1, dpr: 1, canvas: null,
|
|
@@ -46,18 +55,24 @@ export function asciiExport(options = {}) {
|
|
|
46
55
|
c.drawImage(source, 0, 0, cols, rows);
|
|
47
56
|
const data = c.getImageData(0, 0, cols, rows).data;
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
const colored = outputFormat === 'html';
|
|
59
|
+
let ascii = colored ? '<pre style="margin:0;font:7px/1 monospace;white-space:pre;overflow-wrap:normal;max-width:100%;overflow:auto;background:white">' : '';
|
|
50
60
|
for (let y = 0; y < rows; y++) {
|
|
51
61
|
for (let x = 0; x < cols; x++) {
|
|
52
62
|
const i = (y * cols + x) * 4;
|
|
53
63
|
let lum = 1 - (data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114) / 255;
|
|
64
|
+
lum = Math.max(0, Math.min(1, (1 + gain) * (lum - 0.5) + 0.5));
|
|
54
65
|
if (inv) lum = 1 - lum;
|
|
55
66
|
const ci = Math.min(Math.floor(lum * chars.length), chars.length - 1);
|
|
56
|
-
|
|
67
|
+
const char = chars[ci];
|
|
68
|
+
if (colored) {
|
|
69
|
+
const safe = char.replace(/[&<>]/g, c => ({ '&': '&', '<': '<', '>': '>' }[c]));
|
|
70
|
+
ascii += `<span style="color:rgb(${data[i]},${data[i + 1]},${data[i + 2]})">${safe}</span>`;
|
|
71
|
+
} else ascii += char;
|
|
57
72
|
}
|
|
58
73
|
ascii += '\n';
|
|
59
74
|
}
|
|
60
|
-
return ascii;
|
|
75
|
+
return colored ? ascii + '</pre>' : ascii;
|
|
61
76
|
}
|
|
62
77
|
};
|
|
63
78
|
}
|
package/capture-frames.js
CHANGED
|
@@ -50,12 +50,12 @@ export function frameOptions(ctx, opts, defaults, name) {
|
|
|
50
50
|
const duration = exportOption(ctx, opts, 'duration', defaults.duration);
|
|
51
51
|
const frames = exportOption(ctx, opts, 'frames', defaults.frames);
|
|
52
52
|
const scale = exportOption(ctx, opts, 'scale', defaults.scale ?? ctx.scale ?? 1);
|
|
53
|
-
if (!Number.isFinite(fps) || fps <= 0) throw new RangeError(`[
|
|
54
|
-
if (!Number.isFinite(scale) || scale <= 0) throw new RangeError(`[
|
|
55
|
-
if (frames != null && (!Number.isInteger(frames) || frames < 1)) throw new RangeError(`[
|
|
56
|
-
if (frames == null && (!Number.isFinite(duration) || duration <= 0)) throw new RangeError(`[
|
|
53
|
+
if (!Number.isFinite(fps) || fps <= 0) throw new RangeError(`[SnapDOM] ${name}: fps must be positive and finite`);
|
|
54
|
+
if (!Number.isFinite(scale) || scale <= 0) throw new RangeError(`[SnapDOM] ${name}: scale must be positive and finite`);
|
|
55
|
+
if (frames != null && (!Number.isInteger(frames) || frames < 1)) throw new RangeError(`[SnapDOM] ${name}: frames must be a positive integer`);
|
|
56
|
+
if (frames == null && (!Number.isFinite(duration) || duration <= 0)) throw new RangeError(`[SnapDOM] ${name}: duration must be positive and finite`);
|
|
57
57
|
const count = frames ?? Math.max(1, Math.round(duration / 1000 * fps));
|
|
58
|
-
if (!Number.isFinite(count)) throw new RangeError(`[
|
|
58
|
+
if (!Number.isFinite(count)) throw new RangeError(`[SnapDOM] ${name}: frame count must be finite`);
|
|
59
59
|
|
|
60
60
|
// Copy capture inputs, not released stage artifacts or export-session internals. In
|
|
61
61
|
// particular createContext splits exclusion predicates out of exclude: put them back.
|
package/color-tint.js
CHANGED
|
@@ -16,6 +16,10 @@ export function colorTint(options = {}) {
|
|
|
16
16
|
opacity = 1,
|
|
17
17
|
} = options;
|
|
18
18
|
|
|
19
|
+
if (!Number.isFinite(opacity) || opacity < 0 || opacity > 1) {
|
|
20
|
+
throw new RangeError('[SnapDOM] color-tint: opacity must be between 0 and 1');
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
return {
|
|
20
24
|
name: 'color-tint',
|
|
21
25
|
pure: true,
|
package/context-export.d.ts
CHANGED
|
@@ -23,3 +23,26 @@ export interface ContextExportOptions {
|
|
|
23
23
|
export declare function contextExport(options?: ContextExportOptions): SnapdomPlugin;
|
|
24
24
|
|
|
25
25
|
export default contextExport;
|
|
26
|
+
|
|
27
|
+
export interface ContextNode {
|
|
28
|
+
tag: string;
|
|
29
|
+
id?: string;
|
|
30
|
+
class?: string;
|
|
31
|
+
box?: [number, number, number, number];
|
|
32
|
+
text?: string;
|
|
33
|
+
state?: Record<string, string | number | boolean>;
|
|
34
|
+
children?: ContextNode[];
|
|
35
|
+
}
|
|
36
|
+
export interface ContextResult {
|
|
37
|
+
root: ContextNode | null;
|
|
38
|
+
truncated: boolean;
|
|
39
|
+
nodes: number;
|
|
40
|
+
}
|
|
41
|
+
export type ToContextOptions = Pick<ContextExportOptions, 'format' | 'maxTextLength' | 'geometry'>;
|
|
42
|
+
declare module '@zumer/snapdom' {
|
|
43
|
+
interface CaptureResult {
|
|
44
|
+
toContext(options: ToContextOptions & { format: 'json' }): Promise<ContextResult>;
|
|
45
|
+
toContext(options: ToContextOptions & { format: 'outline' }): Promise<string>;
|
|
46
|
+
toContext(options?: ToContextOptions): Promise<string | ContextResult>;
|
|
47
|
+
}
|
|
48
|
+
}
|
package/context-export.js
CHANGED
|
@@ -73,6 +73,8 @@ export function contextExport(options = {}) {
|
|
|
73
73
|
maxNodes = 800,
|
|
74
74
|
geometry = true,
|
|
75
75
|
} = options
|
|
76
|
+
if (!Number.isInteger(maxNodes) || maxNodes < 1) throw new RangeError('[SnapDOM] context-export: maxNodes must be a positive integer')
|
|
77
|
+
if (!Number.isInteger(maxTextLength) || maxTextLength < 1) throw new RangeError('[SnapDOM] context-export: maxTextLength must be a positive integer')
|
|
76
78
|
// `needs` names how far the capture has to run. This export reads the live page in
|
|
77
79
|
// beforeClone, so it does not need pixels: `contextExport({ needs: 'clone' })` skips the
|
|
78
80
|
// render. It cannot go shallower than that any more, and it should not: a capture that
|
|
@@ -106,11 +108,12 @@ export function contextExport(options = {}) {
|
|
|
106
108
|
return {
|
|
107
109
|
context: async (ctx, opts = {}) => {
|
|
108
110
|
const snap = ctx.__contextSnapshot
|
|
109
|
-
if (!snap) throw new Error('[
|
|
111
|
+
if (!snap) throw new Error('[SnapDOM] context-export: this capture has no snapshot (the plugin did not run with it). It is not re-read later, since that would be a different instant.')
|
|
110
112
|
// Core carries its image format in every normalized export bag. Only this
|
|
111
113
|
// plugin's two formats override its factory default.
|
|
112
114
|
const _format = opts.format === 'json' || opts.format === 'outline' ? opts.format : format
|
|
113
115
|
const _maxText = opts.maxTextLength ?? maxTextLength
|
|
116
|
+
if (!Number.isInteger(_maxText) || _maxText < 1) throw new RangeError('[SnapDOM] context-export: maxTextLength must be a positive integer')
|
|
114
117
|
const _geometry = opts.geometry ?? geometry
|
|
115
118
|
|
|
116
119
|
const root = snap.root && project(snap.root, _maxText, _geometry)
|
package/filter.js
CHANGED
|
@@ -26,7 +26,7 @@ export function filter(options = {}) {
|
|
|
26
26
|
} = options;
|
|
27
27
|
|
|
28
28
|
// An unknown preset must not turn the plugin off when a filter string was also given.
|
|
29
|
-
if (preset && !presets[preset]) console.warn(`[
|
|
29
|
+
if (preset && !presets[preset]) console.warn(`[SnapDOM] filter: unknown preset ${JSON.stringify(preset)}`);
|
|
30
30
|
const cssFilter = presets[preset] || filterValue;
|
|
31
31
|
|
|
32
32
|
return {
|
package/gif-export.d.ts
CHANGED
|
@@ -27,3 +27,9 @@ export interface GifExportOptions {
|
|
|
27
27
|
/** Adds `result.toGif()`: records a sequence of captures into an animated GIF. */
|
|
28
28
|
export declare function gifExport(options?: GifExportOptions): SnapdomPlugin;
|
|
29
29
|
export default gifExport;
|
|
30
|
+
|
|
31
|
+
declare module '@zumer/snapdom' {
|
|
32
|
+
interface CaptureResult {
|
|
33
|
+
toGif(options?: GifExportOptions & { download?: boolean | string }): Promise<Blob>;
|
|
34
|
+
}
|
|
35
|
+
}
|
package/gif-export.js
CHANGED
|
@@ -39,15 +39,15 @@ export function gifExport(options = {}) {
|
|
|
39
39
|
return {
|
|
40
40
|
gif: async (ctx, opts = {}) => {
|
|
41
41
|
const el = ctx.element;
|
|
42
|
-
if (!el) throw new Error('[
|
|
42
|
+
if (!el) throw new Error('[SnapDOM] gif-export: there is no element to record.');
|
|
43
43
|
|
|
44
44
|
const recording = frameOptions(ctx, opts, { fps, duration, frames: frameOpt, scale, background }, 'gif-export');
|
|
45
45
|
const { fps: _fps, count: _count, background: _bg } = recording;
|
|
46
46
|
const colors = exportOption(ctx, opts, 'maxColors', maxColors);
|
|
47
|
-
if (!Number.isFinite(colors)) throw new RangeError('[
|
|
47
|
+
if (!Number.isFinite(colors)) throw new RangeError('[SnapDOM] gif-export: maxColors must be finite');
|
|
48
48
|
const _max = Math.min(256, Math.max(2, Math.floor(colors)));
|
|
49
49
|
const _repeat = exportOption(ctx, opts, 'repeat', repeat);
|
|
50
|
-
if (!Number.isInteger(_repeat) || _repeat < -1 || _repeat > 65535) throw new RangeError('[
|
|
50
|
+
if (!Number.isInteger(_repeat) || _repeat < -1 || _repeat > 65535) throw new RangeError('[SnapDOM] gif-export: repeat must be an integer from -1 to 65535');
|
|
51
51
|
const delayCs = Math.max(2, Math.round(100 / _fps)); // GIF delay unit is 1/100 s
|
|
52
52
|
|
|
53
53
|
let W = 0, H = 0;
|
package/html-export.d.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import type { SnapdomPlugin } from '@zumer/snapdom';
|
|
9
9
|
|
|
10
10
|
export interface HtmlExportOptions {
|
|
11
|
+
title?: string;
|
|
12
|
+
lang?: string;
|
|
11
13
|
/** Wrap in a full `<!DOCTYPE html>` document; false returns `<style>` + fragment. */
|
|
12
14
|
fullDocument?: boolean;
|
|
13
15
|
/** Download filename when `toHtml({ download: true })` is used. */
|
|
@@ -29,3 +31,9 @@ export interface ToHtmlOptions extends HtmlExportOptions {
|
|
|
29
31
|
export declare function htmlExport(options?: HtmlExportOptions): SnapdomPlugin;
|
|
30
32
|
|
|
31
33
|
export default htmlExport;
|
|
34
|
+
|
|
35
|
+
declare module '@zumer/snapdom' {
|
|
36
|
+
interface CaptureResult {
|
|
37
|
+
toHtml(options?: ToHtmlOptions): Promise<string>;
|
|
38
|
+
}
|
|
39
|
+
}
|
package/html-export.js
CHANGED
|
@@ -20,6 +20,8 @@ export function htmlExport(options = {}) {
|
|
|
20
20
|
const {
|
|
21
21
|
fullDocument = true,
|
|
22
22
|
filename = 'capture.html',
|
|
23
|
+
title = 'Captured interface',
|
|
24
|
+
lang = 'en',
|
|
23
25
|
} = options;
|
|
24
26
|
|
|
25
27
|
// Where the serialized markup is parked between afterRender and toHtml. It has to hang
|
|
@@ -72,9 +74,8 @@ export function htmlExport(options = {}) {
|
|
|
72
74
|
const frozenBody = ctx[BODY];
|
|
73
75
|
if (typeof frozenBody !== 'string') {
|
|
74
76
|
throw new Error(
|
|
75
|
-
'[
|
|
76
|
-
'
|
|
77
|
-
'needs:\'clone\' never reaches the render stage).'
|
|
77
|
+
'[SnapDOM] html-export: this capture produced no render, so there is no HTML ' +
|
|
78
|
+
'to export (it stopped at needs: \'clone\').'
|
|
78
79
|
);
|
|
79
80
|
}
|
|
80
81
|
const a = ctx.artifacts;
|
|
@@ -82,8 +83,9 @@ export function htmlExport(options = {}) {
|
|
|
82
83
|
const body = frozenBody;
|
|
83
84
|
|
|
84
85
|
const asDoc = opts.fullDocument ?? fullDocument;
|
|
86
|
+
const escape = value => String(value).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
85
87
|
const html = asDoc
|
|
86
|
-
? `<!DOCTYPE html>\n<html>\n<head>\n<meta charset="utf-8">\n<style>${css}</style>\n</head>\n<body>\n${body}\n</body>\n</html>\n`
|
|
88
|
+
? `<!DOCTYPE html>\n<html lang="${escape(opts.lang ?? lang)}">\n<head>\n<meta charset="utf-8">\n<title>${escape(opts.title ?? title)}</title>\n<style>${css}</style>\n</head>\n<body>\n${body}\n</body>\n</html>\n`
|
|
87
89
|
: `<style>${css}</style>\n${body}\n`;
|
|
88
90
|
|
|
89
91
|
const dl = opts.download;
|
package/index.d.ts
CHANGED
|
@@ -11,9 +11,10 @@ export * from './replace-text.js';
|
|
|
11
11
|
export * from './timestamp-overlay.js';
|
|
12
12
|
export * from './redact-inputs.js';
|
|
13
13
|
export * from './ascii-export.js';
|
|
14
|
-
export * from './pdf
|
|
14
|
+
export * from './pdf.js';
|
|
15
15
|
export * from './html-export.js';
|
|
16
16
|
export * from './gif-export.js';
|
|
17
17
|
export * from './video-export.js';
|
|
18
18
|
export * from './agent-map.js';
|
|
19
19
|
export * from './context-export.js';
|
|
20
|
+
export * from './vector.js';
|
package/index.js
CHANGED
|
@@ -12,9 +12,10 @@ export { replaceText } from './replace-text.js';
|
|
|
12
12
|
export { redactInputs } from './redact-inputs.js';
|
|
13
13
|
export { filter } from './filter.js';
|
|
14
14
|
export { colorTint } from './color-tint.js';
|
|
15
|
-
export {
|
|
15
|
+
export { pdf } from './pdf.js';
|
|
16
16
|
export { agentMap } from './agent-map.js';
|
|
17
17
|
export { contextExport } from './context-export.js';
|
|
18
18
|
export { htmlExport } from './html-export.js';
|
|
19
19
|
export { gifExport } from './gif-export.js';
|
|
20
20
|
export { videoExport } from './video-export.js';
|
|
21
|
+
export { vector } from './vector.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zumer/snapdom-plugins",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Official plugins for SnapDOM",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,18 +11,28 @@
|
|
|
11
11
|
"./redact-inputs": "./redact-inputs.js",
|
|
12
12
|
"./filter": "./filter.js",
|
|
13
13
|
"./color-tint": "./color-tint.js",
|
|
14
|
-
"./pdf-image": "./pdf-image.js",
|
|
15
14
|
"./agent-map": "./agent-map.js",
|
|
16
15
|
"./context-export": "./context-export.js",
|
|
17
16
|
"./html-export": "./html-export.js",
|
|
18
17
|
"./gif-export": "./gif-export.js",
|
|
19
|
-
"./video-export": "./video-export.js"
|
|
18
|
+
"./video-export": "./video-export.js",
|
|
19
|
+
"./pdf": "./pdf.js",
|
|
20
|
+
"./vector": "./vector.js"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
22
23
|
"*.js",
|
|
23
24
|
"*.d.ts",
|
|
24
25
|
"README.md",
|
|
25
|
-
"LICENSE"
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"pdf/src/",
|
|
28
|
+
"pdf/REFERENCE.md",
|
|
29
|
+
"pdf/LICENSE",
|
|
30
|
+
"CHANGELOG.md",
|
|
31
|
+
"vector/src/",
|
|
32
|
+
"vector/svd/",
|
|
33
|
+
"vector/CONTRACT.md",
|
|
34
|
+
"vector/LICENSE",
|
|
35
|
+
"vector/README.md"
|
|
26
36
|
],
|
|
27
37
|
"types": "./index.d.ts",
|
|
28
38
|
"keywords": [
|
|
@@ -43,9 +53,10 @@
|
|
|
43
53
|
},
|
|
44
54
|
"homepage": "https://snapdom.dev/",
|
|
45
55
|
"peerDependencies": {
|
|
46
|
-
"@zumer/snapdom": "
|
|
56
|
+
"@zumer/snapdom": "^3.0.0"
|
|
47
57
|
},
|
|
48
58
|
"devDependencies": {
|
|
49
59
|
"@zumer/snapdom": "file:../.."
|
|
50
|
-
}
|
|
60
|
+
},
|
|
61
|
+
"sideEffects": false
|
|
51
62
|
}
|
package/pdf/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ZumerLab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/pdf/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# PDF plugin
|
|
2
|
+
|
|
3
|
+
MIT-licensed PDF export for SnapDOM, replacing the former PDF Pro and `pdf-image` implementations. This source is part of `@zumer/snapdom-plugins` 4.x and requires core 3.x. The 4.x release has not been published to npm yet.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import { snapdom } from '@zumer/snapdom';
|
|
7
|
+
import { pdf } from '@zumer/snapdom-plugins/pdf';
|
|
8
|
+
|
|
9
|
+
const result = await snapdom(element, { plugins: [pdf()] });
|
|
10
|
+
const blob = await result.toPdf({ page: 'a4', margin: 36 });
|
|
11
|
+
await result.toPdf({ page: 'letter', download: 'report.pdf' });
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`toPdf()` returns a `Blob`; downloading is opt-in. The exporter preserves the captured appearance and supports selectable, searchable text, links, pagination, repeated table headers, Unicode, transparency and optional form fields. Export options also cover headers, footers, metadata, tagging and encryption. Tagged output alone does not guarantee PDF/UA conformance.
|
|
15
|
+
|
|
16
|
+
Put capture-time measurement options on `pdf()` and paper/output options on `toPdf()`. Structurally redact sensitive source content before capture: covering pixels does not remove searchable text. See the [full reference](REFERENCE.md) and [public types](../pdf.d.ts).
|
|
17
|
+
|
|
18
|
+
## Live demo and verification
|
|
19
|
+
|
|
20
|
+
From the monorepo root, run `npm run compile` and `npm run site`, then open `/pro/pdf/` or `/playground/`. These demos generate real PDFs from editable DOM samples. The retained `/pro/` URL does not denote a paid product.
|
|
21
|
+
|
|
22
|
+
Run `npm run test:pdf` for the PDF engine suites. See [verification notes](test/README.md).
|