Skip to content

Commit 24db512

Browse files
committed
Add advanced docs examples for PDF fonts and multi-writer exports
1 parent 83ed840 commit 24db512

5 files changed

Lines changed: 358 additions & 0 deletions

File tree

docs/examples/cad-emf-export.html

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Example: CAD + EMF Export Bundle</title>
7+
<link rel="preconnect" href="https://fonts.googleapis.com" />
8+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
10+
<style>
11+
:root { --bg: #f5f1e8; --panel: #fffefb; --ink: #1a201c; --muted: #4a5550; --line: #d7cfbf; --accent: #8f462d; }
12+
* { box-sizing: border-box; }
13+
body { margin: 0; font-family: "Space Grotesk", "Segoe UI", sans-serif; color: var(--ink); background: radial-gradient(circle at 10% 0, #e7dcc2 0, transparent 28%), var(--bg); }
14+
.wrap { width: min(980px, 92vw); margin: 0 auto; padding: 34px 0 42px; }
15+
.crumb { color: var(--muted); text-decoration: none; }
16+
h1 { margin: 10px 0 8px; font-size: clamp(1.8rem, 4vw, 2.8rem); letter-spacing: -0.02em; }
17+
p { color: var(--muted); }
18+
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; padding: 18px; margin-top: 16px; }
19+
.code { font-family: "IBM Plex Mono", Consolas, monospace; font-size: 0.9rem; white-space: pre; overflow-x: auto; background: #131915; color: #eaf2ee; border-radius: 10px; padding: 14px; border: 1px solid #25352d; }
20+
</style>
21+
</head>
22+
<body>
23+
<div class="wrap">
24+
<a class="crumb" href="../index.html">Back to homepage</a>
25+
<h1>CAD + EMF Export in One Pipeline</h1>
26+
<p>Generate DWG/DXF for CAD consumers and EMF/EMF+ for Windows-native vector interchange from a single IR extraction.</p>
27+
28+
<section class="panel">
29+
<h2>Code</h2>
30+
<div class="code">import {
31+
extractIR,
32+
renderIR,
33+
DWGWriter,
34+
AcadDXFWriter,
35+
EMFWriter,
36+
EMFPlusWriter
37+
} from "@node-projects/layout2vector";
38+
39+
async function exportCadAndMetafile(root) {
40+
const bounds = root.getBoundingClientRect();
41+
const maxY = Math.ceil(bounds.height);
42+
43+
const ir = await extractIR(root, {
44+
includeText: true,
45+
includeImages: true,
46+
includePseudoElements: true,
47+
svgToVector: true
48+
});
49+
50+
const dwgBytes = await renderIR(ir, new DWGWriter({ maxY }));
51+
const dxfBytes = await renderIR(ir, new AcadDXFWriter({ maxY }));
52+
const emfBytes = await renderIR(ir, new EMFWriter({
53+
width: bounds.width,
54+
height: bounds.height
55+
}));
56+
const emfPlusBytes = await renderIR(ir, new EMFPlusWriter({
57+
width: bounds.width,
58+
height: bounds.height
59+
}));
60+
61+
return { dwgBytes, dxfBytes, emfBytes, emfPlusBytes };
62+
}</div>
63+
</section>
64+
65+
<section class="panel">
66+
<h2>Notes</h2>
67+
<p>If exact browser webfont appearance is required for CAD/EMF-family outputs, pre-process IR text nodes with rasterizeFontTextNodes() and use the rasterized IR for those writers.</p>
68+
</section>
69+
</div>
70+
</body>
71+
</html>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Example: Webfont Fallback for CAD Writers</title>
7+
<link rel="preconnect" href="https://fonts.googleapis.com" />
8+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
10+
<style>
11+
:root { --bg: #f3f0e6; --panel: #fffefb; --ink: #17211b; --muted: #4a564e; --line: #d9d0bf; }
12+
* { box-sizing: border-box; }
13+
body { margin: 0; font-family: "Space Grotesk", "Segoe UI", sans-serif; color: var(--ink); background: linear-gradient(180deg, #f8f5ed 0, var(--bg) 100%); }
14+
.wrap { width: min(980px, 92vw); margin: 0 auto; padding: 34px 0 42px; }
15+
.crumb { color: var(--muted); text-decoration: none; }
16+
h1 { margin: 10px 0 8px; font-size: clamp(1.8rem, 4vw, 2.8rem); letter-spacing: -0.02em; }
17+
p { color: var(--muted); }
18+
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; padding: 18px; margin-top: 16px; }
19+
.code { font-family: "IBM Plex Mono", Consolas, monospace; font-size: 0.9rem; white-space: pre; overflow-x: auto; background: #111916; color: #e6efea; border-radius: 10px; padding: 14px; border: 1px solid #21312a; }
20+
</style>
21+
</head>
22+
<body>
23+
<div class="wrap">
24+
<a class="crumb" href="../index.html">Back to homepage</a>
25+
<h1>Font Fidelity for CAD and EMF Writers</h1>
26+
<p>CAD and EMF-family formats cannot embed browser webfonts directly. Use rasterizeFontTextNodes() to preserve exact text appearance for those targets.</p>
27+
28+
<section class="panel">
29+
<h2>Code</h2>
30+
<div class="code">import {
31+
extractIRWithAssets,
32+
rasterizeFontTextNodes,
33+
renderIR,
34+
DWGWriter,
35+
EMFWriter
36+
} from "@node-projects/layout2vector";
37+
38+
async function exportCadWithFontFidelity(root) {
39+
const bounds = root.getBoundingClientRect();
40+
41+
const { ir, fontAssets } = await extractIRWithAssets(root, {
42+
includeFonts: true,
43+
includeText: true,
44+
includeImages: true
45+
});
46+
47+
const fallbackIr = await rasterizeFontTextNodes(ir, fontAssets, {
48+
scale: 2
49+
});
50+
51+
const dwgBytes = await renderIR(fallbackIr, new DWGWriter({
52+
maxY: Math.ceil(bounds.height)
53+
}));
54+
55+
const emfBytes = await renderIR(fallbackIr, new EMFWriter({
56+
width: bounds.width,
57+
height: bounds.height
58+
}));
59+
60+
return { dwgBytes, emfBytes };
61+
}</div>
62+
</section>
63+
</div>
64+
</body>
65+
</html>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Example: One IR, Multiple Writers</title>
7+
<link rel="preconnect" href="https://fonts.googleapis.com" />
8+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
10+
<style>
11+
:root { --bg: #f4efe3; --panel: #fffef9; --ink: #1a2218; --muted: #4f594f; --line: #dad0b9; --accent: #2f6b53; }
12+
* { box-sizing: border-box; }
13+
body { margin: 0; font-family: "Space Grotesk", "Segoe UI", sans-serif; color: var(--ink); background: linear-gradient(180deg, var(--bg), #ece5d3); }
14+
.wrap { width: min(980px, 92vw); margin: 0 auto; padding: 34px 0 42px; }
15+
.crumb { color: var(--muted); text-decoration: none; }
16+
h1 { margin: 10px 0 8px; font-size: clamp(1.8rem, 4vw, 2.8rem); letter-spacing: -0.02em; }
17+
p { color: var(--muted); }
18+
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; padding: 18px; margin-top: 16px; }
19+
.code { font-family: "IBM Plex Mono", Consolas, monospace; font-size: 0.9rem; white-space: pre; overflow-x: auto; background: #111815; color: #e5eeea; border-radius: 10px; padding: 14px; border: 1px solid #203129; }
20+
.tag { display: inline-block; border: 1px solid var(--line); border-radius: 999px; padding: 4px 10px; margin-right: 8px; margin-bottom: 8px; font-size: 0.86rem; }
21+
</style>
22+
</head>
23+
<body>
24+
<div class="wrap">
25+
<a class="crumb" href="../index.html">Back to homepage</a>
26+
<h1>One Extraction, Multiple Output Writers</h1>
27+
<p>Extract once, then fan out to several output formats. This keeps geometry and paint-order consistent across artifacts generated in a single run.</p>
28+
29+
<section class="panel">
30+
<span class="tag">DXF</span>
31+
<span class="tag">SVG</span>
32+
<span class="tag">HTML</span>
33+
<span class="tag">PNG</span>
34+
<h2>Code</h2>
35+
<div class="code">import {
36+
extractIR,
37+
renderIR,
38+
DXFWriter,
39+
SVGWriter,
40+
HTMLWriter,
41+
ImageWriter
42+
} from "@node-projects/layout2vector";
43+
44+
async function exportAll(root) {
45+
const bounds = root.getBoundingClientRect();
46+
47+
const ir = await extractIR(root, {
48+
includeText: true,
49+
includeImages: true,
50+
convertFormControls: true,
51+
includePseudoElements: true
52+
});
53+
54+
const dxf = await renderIR(ir, new DXFWriter({ maxY: Math.ceil(bounds.height) }));
55+
const svg = await renderIR(ir, new SVGWriter({ width: bounds.width, height: bounds.height }));
56+
const html = await renderIR(ir, new HTMLWriter({ width: bounds.width, height: bounds.height }));
57+
58+
const image = await renderIR(ir, new ImageWriter({
59+
width: bounds.width,
60+
height: bounds.height,
61+
scale: 2
62+
}));
63+
await image.finalize();
64+
const pngBytes = image.toBytes();
65+
66+
return { dxf, svg, html, pngBytes };
67+
}</div>
68+
</section>
69+
70+
<section class="panel">
71+
<h2>When this helps</h2>
72+
<p>Use this for workflows where engineering receives CAD output, product receives PDF/SVG previews, and support receives PNG snapshots from the same source DOM.</p>
73+
</section>
74+
</div>
75+
</body>
76+
</html>
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Example: Complex PDF with Embedded WOFF/WOFF2 Fonts</title>
7+
<link rel="preconnect" href="https://fonts.googleapis.com" />
8+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
10+
<style>
11+
:root {
12+
--bg: #f6f2e8;
13+
--panel: #fffef9;
14+
--ink: #132218;
15+
--muted: #4b5a4f;
16+
--line: #d8cfba;
17+
--accent: #9f3f1f;
18+
}
19+
* { box-sizing: border-box; }
20+
body {
21+
margin: 0;
22+
font-family: "Space Grotesk", "Segoe UI", sans-serif;
23+
color: var(--ink);
24+
background: radial-gradient(circle at 90% -10%, #d9e8de 0, transparent 40%), var(--bg);
25+
line-height: 1.6;
26+
}
27+
.wrap { width: min(980px, 92vw); margin: 0 auto; padding: 34px 0 42px; }
28+
.crumb { color: var(--muted); text-decoration: none; font-size: 0.95rem; }
29+
h1 { font-size: clamp(1.8rem, 4vw, 2.8rem); margin: 10px 0 8px; letter-spacing: -0.02em; }
30+
p { color: var(--muted); }
31+
.panel {
32+
background: var(--panel);
33+
border: 1px solid var(--line);
34+
border-radius: 14px;
35+
padding: 18px;
36+
margin-top: 18px;
37+
}
38+
.code {
39+
font-family: "IBM Plex Mono", Consolas, monospace;
40+
font-size: 0.9rem;
41+
white-space: pre;
42+
overflow-x: auto;
43+
background: #121916;
44+
color: #e8efea;
45+
border-radius: 10px;
46+
padding: 14px;
47+
border: 1px solid #25342d;
48+
}
49+
.note {
50+
border-left: 4px solid var(--accent);
51+
padding-left: 12px;
52+
margin-top: 14px;
53+
color: var(--ink);
54+
}
55+
ul { margin: 8px 0; }
56+
</style>
57+
</head>
58+
<body>
59+
<div class="wrap">
60+
<a class="crumb" href="../index.html">Back to homepage</a>
61+
<h1>Complex PDF Export with Embedded WOFF/WOFF2 Fonts</h1>
62+
<p>This pattern matches the browser addon flow: collect used webfont assets from the live DOM, convert non-TTF font files through fonteditor-core, and embed the result directly into the PDF.</p>
63+
64+
<section class="panel">
65+
<h2>Install</h2>
66+
<div class="code">npm install @node-projects/layout2vector fonteditor-core</div>
67+
</section>
68+
69+
<section class="panel">
70+
<h2>Code</h2>
71+
<div class="code">import {
72+
extractIRWithAssets,
73+
renderIR,
74+
PDFWriter
75+
} from "@node-projects/layout2vector";
76+
77+
async function exportInvoicePdf(root) {
78+
// includeFonts gathers the @font-face files used by extracted text.
79+
const { ir, fontAssets } = await extractIRWithAssets(root, {
80+
includeText: true,
81+
includeImages: true,
82+
includeFonts: true,
83+
convertFormControls: true,
84+
walkIframes: true
85+
});
86+
87+
const writer = new PDFWriter({
88+
pageWidth: 210,
89+
pageHeight: 297,
90+
fontAssets,
91+
// Enables WOFF/WOFF2/OTF -> TTF conversion for embedding.
92+
useFontEditorCore: true
93+
});
94+
95+
const pdfDoc = await renderIR(ir, writer);
96+
await pdfDoc.finalize();
97+
const pdfBytes = pdfDoc.toBytes();
98+
99+
return pdfBytes;
100+
}
101+
102+
function downloadPdf(bytes, filename = "layout-export.pdf") {
103+
const blob = new Blob([bytes], { type: "application/pdf" });
104+
const url = URL.createObjectURL(blob);
105+
const a = document.createElement("a");
106+
a.href = url;
107+
a.download = filename;
108+
a.click();
109+
URL.revokeObjectURL(url);
110+
}</div>
111+
<p class="note">Important: without <strong>useFontEditorCore: true</strong>, the PDF writer only embeds TTF sources and ignores WOFF/WOFF2/OTF sources.</p>
112+
</section>
113+
114+
<section class="panel">
115+
<h2>Production notes</h2>
116+
<ul>
117+
<li>Keep includeFonts enabled only when you need webfont fidelity to reduce network work.</li>
118+
<li>If the source app uses dynamic @font-face rules, wait for document fonts to load before calling extractIRWithAssets().</li>
119+
<li>This same approach works in browser extensions where fonts are loaded from page styles.</li>
120+
</ul>
121+
</section>
122+
</div>
123+
</body>
124+
</html>

docs/index.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,28 @@ <h2>Quick code sample</h2>
306306
const pdfBytes = pdfDoc.toBytes();</div>
307307
</section>
308308

309+
<section>
310+
<h2>Advanced example pages</h2>
311+
<div class="grid">
312+
<article class="card">
313+
<strong>Complex PDF with embedded WOFF/WOFF2 fonts</strong>
314+
Uses <code>extractIRWithAssets()</code> and <code>PDFWriter({ useFontEditorCore: true })</code> so non-TTF webfonts are converted and embedded in the exported PDF.
315+
<p><a class="button" href="examples/pdf-embedded-fonts.html">Open example</a></p>
316+
</article>
317+
<article class="card">
318+
<strong>One extraction to multiple writers</strong>
319+
Produces DXF, SVG, HTML, and PNG outputs from one IR extraction pass to keep all artifacts in sync.
320+
<p><a class="button" href="examples/multi-writer-export.html">Open example</a></p>
321+
</article>
322+
<article class="card">
323+
<strong>CAD + EMF export bundle</strong>
324+
Generates DWG, Acad DXF, EMF, and EMF+ together for mixed CAD and Windows vector workflows.
325+
<p><a class="button" href="examples/cad-emf-export.html">Open example</a></p>
326+
</article>
327+
</div>
328+
<p style="margin-top: 14px; color: var(--ink-muted);">Need browser-webfont fidelity for CAD/EMF? See <a href="examples/font-fallback-cad.html">the font fallback example</a>.</p>
329+
</section>
330+
309331
<section>
310332
<h2>Advantages over screenshot-focused libraries</h2>
311333
<table>

0 commit comments

Comments
 (0)