@formepdf/renderer 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/preview/index.html +93 -55
- package/dist/resolve.d.ts +1 -1
- package/dist/resolve.js +30 -9
- package/package.json +3 -3
package/dist/preview/index.html
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
--accent-hover: #2563eb;
|
|
20
20
|
--inspector-width: 320px;
|
|
21
21
|
--left-sidebar-width: 280px;
|
|
22
|
-
--toolbar-height:
|
|
22
|
+
--toolbar-height: 78px;
|
|
23
23
|
--font-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', 'JetBrains Mono', monospace;
|
|
24
24
|
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
25
25
|
}
|
|
@@ -44,12 +44,21 @@
|
|
|
44
44
|
height: var(--toolbar-height);
|
|
45
45
|
background: var(--surface);
|
|
46
46
|
border-bottom: 1px solid var(--border);
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-direction: column;
|
|
49
|
+
font-size: 13px;
|
|
50
|
+
-webkit-app-region: drag;
|
|
51
|
+
}
|
|
52
|
+
.toolbar-row {
|
|
47
53
|
display: flex;
|
|
48
54
|
align-items: center;
|
|
49
55
|
padding: 0 12px;
|
|
50
56
|
gap: 12px;
|
|
51
|
-
|
|
52
|
-
-
|
|
57
|
+
height: 39px;
|
|
58
|
+
flex-shrink: 0;
|
|
59
|
+
}
|
|
60
|
+
.toolbar-row:first-child {
|
|
61
|
+
border-bottom: 1px solid var(--border);
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
.toolbar-group {
|
|
@@ -785,70 +794,77 @@
|
|
|
785
794
|
<body>
|
|
786
795
|
|
|
787
796
|
<div id="toolbar">
|
|
788
|
-
<div class="toolbar-
|
|
789
|
-
<div class="
|
|
790
|
-
|
|
791
|
-
|
|
797
|
+
<div class="toolbar-row">
|
|
798
|
+
<div class="toolbar-group">
|
|
799
|
+
<div class="status-dot" id="status-dot"></div>
|
|
800
|
+
<div class="wordmark">forme <span>preview</span></div>
|
|
801
|
+
</div>
|
|
792
802
|
|
|
793
|
-
|
|
803
|
+
<div class="toolbar-separator"></div>
|
|
794
804
|
|
|
795
|
-
|
|
805
|
+
<button class="toolbar-btn" id="tree-toggle" title="Toggle component tree (T)">Tree</button>
|
|
796
806
|
|
|
797
|
-
|
|
807
|
+
<div class="toolbar-separator"></div>
|
|
798
808
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
809
|
+
<div class="segmented-control" id="mode-control">
|
|
810
|
+
<button data-mode="preview" class="active">Preview <span class="shortcut">1</span></button>
|
|
811
|
+
<button data-mode="layout">Layout <span class="shortcut">2</span></button>
|
|
812
|
+
<button data-mode="margins">Margins <span class="shortcut">3</span></button>
|
|
813
|
+
<button data-mode="breaks">Breaks <span class="shortcut">4</span></button>
|
|
814
|
+
</div>
|
|
805
815
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
<option value="tabloid">Tabloid (792 x 1224)</option>
|
|
815
|
-
<option value="a3">A3 (842 x 1191)</option>
|
|
816
|
-
<option value="a5">A5 (420 x 595)</option>
|
|
817
|
-
<option value="custom">Custom...</option>
|
|
818
|
-
</select>
|
|
819
|
-
<div class="custom-size-inputs" id="custom-size-inputs">
|
|
820
|
-
<input type="number" id="custom-width" placeholder="W" value="612" min="72" max="4000">
|
|
821
|
-
<span>x</span>
|
|
822
|
-
<input type="number" id="custom-height" placeholder="H" value="792" min="72" max="4000">
|
|
823
|
-
<span>pt</span>
|
|
816
|
+
<div class="toolbar-spacer"></div>
|
|
817
|
+
|
|
818
|
+
<div class="toolbar-group">
|
|
819
|
+
<select id="editor-select" title="Editor for Open in Editor">
|
|
820
|
+
<option value="vscode">VS Code</option>
|
|
821
|
+
<option value="cursor">Cursor</option>
|
|
822
|
+
<option value="webstorm">WebStorm</option>
|
|
823
|
+
</select>
|
|
824
824
|
</div>
|
|
825
825
|
</div>
|
|
826
826
|
|
|
827
|
-
<div class="toolbar-
|
|
827
|
+
<div class="toolbar-row">
|
|
828
|
+
<div class="toolbar-group">
|
|
829
|
+
<select id="page-size-select" title="Page size override">
|
|
830
|
+
<option value="default">Default</option>
|
|
831
|
+
<option value="letter">Letter (612 x 792)</option>
|
|
832
|
+
<option value="a4">A4 (595 x 842)</option>
|
|
833
|
+
<option value="legal">Legal (612 x 1008)</option>
|
|
834
|
+
<option value="tabloid">Tabloid (792 x 1224)</option>
|
|
835
|
+
<option value="a3">A3 (842 x 1191)</option>
|
|
836
|
+
<option value="a5">A5 (420 x 595)</option>
|
|
837
|
+
<option value="custom">Custom...</option>
|
|
838
|
+
</select>
|
|
839
|
+
<div class="custom-size-inputs" id="custom-size-inputs">
|
|
840
|
+
<input type="number" id="custom-width" placeholder="W" value="612" min="72" max="4000">
|
|
841
|
+
<span>x</span>
|
|
842
|
+
<input type="number" id="custom-height" placeholder="H" value="792" min="72" max="4000">
|
|
843
|
+
<span>pt</span>
|
|
844
|
+
</div>
|
|
845
|
+
</div>
|
|
828
846
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
847
|
+
<div class="toolbar-separator"></div>
|
|
848
|
+
|
|
849
|
+
<div class="badge render-time" id="render-badge" style="display:none">
|
|
850
|
+
<span id="render-time">0ms</span>
|
|
851
|
+
</div>
|
|
852
|
+
<div class="badge page-count" id="page-badge" style="display:none">
|
|
853
|
+
<span id="page-count">0</span> pages
|
|
854
|
+
</div>
|
|
835
855
|
|
|
836
|
-
|
|
856
|
+
<div class="toolbar-separator"></div>
|
|
837
857
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
858
|
+
<div class="zoom-controls">
|
|
859
|
+
<button id="zoom-out" title="Zoom out (Cmd -)">−</button>
|
|
860
|
+
<span class="zoom-level" id="zoom-level">100%</span>
|
|
861
|
+
<button id="zoom-in" title="Zoom in (Cmd +)">+</button>
|
|
862
|
+
<button id="zoom-fit" title="Fit to window (Cmd 0)" style="font-size:11px; width:auto; padding: 0 8px;">Fit</button>
|
|
863
|
+
</div>
|
|
844
864
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
<
|
|
848
|
-
<option value="vscode">VS Code</option>
|
|
849
|
-
<option value="cursor">Cursor</option>
|
|
850
|
-
<option value="webstorm">WebStorm</option>
|
|
851
|
-
</select>
|
|
865
|
+
<div class="toolbar-spacer"></div>
|
|
866
|
+
|
|
867
|
+
<button class="toolbar-btn" id="download-btn" title="Download PDF" style="display:none">⤓ PDF</button>
|
|
852
868
|
</div>
|
|
853
869
|
</div>
|
|
854
870
|
|
|
@@ -1900,6 +1916,26 @@
|
|
|
1900
1916
|
return { element: best, ancestors: bestAncestorNames, ancestorElements: bestAncestorElements };
|
|
1901
1917
|
}
|
|
1902
1918
|
|
|
1919
|
+
// -- Download PDF -------------------------------------------------
|
|
1920
|
+
let lastPdfBase64 = null;
|
|
1921
|
+
const downloadBtn = document.getElementById('download-btn');
|
|
1922
|
+
if (downloadBtn) {
|
|
1923
|
+
downloadBtn.addEventListener('click', function() {
|
|
1924
|
+
if (!lastPdfBase64) return;
|
|
1925
|
+
if (isVSCode) {
|
|
1926
|
+
vscode.postMessage({ type: 'downloadPdf' });
|
|
1927
|
+
} else {
|
|
1928
|
+
const bytes = Uint8Array.from(atob(lastPdfBase64), c => c.charCodeAt(0));
|
|
1929
|
+
const blob = new Blob([bytes], { type: 'application/pdf' });
|
|
1930
|
+
const a = document.createElement('a');
|
|
1931
|
+
a.href = URL.createObjectURL(blob);
|
|
1932
|
+
a.download = 'document.pdf';
|
|
1933
|
+
a.click();
|
|
1934
|
+
URL.revokeObjectURL(a.href);
|
|
1935
|
+
}
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1903
1939
|
// -- Render PDF pages --------------------------------------------
|
|
1904
1940
|
let currentPdfDoc = null;
|
|
1905
1941
|
|
|
@@ -2429,6 +2465,8 @@
|
|
|
2429
2465
|
if (!msg || !msg.type) return;
|
|
2430
2466
|
|
|
2431
2467
|
if (msg.type === 'pdfData') {
|
|
2468
|
+
lastPdfBase64 = msg.pdf;
|
|
2469
|
+
if (downloadBtn) downloadBtn.style.display = '';
|
|
2432
2470
|
const bytes = Uint8Array.from(atob(msg.pdf), c => c.charCodeAt(0));
|
|
2433
2471
|
if (msg.renderTime) {
|
|
2434
2472
|
renderTimeEl.textContent = msg.renderTime + 'ms';
|
package/dist/resolve.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare function uint8ArrayToBase64(bytes: Uint8Array): string;
|
|
2
2
|
export declare function resolveFontSources(doc: Record<string, unknown>, basePath?: string): Promise<void>;
|
|
3
|
-
export declare function resolveImageSources(doc: Record<string, unknown
|
|
3
|
+
export declare function resolveImageSources(doc: Record<string, unknown>, basePath?: string): Promise<void>;
|
|
4
4
|
export declare function resolveAllSources(doc: Record<string, unknown>, basePath?: string): Promise<void>;
|
package/dist/resolve.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
2
|
+
import { resolve, extname } from 'node:path';
|
|
3
3
|
export function uint8ArrayToBase64(bytes) {
|
|
4
4
|
return Buffer.from(bytes).toString('base64');
|
|
5
5
|
}
|
|
@@ -22,19 +22,33 @@ export async function resolveFontSources(doc, basePath) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const MIME_BY_EXT = {
|
|
26
|
+
'.png': 'image/png',
|
|
27
|
+
'.jpg': 'image/jpeg',
|
|
28
|
+
'.jpeg': 'image/jpeg',
|
|
29
|
+
'.gif': 'image/gif',
|
|
30
|
+
'.webp': 'image/webp',
|
|
31
|
+
'.svg': 'image/svg+xml',
|
|
32
|
+
'.bmp': 'image/bmp',
|
|
33
|
+
'.ico': 'image/x-icon',
|
|
34
|
+
'.avif': 'image/avif',
|
|
35
|
+
};
|
|
36
|
+
/// Resolve image sources — converts HTTP/HTTPS URLs and local file paths to base64 data URIs.
|
|
37
|
+
/// Walks the document tree recursively. File paths are resolved relative to `basePath`.
|
|
38
|
+
export async function resolveImageSources(doc, basePath) {
|
|
28
39
|
const children = doc.children;
|
|
29
40
|
if (!children?.length)
|
|
30
41
|
return;
|
|
31
|
-
await Promise.all(children.map(resolveImageSourcesInNode));
|
|
42
|
+
await Promise.all(children.map((n) => resolveImageSourcesInNode(n, basePath)));
|
|
32
43
|
}
|
|
33
|
-
async function resolveImageSourcesInNode(node) {
|
|
44
|
+
async function resolveImageSourcesInNode(node, basePath) {
|
|
34
45
|
const kind = node.kind;
|
|
35
46
|
if (kind?.type === 'Image' && typeof kind.src === 'string') {
|
|
36
47
|
const src = kind.src;
|
|
37
|
-
if (src.startsWith('
|
|
48
|
+
if (src.startsWith('data:')) {
|
|
49
|
+
// Already a data URI — pass through
|
|
50
|
+
}
|
|
51
|
+
else if (src.startsWith('http://') || src.startsWith('https://')) {
|
|
38
52
|
const res = await fetch(src);
|
|
39
53
|
if (!res.ok)
|
|
40
54
|
throw new Error(`Failed to fetch image: ${src} (${res.status})`);
|
|
@@ -42,16 +56,23 @@ async function resolveImageSourcesInNode(node) {
|
|
|
42
56
|
const buf = new Uint8Array(await res.arrayBuffer());
|
|
43
57
|
kind.src = `data:${contentType};base64,${uint8ArrayToBase64(buf)}`;
|
|
44
58
|
}
|
|
59
|
+
else if (basePath) {
|
|
60
|
+
const filePath = resolve(basePath, src);
|
|
61
|
+
const ext = extname(filePath).toLowerCase();
|
|
62
|
+
const mime = MIME_BY_EXT[ext] || 'application/octet-stream';
|
|
63
|
+
const bytes = await readFile(filePath);
|
|
64
|
+
kind.src = `data:${mime};base64,${uint8ArrayToBase64(new Uint8Array(bytes))}`;
|
|
65
|
+
}
|
|
45
66
|
}
|
|
46
67
|
const children = node.children;
|
|
47
68
|
if (children?.length) {
|
|
48
|
-
await Promise.all(children.map(resolveImageSourcesInNode));
|
|
69
|
+
await Promise.all(children.map((n) => resolveImageSourcesInNode(n, basePath)));
|
|
49
70
|
}
|
|
50
71
|
}
|
|
51
72
|
/// Resolve all asset sources (fonts + images) in parallel.
|
|
52
73
|
export async function resolveAllSources(doc, basePath) {
|
|
53
74
|
await Promise.all([
|
|
54
75
|
resolveFontSources(doc, basePath),
|
|
55
|
-
resolveImageSources(doc),
|
|
76
|
+
resolveImageSources(doc, basePath),
|
|
56
77
|
]);
|
|
57
78
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formepdf/renderer",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "File-to-PDF rendering pipeline for Forme — bundles TSX, resolves assets, renders via WASM",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"test": "vitest run"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@formepdf/core": "0.7.
|
|
21
|
-
"@formepdf/react": "0.7.
|
|
20
|
+
"@formepdf/core": "0.7.3",
|
|
21
|
+
"@formepdf/react": "0.7.3",
|
|
22
22
|
"esbuild": "^0.24.0"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|