@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.
Files changed (77) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +54 -20
  3. package/agent-map.d.ts +32 -0
  4. package/agent-map.js +5 -3
  5. package/ascii-export.d.ts +10 -0
  6. package/ascii-export.js +21 -6
  7. package/capture-frames.js +5 -5
  8. package/color-tint.js +4 -0
  9. package/context-export.d.ts +23 -0
  10. package/context-export.js +4 -1
  11. package/filter.js +1 -1
  12. package/gif-export.d.ts +6 -0
  13. package/gif-export.js +3 -3
  14. package/html-export.d.ts +8 -0
  15. package/html-export.js +6 -4
  16. package/index.d.ts +2 -1
  17. package/index.js +2 -1
  18. package/package.json +17 -6
  19. package/pdf/LICENSE +21 -0
  20. package/pdf/README.md +22 -0
  21. package/pdf/REFERENCE.md +520 -0
  22. package/pdf/src/frontmatter.js +200 -0
  23. package/pdf/src/furniture.js +398 -0
  24. package/pdf/src/index.js +2415 -0
  25. package/pdf/src/mega/README.md +47 -0
  26. package/pdf/src/mega/diag.js +148 -0
  27. package/pdf/src/mega/flow.js +56 -0
  28. package/pdf/src/mega/fonts.js +287 -0
  29. package/pdf/src/mega/forms.js +479 -0
  30. package/pdf/src/mega/labels.js +43 -0
  31. package/pdf/src/mega/meta.js +145 -0
  32. package/pdf/src/nav.js +357 -0
  33. package/pdf/src/paginate.js +583 -0
  34. package/pdf/src/tagged.js +355 -0
  35. package/pdf/src/text-layer.js +1771 -0
  36. package/pdf/src/writer/encrypt.js +218 -0
  37. package/pdf/src/writer/font.js +507 -0
  38. package/pdf/src/writer/index.js +1135 -0
  39. package/pdf/src/writer/subset.js +387 -0
  40. package/pdf.d.ts +294 -0
  41. package/pdf.js +1 -0
  42. package/redact-inputs.js +6 -6
  43. package/vector/CONTRACT.md +906 -0
  44. package/vector/LICENSE +21 -0
  45. package/vector/README.md +35 -0
  46. package/vector/src/adapters/snapdom.js +795 -0
  47. package/vector/src/collect/box.js +1676 -0
  48. package/vector/src/collect/font.js +487 -0
  49. package/vector/src/collect/image.js +1458 -0
  50. package/vector/src/collect/mathml.js +272 -0
  51. package/vector/src/collect/text.js +3027 -0
  52. package/vector/src/css/color.js +823 -0
  53. package/vector/src/css/gradient.js +1126 -0
  54. package/vector/src/css/matrix.js +398 -0
  55. package/vector/src/css/radius.js +124 -0
  56. package/vector/src/css/shadow.js +201 -0
  57. package/vector/src/emit/figma-json.js +1995 -0
  58. package/vector/src/emit/h2d-vector.js +215 -0
  59. package/vector/src/emit/h2d.js +1147 -0
  60. package/vector/src/emit/pdf.js +2571 -0
  61. package/vector/src/emit/svg-flat.js +4842 -0
  62. package/vector/src/exclusion.js +13 -0
  63. package/vector/src/index.js +2910 -0
  64. package/vector/src/paint.js +2908 -0
  65. package/vector/src/public.js +57 -0
  66. package/vector/src/writer/encrypt.js +227 -0
  67. package/vector/src/writer/font.js +507 -0
  68. package/vector/src/writer/index.js +1148 -0
  69. package/vector/src/writer/subset.js +387 -0
  70. package/vector/svd/index.js +72 -0
  71. package/vector/svd/schema.js +985 -0
  72. package/vector.d.ts +28 -0
  73. package/vector.js +1 -0
  74. package/video-export.d.ts +6 -0
  75. package/video-export.js +7 -7
  76. package/pdf-image.d.ts +0 -18
  77. package/pdf-image.js +0 -114
package/vector.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import type { SnapdomPlugin } from '@zumer/snapdom';
2
+
3
+ /** A CSS selector, or a predicate that returns true for an element to leave out. */
4
+ export type Exclusion = string | ((element: Element) => boolean);
5
+
6
+ export interface VectorOptions {
7
+ /** Elements to leave out of the vector output. On a call it wins over the vector() default. */
8
+ exclude?: Exclusion | Exclusion[];
9
+ }
10
+
11
+ /** Create the plugin. A capture it runs on gains toVector() and toFigma(). */
12
+ export declare function vector(options?: VectorOptions): SnapdomPlugin;
13
+ export default vector;
14
+
15
+ declare module '@zumer/snapdom' {
16
+ interface CaptureResult {
17
+ /**
18
+ * The capture as a standalone SVG string: real shapes, editable text, no
19
+ * foreignObject. Requires vector() on this capture and a connected source element.
20
+ */
21
+ toVector(options?: VectorOptions): Promise<string>;
22
+ /**
23
+ * Copy the capture to the clipboard in Figma's paste format, then paste it into
24
+ * a Figma file. Needs the async clipboard API: https or localhost, from a user action.
25
+ */
26
+ toFigma(options?: VectorOptions): Promise<void>;
27
+ }
28
+ }
package/vector.js ADDED
@@ -0,0 +1 @@
1
+ export { default, vector } from './vector/src/public.js';
package/video-export.d.ts CHANGED
@@ -26,3 +26,9 @@ export interface VideoExportOptions {
26
26
  /** Adds `result.toMp4()`: records through MediaRecorder, with WebM fallback when needed. */
27
27
  export declare function videoExport(options?: VideoExportOptions): SnapdomPlugin;
28
28
  export default videoExport;
29
+
30
+ declare module '@zumer/snapdom' {
31
+ interface CaptureResult {
32
+ toMp4(options?: VideoExportOptions & { download?: boolean | string }): Promise<Blob>;
33
+ }
34
+ }
package/video-export.js CHANGED
@@ -47,13 +47,13 @@ export function videoExport(options = {}) {
47
47
  return {
48
48
  mp4: async (ctx, opts = {}) => {
49
49
  if (typeof MediaRecorder === 'undefined') {
50
- throw new Error('[snapdom] video-export: MediaRecorder is not available in this environment');
50
+ throw new Error('[SnapDOM] video-export: MediaRecorder is not available in this environment');
51
51
  }
52
52
  if (typeof HTMLCanvasElement.prototype.captureStream !== 'function') {
53
- throw new Error('[snapdom] video-export: canvas.captureStream is not available in this environment');
53
+ throw new Error('[SnapDOM] video-export: canvas.captureStream is not available in this environment');
54
54
  }
55
55
  const el = ctx.element;
56
- if (!el) throw new Error('[snapdom] video-export: no source element on context');
56
+ if (!el) throw new Error('[SnapDOM] video-export: there is no element to record.');
57
57
 
58
58
  const recording = frameOptions(ctx, opts, { fps, duration, frames: frameOpt, scale, background }, 'video-export');
59
59
  const { fps: _fps, count: _count, background: _bg } = recording;
@@ -70,7 +70,7 @@ export function videoExport(options = {}) {
70
70
  typeof MediaRecorder.isTypeSupported === 'function' && MediaRecorder.isTypeSupported(t)
71
71
  ) || '';
72
72
  if (mimeType && !mimeType.startsWith('video/mp4')) {
73
- console.warn(`[snapdom] video-export: MP4 not supported by this browser's MediaRecorder; falling back to ${mimeType}`);
73
+ console.warn(`[SnapDOM] video-export: MP4 not supported by this browser's MediaRecorder; falling back to ${mimeType}`);
74
74
  }
75
75
 
76
76
  // 3) Request each painted frame where supported. MediaRecorder uses a real-time
@@ -91,20 +91,20 @@ export function videoExport(options = {}) {
91
91
  try {
92
92
  stream = stage.captureStream(0);
93
93
  let track = stream.getVideoTracks()[0];
94
- if (!track) throw new Error('[snapdom] video-export: captureStream produced no video track');
94
+ if (!track) throw new Error('[SnapDOM] video-export: captureStream produced no video track');
95
95
  if (typeof track.requestFrame !== 'function') {
96
96
  // A zero-rate track without requestFrame never receives later paintings.
97
97
  for (const t of stream.getTracks()) t.stop();
98
98
  stream = stage.captureStream(_fps);
99
99
  track = stream.getVideoTracks()[0];
100
- if (!track) throw new Error('[snapdom] video-export: captureStream produced no video track');
100
+ if (!track) throw new Error('[SnapDOM] video-export: captureStream produced no video track');
101
101
  }
102
102
  const pushFrame = () => track.requestFrame?.();
103
103
  rec = new MediaRecorder(stream, recOpts);
104
104
  rec.ondataavailable = e => { if (e.data && e.data.size) chunks.push(e.data); };
105
105
  const stopped = new Promise(resolve => { rec.onstop = resolve; });
106
106
  const failed = new Promise((_, reject) => {
107
- rec.onerror = event => reject(event.error || new Error('[snapdom] video-export: MediaRecorder failed'));
107
+ rec.onerror = event => reject(event.error || new Error('[SnapDOM] video-export: MediaRecorder failed'));
108
108
  });
109
109
  // An error can arrive between awaited frame operations. Mark it handled now;
110
110
  // every wait below still observes the original rejection and exits the recorder.
package/pdf-image.d.ts DELETED
@@ -1,18 +0,0 @@
1
- /**
2
- * Type declarations for `@zumer/snapdom-plugins/pdf-image`.
3
- *
4
- * The plugin instance type comes from the core package: one definition of what a plugin IS,
5
- * so a plugin that stops matching the host's contract fails to typecheck instead of failing
6
- * at capture time.
7
- */
8
- import type { SnapdomPlugin } from '@zumer/snapdom';
9
-
10
- export interface PdfImageOptions {
11
- orientation?: 'portrait' | 'landscape';
12
- /** JPEG quality, 0-1. */
13
- quality?: number;
14
- filename?: string;
15
- }
16
-
17
- /** Adds `result.toPdfImage()`: the capture as a JPEG embedded in a downloadable PDF. */
18
- export declare function pdfImage(options?: PdfImageOptions): SnapdomPlugin;
package/pdf-image.js DELETED
@@ -1,114 +0,0 @@
1
- /**
2
- * pdfImage - Official SnapDOM Plugin
3
- * Exports the capture as a JPEG embedded in a downloadable PDF.
4
- * Pinned by __tests__/plugins.imageExports.v3.test.js.
5
- * @module plugins/pdf-image
6
- *
7
- * @param {Object} [options]
8
- * @param {string} [options.orientation='portrait'] - 'portrait' | 'landscape'
9
- * @param {number} [options.quality=0.92] - JPEG quality (0-1)
10
- * @param {string} [options.filename='capture.pdf'] - Download filename
11
- * @returns {Object} SnapDOM plugin
12
- */
13
- export function pdfImage(options = {}) {
14
- const {
15
- orientation = 'portrait',
16
- quality = 0.92,
17
- filename = 'capture.pdf',
18
- } = options;
19
-
20
- return {
21
- name: 'pdf-image',
22
-
23
- defineExports({ exports }) {
24
- return {
25
- pdfImage: async (_ctx, opts) => {
26
- // Core owns sizing, captured originals and Safari's image/font paint workarounds.
27
- const canvas = await exports.canvas({
28
- ...opts, canvas: null,
29
- backgroundColor: !opts.backgroundColor || opts.backgroundColor === 'transparent' ? '#ffffff' : opts.backgroundColor,
30
- });
31
-
32
- const isLandscape = orientation === 'landscape';
33
- const pageW = isLandscape ? 841.89 : 595.28; // A4 in points
34
- const pageH = isLandscape ? 595.28 : 841.89;
35
- const margin = 40;
36
- const maxW = pageW - margin * 2;
37
- const maxH = pageH - margin * 2;
38
- const scale = Math.min(maxW / canvas.width, maxH / canvas.height, 1);
39
- const w = canvas.width * scale;
40
- const h = canvas.height * scale;
41
- const x = (pageW - w) / 2;
42
- const y = (pageH - h) / 2;
43
-
44
- // Get image as JPEG data
45
- const jpegDataUrl = canvas.toDataURL('image/jpeg', quality);
46
- const jpegBase64 = jpegDataUrl.split(',')[1];
47
- const jpegBytes = atob(jpegBase64);
48
- const jpegLen = jpegBytes.length;
49
-
50
- const enc = new TextEncoder();
51
- const realOffsets = [];
52
-
53
- // Obj 1-3
54
- let textPart = '%PDF-1.4\n';
55
- realOffsets.push(textPart.length);
56
- textPart += '1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n';
57
- realOffsets.push(textPart.length);
58
- textPart += '2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n';
59
- realOffsets.push(textPart.length);
60
- textPart += `3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${pageW} ${pageH}] /Contents 5 0 R /Resources << /XObject << /Img 4 0 R >> >> >>\nendobj\n`;
61
-
62
- // Obj 4 — image with binary JPEG stream
63
- realOffsets.push(textPart.length);
64
- const imgHeader = `4 0 obj\n<< /Type /XObject /Subtype /Image /Width ${canvas.width} /Height ${canvas.height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${jpegLen} >>\nstream\n`;
65
-
66
- const imgHeaderBytes = enc.encode(imgHeader);
67
- const preImgBytes = enc.encode(textPart);
68
-
69
- const jpegBuf = new Uint8Array(jpegLen);
70
- for (let i = 0; i < jpegLen; i++) jpegBuf[i] = jpegBytes.charCodeAt(i);
71
-
72
- const afterImg = '\nendstream\nendobj\n';
73
- const afterImgBytes = enc.encode(afterImg);
74
-
75
- // Obj 5 — content stream
76
- const contentStr = `q ${w.toFixed(2)} 0 0 ${h.toFixed(2)} ${x.toFixed(2)} ${y.toFixed(2)} cm /Img Do Q`;
77
- const obj5 = `5 0 obj\n<< /Length ${contentStr.length} >>\nstream\n${contentStr}\nendstream\nendobj\n`;
78
- const obj5Bytes = enc.encode(obj5);
79
-
80
- realOffsets.push(preImgBytes.length + imgHeaderBytes.length + jpegLen + afterImgBytes.length);
81
-
82
- // xref table
83
- const xrefOffset = preImgBytes.length + imgHeaderBytes.length + jpegLen + afterImgBytes.length + obj5Bytes.length;
84
- let xref = 'xref\n0 6\n0000000000 65535 f \n';
85
- for (let i = 0; i < 5; i++) {
86
- xref += `${String(realOffsets[i]).padStart(10, '0')} 00000 n \n`;
87
- }
88
- xref += `trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF`;
89
- const xrefBytes = enc.encode(xref);
90
-
91
- // Combine all parts
92
- const totalLen = preImgBytes.length + imgHeaderBytes.length + jpegLen + afterImgBytes.length + obj5Bytes.length + xrefBytes.length;
93
- const pdfBuf = new Uint8Array(totalLen);
94
- let pos = 0;
95
- pdfBuf.set(preImgBytes, pos); pos += preImgBytes.length;
96
- pdfBuf.set(imgHeaderBytes, pos); pos += imgHeaderBytes.length;
97
- pdfBuf.set(jpegBuf, pos); pos += jpegLen;
98
- pdfBuf.set(afterImgBytes, pos); pos += afterImgBytes.length;
99
- pdfBuf.set(obj5Bytes, pos); pos += obj5Bytes.length;
100
- pdfBuf.set(xrefBytes, pos);
101
-
102
- const blob = new Blob([pdfBuf], { type: 'application/pdf' });
103
- const url = URL.createObjectURL(blob);
104
- const a = document.createElement('a');
105
- a.href = url;
106
- a.download = filename;
107
- a.click();
108
- setTimeout(() => URL.revokeObjectURL(url), 5000);
109
- return url;
110
- }
111
- };
112
- }
113
- };
114
- }