@flowselections/layout-maker 1.3.3 → 1.6.5
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/README.md +24 -24
- package/dist-lib/components/AfdrukKnop.d.ts +3 -1
- package/dist-lib/components/AfdrukKnop.d.ts.map +1 -1
- package/dist-lib/components/AfdrukKnop.js +24 -3
- package/dist-lib/components/ComputersLijst.d.ts +23 -0
- package/dist-lib/components/ComputersLijst.d.ts.map +1 -0
- package/dist-lib/components/ComputersLijst.js +75 -0
- package/dist-lib/components/LayoutsPage.d.ts.map +1 -1
- package/dist-lib/components/LayoutsPage.js +2 -0
- package/dist-lib/components/LogosPage.d.ts.map +1 -1
- package/dist-lib/components/LogosPage.js +2 -0
- package/dist-lib/components/NetwerkPrintersDialog.d.ts +8 -0
- package/dist-lib/components/NetwerkPrintersDialog.d.ts.map +1 -0
- package/dist-lib/components/NetwerkPrintersDialog.js +79 -0
- package/dist-lib/components/PrintProgrammaUitleg.d.ts +5 -0
- package/dist-lib/components/PrintProgrammaUitleg.d.ts.map +1 -0
- package/dist-lib/components/PrintProgrammaUitleg.js +19 -0
- package/dist-lib/components/PrintQueuePage.d.ts.map +1 -1
- package/dist-lib/components/PrintQueuePage.js +37 -3
- package/dist-lib/components/PrinterKaart.d.ts +17 -0
- package/dist-lib/components/PrinterKaart.d.ts.map +1 -0
- package/dist-lib/components/PrinterKaart.js +14 -0
- package/dist-lib/components/PrinterSetupWizard.d.ts +11 -1
- package/dist-lib/components/PrinterSetupWizard.d.ts.map +1 -1
- package/dist-lib/components/PrinterSetupWizard.js +134 -64
- package/dist-lib/components/PrinterStatsKaarten.d.ts +11 -0
- package/dist-lib/components/PrinterStatsKaarten.d.ts.map +1 -0
- package/dist-lib/components/PrinterStatsKaarten.js +8 -0
- package/dist-lib/components/PrintersPage.d.ts.map +1 -1
- package/dist-lib/components/PrintersPage.js +120 -49
- package/dist-lib/components/WerkplekConfiguratiePage.d.ts +2 -0
- package/dist-lib/components/WerkplekConfiguratiePage.d.ts.map +1 -0
- package/dist-lib/components/WerkplekConfiguratiePage.js +101 -0
- package/dist-lib/components/WerkplekkenTabel.d.ts +14 -0
- package/dist-lib/components/WerkplekkenTabel.d.ts.map +1 -0
- package/dist-lib/components/WerkplekkenTabel.js +16 -0
- package/dist-lib/index.d.ts +11 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +11 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +925 -34
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +925 -34
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +920 -29
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +12 -1
- package/dist-lib/lib/aiLayout.functions.d.ts +925 -34
- package/dist-lib/lib/aiLayout.functions.d.ts.map +1 -1
- package/dist-lib/lib/aiLayout.server.js +9 -9
- package/dist-lib/lib/browserPrint.d.ts +1 -0
- package/dist-lib/lib/browserPrint.d.ts.map +1 -1
- package/dist-lib/lib/browserPrint.js +6 -6
- package/dist-lib/lib/centralAuth.server.d.ts +13 -0
- package/dist-lib/lib/centralAuth.server.d.ts.map +1 -0
- package/dist-lib/lib/centralAuth.server.js +60 -0
- package/dist-lib/lib/directPrint.d.ts +26 -0
- package/dist-lib/lib/directPrint.d.ts.map +1 -0
- package/dist-lib/lib/directPrint.js +65 -0
- package/dist-lib/lib/documentPdf.d.ts +11 -0
- package/dist-lib/lib/documentPdf.d.ts.map +1 -0
- package/dist-lib/lib/documentPdf.js +62 -0
- package/dist-lib/lib/htmlNaarPdf.server.d.ts +14 -0
- package/dist-lib/lib/htmlNaarPdf.server.d.ts.map +1 -0
- package/dist-lib/lib/htmlNaarPdf.server.js +34 -0
- package/dist-lib/lib/htmlPdf.server.d.ts +29 -0
- package/dist-lib/lib/htmlPdf.server.d.ts.map +1 -0
- package/dist-lib/lib/htmlPdf.server.js +1009 -0
- package/dist-lib/lib/layoutPreview.d.ts +1 -1
- package/dist-lib/lib/layoutPreview.d.ts.map +1 -1
- package/dist-lib/lib/layoutPreview.js +9 -9
- package/dist-lib/lib/logoLibrary.d.ts +1 -1
- package/dist-lib/lib/logoLibrary.js +5 -3
- package/dist-lib/lib/orderData.server.d.ts +15 -0
- package/dist-lib/lib/orderData.server.d.ts.map +1 -0
- package/dist-lib/lib/orderData.server.js +194 -0
- package/dist-lib/lib/pakbonBuilder.d.ts.map +1 -1
- package/dist-lib/lib/pakbonBuilder.js +22 -2
- package/dist-lib/lib/pakbonHtml.js +50 -50
- package/dist-lib/lib/printQueue.server.d.ts +31 -0
- package/dist-lib/lib/printQueue.server.d.ts.map +1 -0
- package/dist-lib/lib/printQueue.server.js +435 -0
- package/dist-lib/lib/printService.functions.d.ts +103742 -0
- package/dist-lib/lib/printService.functions.d.ts.map +1 -0
- package/dist-lib/lib/printService.functions.js +94 -0
- package/dist-lib/lib/printService.server.d.ts +31 -0
- package/dist-lib/lib/printService.server.d.ts.map +1 -0
- package/dist-lib/lib/printService.server.js +110 -0
- package/dist-lib/lib/productiebonHtml.d.ts +18 -0
- package/dist-lib/lib/productiebonHtml.d.ts.map +1 -0
- package/dist-lib/lib/productiebonHtml.js +166 -0
- package/dist-lib/lib/profielHtml.d.ts +14 -0
- package/dist-lib/lib/profielHtml.d.ts.map +1 -0
- package/dist-lib/lib/profielHtml.js +256 -0
- package/dist-lib/lib/runnerRegistry.server.d.ts +10 -0
- package/dist-lib/lib/runnerRegistry.server.d.ts.map +1 -0
- package/dist-lib/lib/runnerRegistry.server.js +79 -0
- package/dist-lib/lib/supabaseAdmin.server.d.ts +6 -0
- package/dist-lib/lib/supabaseAdmin.server.d.ts.map +1 -0
- package/dist-lib/lib/supabaseAdmin.server.js +43 -0
- package/dist-lib/lib/testpagina.d.ts +2 -0
- package/dist-lib/lib/testpagina.d.ts.map +1 -0
- package/dist-lib/lib/testpagina.js +31 -0
- package/dist-lib/lib/useAutoRefresh.d.ts +8 -0
- package/dist-lib/lib/useAutoRefresh.d.ts.map +1 -0
- package/dist-lib/lib/useAutoRefresh.js +44 -0
- package/dist-lib/lib/useDocumentData.d.ts +4 -3
- package/dist-lib/lib/useDocumentData.d.ts.map +1 -1
- package/dist-lib/lib/useDocumentData.js +10 -7
- package/dist-lib/lib/useNetwerkPrinters.d.ts +12 -0
- package/dist-lib/lib/useNetwerkPrinters.d.ts.map +1 -0
- package/dist-lib/lib/useNetwerkPrinters.js +26 -0
- package/package.json +11 -3
|
@@ -0,0 +1,1009 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// src/lib/htmlPdf.server.ts — eigen documentmaker (HTML -> PDF) op de server.
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// Geen externe dienst en geen browser nodig: dit bestand bevat een compacte
|
|
5
|
+
// opmaakmotor die de print-HTML van de layoutmodule (pakbon, sticker,
|
|
6
|
+
// verzendlabel, productiebon, vrije layouts) tekent op echte PDF-pagina's.
|
|
7
|
+
// De motor kent precies de bouwstenen die de renderers gebruiken: blokken,
|
|
8
|
+
// rijen naast elkaar, tabellen met vaste kolombreedtes, kaders, lijnen,
|
|
9
|
+
// afbeeldingen, barcodes en QR-vlakken.
|
|
10
|
+
// ============================================================================
|
|
11
|
+
import { PDFDocument, StandardFonts, rgb, } from "pdf-lib";
|
|
12
|
+
export const MM = 2.834645669291339; // punten per millimeter
|
|
13
|
+
const LEEG = new Set(["img", "br", "hr", "col", "meta", "link", "input"]);
|
|
14
|
+
function decodeer(t) {
|
|
15
|
+
return t
|
|
16
|
+
.replace(/ /g, " ")
|
|
17
|
+
.replace(/&/g, "&")
|
|
18
|
+
.replace(/</g, "<")
|
|
19
|
+
.replace(/>/g, ">")
|
|
20
|
+
.replace(/"/g, '"')
|
|
21
|
+
.replace(/'/g, "'")
|
|
22
|
+
.replace(/€/g, "\u20ac");
|
|
23
|
+
}
|
|
24
|
+
function leesAttrs(rauw) {
|
|
25
|
+
const out = {};
|
|
26
|
+
const re = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/g;
|
|
27
|
+
let m;
|
|
28
|
+
while ((m = re.exec(rauw)))
|
|
29
|
+
out[m[1].toLowerCase()] = decodeer(m[3] ?? m[4] ?? m[5] ?? "");
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
/** Zet HTML om naar een boom. Alleen de <body> wordt gebruikt. */
|
|
33
|
+
export function ontleedHtml(html) {
|
|
34
|
+
const body = /<body[^>]*>([\s\S]*?)<\/body>/i.exec(html)?.[1] ?? html;
|
|
35
|
+
const wortel = { tag: "body", attrs: {}, kinderen: [] };
|
|
36
|
+
const stapel = [wortel];
|
|
37
|
+
const re = /<!--[\s\S]*?-->|<\/?\s*([a-zA-Z][a-zA-Z0-9]*)([^>]*)>|([^<]+)/g;
|
|
38
|
+
let m;
|
|
39
|
+
while ((m = re.exec(body))) {
|
|
40
|
+
const stuk = m[0];
|
|
41
|
+
if (stuk.startsWith("<!--"))
|
|
42
|
+
continue;
|
|
43
|
+
const huidig = stapel[stapel.length - 1];
|
|
44
|
+
if (m[3] !== undefined) {
|
|
45
|
+
const tekst = decodeer(m[3]).replace(/\s+/g, " ");
|
|
46
|
+
if (tekst.trim().length || (tekst === " " && huidig.kinderen.length))
|
|
47
|
+
huidig.kinderen.push({ tag: "#tekst", attrs: {}, kinderen: [], tekst });
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const tag = (m[1] ?? "").toLowerCase();
|
|
51
|
+
if (!tag)
|
|
52
|
+
continue;
|
|
53
|
+
if (stuk.startsWith("</")) {
|
|
54
|
+
for (let i = stapel.length - 1; i > 0; i--) {
|
|
55
|
+
if (stapel[i].tag === tag) {
|
|
56
|
+
stapel.length = i;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (tag === "style" || tag === "script") {
|
|
63
|
+
const eind = new RegExp(`</\\s*${tag}\\s*>`, "i");
|
|
64
|
+
const rest = body.slice(re.lastIndex);
|
|
65
|
+
const p = rest.search(eind);
|
|
66
|
+
re.lastIndex += p >= 0 ? p + rest.slice(p).indexOf(">") + 1 : 0;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const knoop = { tag, attrs: leesAttrs(m[2] ?? ""), kinderen: [] };
|
|
70
|
+
huidig.kinderen.push(knoop);
|
|
71
|
+
if (!LEEG.has(tag) && !stuk.endsWith("/>"))
|
|
72
|
+
stapel.push(knoop);
|
|
73
|
+
}
|
|
74
|
+
return wortel;
|
|
75
|
+
}
|
|
76
|
+
const GRIJS = rgb(0.42, 0.45, 0.5);
|
|
77
|
+
const DONKER = rgb(0.07, 0.09, 0.15);
|
|
78
|
+
const LIJN = rgb(0.82, 0.84, 0.86);
|
|
79
|
+
function klassen(n) {
|
|
80
|
+
return String(n.attrs["class"] ?? "").split(/\s+/).filter(Boolean);
|
|
81
|
+
}
|
|
82
|
+
const heeft = (n, k) => klassen(n).includes(k);
|
|
83
|
+
function inline(n) {
|
|
84
|
+
const out = {};
|
|
85
|
+
for (const deel of String(n.attrs["style"] ?? "").split(";")) {
|
|
86
|
+
const i = deel.indexOf(":");
|
|
87
|
+
if (i > 0)
|
|
88
|
+
out[deel.slice(0, i).trim().toLowerCase()] = deel.slice(i + 1).trim();
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
function hexNaarRgb(v) {
|
|
93
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(v.trim());
|
|
94
|
+
if (m) {
|
|
95
|
+
const n = parseInt(m[1], 16);
|
|
96
|
+
return rgb(((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255);
|
|
97
|
+
}
|
|
98
|
+
const k = /^#?([0-9a-f]{3})$/i.exec(v.trim());
|
|
99
|
+
if (k) {
|
|
100
|
+
const s = k[1];
|
|
101
|
+
return rgb(parseInt(s[0] + s[0], 16) / 255, parseInt(s[1] + s[1], 16) / 255, parseInt(s[2] + s[2], 16) / 255);
|
|
102
|
+
}
|
|
103
|
+
if (/^white$/i.test(v))
|
|
104
|
+
return rgb(1, 1, 1);
|
|
105
|
+
if (/^black$/i.test(v))
|
|
106
|
+
return rgb(0, 0, 0);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
/** Lengte in punten uit een css-waarde (mm, pt, px of %). */
|
|
110
|
+
function lengte(v, basis = 0) {
|
|
111
|
+
if (!v)
|
|
112
|
+
return null;
|
|
113
|
+
const m = /^(-?[\d.]+)\s*(mm|pt|px|%)?$/.exec(v.trim());
|
|
114
|
+
if (!m)
|
|
115
|
+
return null;
|
|
116
|
+
const n = Number(m[1]);
|
|
117
|
+
if (!Number.isFinite(n))
|
|
118
|
+
return null;
|
|
119
|
+
switch (m[2]) {
|
|
120
|
+
case "mm":
|
|
121
|
+
return n * MM;
|
|
122
|
+
case "pt":
|
|
123
|
+
return n;
|
|
124
|
+
case "px":
|
|
125
|
+
return n * 0.75;
|
|
126
|
+
case "%":
|
|
127
|
+
return (n / 100) * basis;
|
|
128
|
+
default:
|
|
129
|
+
return n;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function stijlVan(n, ouder) {
|
|
133
|
+
const s = { ...ouder };
|
|
134
|
+
const cl = klassen(n);
|
|
135
|
+
if (n.tag === "th" || cl.includes("vet") || n.tag === "b" || n.tag === "strong")
|
|
136
|
+
s.vet = true;
|
|
137
|
+
if (cl.includes("sectitel")) {
|
|
138
|
+
s.grootte = ouder.grootte * 0.9;
|
|
139
|
+
s.vet = true;
|
|
140
|
+
s.kleur = GRIJS;
|
|
141
|
+
}
|
|
142
|
+
if (cl.includes("klein") || cl.includes("meta") || cl.includes("k")) {
|
|
143
|
+
s.grootte = ouder.grootte * (cl.includes("meta") ? 0.88 : 0.85);
|
|
144
|
+
s.kleur = GRIJS;
|
|
145
|
+
}
|
|
146
|
+
if (n.tag === "th") {
|
|
147
|
+
s.grootte = ouder.grootte * 0.9;
|
|
148
|
+
s.kleur = GRIJS;
|
|
149
|
+
}
|
|
150
|
+
if (cl.includes("bundel"))
|
|
151
|
+
s.vet = true;
|
|
152
|
+
if (cl.includes("karkop") || cl.includes("laagkop") || cl.includes("totalen"))
|
|
153
|
+
s.vet = true;
|
|
154
|
+
const st = inline(n);
|
|
155
|
+
const fs = st["font-size"];
|
|
156
|
+
if (fs) {
|
|
157
|
+
const g = /%$/.test(fs) ? (parseFloat(fs) / 100) * ouder.grootte : lengte(fs);
|
|
158
|
+
if (g && g > 0)
|
|
159
|
+
s.grootte = g;
|
|
160
|
+
}
|
|
161
|
+
if (st["font-weight"])
|
|
162
|
+
s.vet = Number(st["font-weight"]) >= 600 || st["font-weight"] === "bold";
|
|
163
|
+
const kl = st["color"] ? hexNaarRgb(st["color"]) : null;
|
|
164
|
+
if (kl)
|
|
165
|
+
s.kleur = kl;
|
|
166
|
+
const ta = st["text-align"];
|
|
167
|
+
if (ta === "left" || ta === "center" || ta === "right")
|
|
168
|
+
s.uitlijning = ta;
|
|
169
|
+
return s;
|
|
170
|
+
}
|
|
171
|
+
// --- 3. tekenhulp -----------------------------------------------------------
|
|
172
|
+
const NORMALISEER = {
|
|
173
|
+
"\u2013": "-",
|
|
174
|
+
"\u2014": "-",
|
|
175
|
+
"\u2018": "'",
|
|
176
|
+
"\u2019": "'",
|
|
177
|
+
"\u201c": '"',
|
|
178
|
+
"\u201d": '"',
|
|
179
|
+
"\u2022": "-",
|
|
180
|
+
"\u00a0": " ",
|
|
181
|
+
"\u2026": "...",
|
|
182
|
+
"\u20ac": "EUR",
|
|
183
|
+
"\u2192": "->",
|
|
184
|
+
"\u2713": "v",
|
|
185
|
+
"\u00d7": "x",
|
|
186
|
+
};
|
|
187
|
+
function veiligeTekst(t) {
|
|
188
|
+
let out = "";
|
|
189
|
+
for (const c of t) {
|
|
190
|
+
const v = NORMALISEER[c] ?? c;
|
|
191
|
+
out += v.charCodeAt(0) > 255 ? "?" : v;
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
195
|
+
class Tekenaar {
|
|
196
|
+
constructor(pdf, breedte, hoogte, marge, normaal, vet) {
|
|
197
|
+
this.pdf = pdf;
|
|
198
|
+
this.breedte = breedte;
|
|
199
|
+
this.hoogte = hoogte;
|
|
200
|
+
this.marge = marge;
|
|
201
|
+
this.normaal = normaal;
|
|
202
|
+
this.vet = vet;
|
|
203
|
+
this.paginas = [];
|
|
204
|
+
this.voetHoogte = 0;
|
|
205
|
+
this.voetTekenaar = null;
|
|
206
|
+
this.pagina = pdf.addPage([breedte, hoogte]);
|
|
207
|
+
this.paginas.push(this.pagina);
|
|
208
|
+
this.y = hoogte - marge;
|
|
209
|
+
}
|
|
210
|
+
font(s) {
|
|
211
|
+
return s.vet ? this.vet : this.normaal;
|
|
212
|
+
}
|
|
213
|
+
breedteVan(t, s) {
|
|
214
|
+
return this.font(s).widthOfTextAtSize(veiligeTekst(t), s.grootte);
|
|
215
|
+
}
|
|
216
|
+
get bodem() {
|
|
217
|
+
return this.marge + this.voetHoogte;
|
|
218
|
+
}
|
|
219
|
+
get ruimte() {
|
|
220
|
+
return this.y - this.bodem;
|
|
221
|
+
}
|
|
222
|
+
zetVoet(hoogte, teken) {
|
|
223
|
+
this.voetHoogte = hoogte;
|
|
224
|
+
this.voetTekenaar = teken;
|
|
225
|
+
}
|
|
226
|
+
nieuwePagina() {
|
|
227
|
+
this.pagina = this.pdf.addPage([this.breedte, this.hoogte]);
|
|
228
|
+
this.paginas.push(this.pagina);
|
|
229
|
+
this.y = this.hoogte - this.marge;
|
|
230
|
+
}
|
|
231
|
+
zorgVoor(h) {
|
|
232
|
+
if (h <= this.ruimte || this.y >= this.hoogte - this.marge - 0.01)
|
|
233
|
+
return;
|
|
234
|
+
this.nieuwePagina();
|
|
235
|
+
}
|
|
236
|
+
/** Tekent op een wegwerp-pagina om de exacte hoogte te meten. */
|
|
237
|
+
async meet(teken) {
|
|
238
|
+
const bewaard = { pagina: this.pagina, y: this.y, paginas: this.paginas, voet: this.voetHoogte };
|
|
239
|
+
const hoog = 20000;
|
|
240
|
+
const tijdelijk = this.pdf.addPage([this.breedte, hoog]);
|
|
241
|
+
this.pagina = tijdelijk;
|
|
242
|
+
this.paginas = [tijdelijk];
|
|
243
|
+
this.y = hoog - this.marge;
|
|
244
|
+
this.voetHoogte = 0;
|
|
245
|
+
try {
|
|
246
|
+
await teken();
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// meten mag nooit het document blokkeren
|
|
250
|
+
}
|
|
251
|
+
const hoogte = hoog - this.marge - this.y;
|
|
252
|
+
for (const p of this.paginas) {
|
|
253
|
+
const i = this.pdf.getPages().indexOf(p);
|
|
254
|
+
if (i >= 0)
|
|
255
|
+
this.pdf.removePage(i);
|
|
256
|
+
}
|
|
257
|
+
this.pagina = bewaard.pagina;
|
|
258
|
+
this.y = bewaard.y;
|
|
259
|
+
this.paginas = bewaard.paginas;
|
|
260
|
+
this.voetHoogte = bewaard.voet;
|
|
261
|
+
return hoogte;
|
|
262
|
+
}
|
|
263
|
+
rondAf() {
|
|
264
|
+
if (!this.voetTekenaar)
|
|
265
|
+
return;
|
|
266
|
+
for (const p of this.paginas)
|
|
267
|
+
this.voetTekenaar(p);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function regelHoogte(s) {
|
|
271
|
+
return s.grootte * 1.35;
|
|
272
|
+
}
|
|
273
|
+
/** Breekt tekst af op de beschikbare breedte. */
|
|
274
|
+
function breekAf(tk, tekst, s, breedte) {
|
|
275
|
+
const woorden = veiligeTekst(tekst).split(/\s+/).filter((w) => w.length);
|
|
276
|
+
if (!woorden.length)
|
|
277
|
+
return [];
|
|
278
|
+
const regels = [];
|
|
279
|
+
let huidig = "";
|
|
280
|
+
for (const w of woorden) {
|
|
281
|
+
const kandidaat = huidig ? `${huidig} ${w}` : w;
|
|
282
|
+
if (tk.breedteVan(kandidaat, s) <= breedte || !huidig)
|
|
283
|
+
huidig = kandidaat;
|
|
284
|
+
else {
|
|
285
|
+
regels.push(huidig);
|
|
286
|
+
huidig = w;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (huidig)
|
|
290
|
+
regels.push(huidig);
|
|
291
|
+
return regels;
|
|
292
|
+
}
|
|
293
|
+
function tekenRegel(tk, regel, x, breedte, y, s) {
|
|
294
|
+
const t = veiligeTekst(regel);
|
|
295
|
+
const w = tk.breedteVan(t, s);
|
|
296
|
+
const dx = s.uitlijning === "right" ? breedte - w : s.uitlijning === "center" ? (breedte - w) / 2 : 0;
|
|
297
|
+
tk.pagina.drawText(t, {
|
|
298
|
+
x: x + Math.max(0, dx),
|
|
299
|
+
y,
|
|
300
|
+
size: s.grootte,
|
|
301
|
+
font: tk.font(s),
|
|
302
|
+
color: s.kleur,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
const inlineTags = new Set(["#tekst", "span", "b", "strong", "em", "i", "small", "a"]);
|
|
306
|
+
function isInline(n) {
|
|
307
|
+
if (!inlineTags.has(n.tag))
|
|
308
|
+
return false;
|
|
309
|
+
const st = inline(n);
|
|
310
|
+
return !st["height"] && !st["width"];
|
|
311
|
+
}
|
|
312
|
+
/** Verzamelt de platte tekst van een knoop (met spatie tussen losse stukjes). */
|
|
313
|
+
function plattTekst(n) {
|
|
314
|
+
if (n.tag === "#tekst")
|
|
315
|
+
return n.tekst ?? "";
|
|
316
|
+
let uit = "";
|
|
317
|
+
for (const k of n.kinderen) {
|
|
318
|
+
const deel = plattTekst(k);
|
|
319
|
+
if (!deel)
|
|
320
|
+
continue;
|
|
321
|
+
if (uit && !/\s$/.test(uit) && !/^\s/.test(deel))
|
|
322
|
+
uit += " ";
|
|
323
|
+
uit += deel;
|
|
324
|
+
}
|
|
325
|
+
return uit;
|
|
326
|
+
}
|
|
327
|
+
/** Meet de exacte hoogte van een knoop door hem op een wegwerp-pagina te tekenen. */
|
|
328
|
+
async function meetHoogte(ctx, n, vak, s) {
|
|
329
|
+
const meetCtx = { ...ctx, meten: true };
|
|
330
|
+
return ctx.tk.meet(async () => {
|
|
331
|
+
await tekenKinderen(meetCtx, { tag: "div", attrs: {}, kinderen: [n] }, vak, s);
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
function rijenVan(tabel) {
|
|
335
|
+
const rijen = [];
|
|
336
|
+
const loop = (k) => {
|
|
337
|
+
for (const c of k.kinderen) {
|
|
338
|
+
if (c.tag === "tr")
|
|
339
|
+
rijen.push(c);
|
|
340
|
+
else if (c.tag === "thead" || c.tag === "tbody" || c.tag === "tfoot")
|
|
341
|
+
loop(c);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
loop(tabel);
|
|
345
|
+
return rijen;
|
|
346
|
+
}
|
|
347
|
+
function kolomBreedtes(tabel, rijen, totaal) {
|
|
348
|
+
const cols = [];
|
|
349
|
+
for (const c of tabel.kinderen)
|
|
350
|
+
if (c.tag === "colgroup")
|
|
351
|
+
cols.push(...c.kinderen.filter((x) => x.tag === "col"));
|
|
352
|
+
const eerste = rijen[0];
|
|
353
|
+
const aantal = Math.max(cols.length, eerste ? eerste.kinderen.filter((c) => c.tag === "td" || c.tag === "th").length : 1);
|
|
354
|
+
if (!aantal)
|
|
355
|
+
return [totaal];
|
|
356
|
+
if (cols.length === aantal) {
|
|
357
|
+
const rauw = cols.map((c) => lengte(inline(c)["width"] ?? c.attrs["width"], totaal));
|
|
358
|
+
const som = rauw.reduce((a, b) => a + (b ?? 0), 0);
|
|
359
|
+
if (som > 1) {
|
|
360
|
+
const factor = totaal / som;
|
|
361
|
+
return rauw.map((v) => (v ?? 0) * factor);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return Array.from({ length: aantal }, () => totaal / aantal);
|
|
365
|
+
}
|
|
366
|
+
async function tekenBlok(ctx, n, vak, ouderStijl) {
|
|
367
|
+
const tk = ctx.tk;
|
|
368
|
+
const s = stijlVan(n, ouderStijl);
|
|
369
|
+
const st = inline(n);
|
|
370
|
+
const cl = klassen(n);
|
|
371
|
+
if (n.tag === "#tekst") {
|
|
372
|
+
const regels = breekAf(tk, n.tekst ?? "", s, vak.breedte);
|
|
373
|
+
for (const r of regels) {
|
|
374
|
+
tk.zorgVoor(regelHoogte(s));
|
|
375
|
+
tk.y -= regelHoogte(s);
|
|
376
|
+
tekenRegel(tk, r, vak.x, vak.breedte, tk.y + s.grootte * 0.25, s);
|
|
377
|
+
}
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (n.tag === "br") {
|
|
381
|
+
tk.y -= regelHoogte(s);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (n.tag === "hr" || cl.includes("lijn")) {
|
|
385
|
+
tk.zorgVoor(2 * MM);
|
|
386
|
+
tk.y -= 1.2 * MM;
|
|
387
|
+
tk.pagina.drawLine({
|
|
388
|
+
start: { x: vak.x, y: tk.y },
|
|
389
|
+
end: { x: vak.x + vak.breedte, y: tk.y },
|
|
390
|
+
thickness: 0.7,
|
|
391
|
+
color: LIJN,
|
|
392
|
+
});
|
|
393
|
+
tk.y -= 1.2 * MM;
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (cl.includes("balk")) {
|
|
397
|
+
tk.zorgVoor(3 * MM);
|
|
398
|
+
tk.y -= 1.5 * MM + 1 * MM;
|
|
399
|
+
tk.pagina.drawRectangle({
|
|
400
|
+
x: vak.x,
|
|
401
|
+
y: tk.y,
|
|
402
|
+
width: vak.breedte,
|
|
403
|
+
height: 1 * MM,
|
|
404
|
+
color: rgb(0.22, 0.25, 0.3),
|
|
405
|
+
});
|
|
406
|
+
tk.y -= 1.5 * MM;
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (n.tag === "img") {
|
|
410
|
+
await tekenAfbeelding(ctx, n, vak, s);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (cl.includes("qr")) {
|
|
414
|
+
const g = lengte(st["width"]) ?? 25 * MM;
|
|
415
|
+
tk.zorgVoor(g + 1 * MM);
|
|
416
|
+
tk.y -= g;
|
|
417
|
+
tk.pagina.drawRectangle({
|
|
418
|
+
x: vak.x,
|
|
419
|
+
y: tk.y,
|
|
420
|
+
width: g,
|
|
421
|
+
height: g,
|
|
422
|
+
borderWidth: 0.7,
|
|
423
|
+
borderColor: GRIJS,
|
|
424
|
+
});
|
|
425
|
+
tk.y -= 1 * MM;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (cl.includes("streepjes")) {
|
|
429
|
+
const h = lengte(st["height"]) ?? 15 * MM;
|
|
430
|
+
const b = Math.min(45 * MM, vak.breedte);
|
|
431
|
+
tk.zorgVoor(h + 1 * MM);
|
|
432
|
+
tk.y -= h;
|
|
433
|
+
for (let x = 0; x < b - 1; x += 1.9) {
|
|
434
|
+
tk.pagina.drawRectangle({
|
|
435
|
+
x: vak.x + x,
|
|
436
|
+
y: tk.y,
|
|
437
|
+
width: 0.9,
|
|
438
|
+
height: h,
|
|
439
|
+
color: DONKER,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
tk.y -= 1 * MM;
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (cl.includes("lijnveld")) {
|
|
446
|
+
const extra = cl.includes("hoog") ? 10 * MM : 3 * MM;
|
|
447
|
+
tk.zorgVoor(regelHoogte(s) * 2 + extra);
|
|
448
|
+
tk.y -= 4 * MM;
|
|
449
|
+
const tekst = plattTekst(n).trim();
|
|
450
|
+
if (tekst) {
|
|
451
|
+
tk.y -= regelHoogte(s);
|
|
452
|
+
tekenRegel(tk, tekst, vak.x, vak.breedte, tk.y + s.grootte * 0.25, s);
|
|
453
|
+
}
|
|
454
|
+
tk.y -= extra;
|
|
455
|
+
tk.pagina.drawLine({
|
|
456
|
+
start: { x: vak.x, y: tk.y },
|
|
457
|
+
end: { x: vak.x + vak.breedte, y: tk.y },
|
|
458
|
+
thickness: 0.7,
|
|
459
|
+
color: GRIJS,
|
|
460
|
+
});
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (n.tag === "table") {
|
|
464
|
+
await tekenTabel(ctx, n, vak, s);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (n.tag === "ul" || n.tag === "ol") {
|
|
468
|
+
let i = 1;
|
|
469
|
+
for (const li of n.kinderen.filter((k) => k.tag === "li")) {
|
|
470
|
+
const bullet = n.tag === "ol" ? `${i++}.` : "-";
|
|
471
|
+
const binnen = { x: vak.x + 4 * MM, breedte: vak.breedte - 4 * MM };
|
|
472
|
+
const voor = tk.y;
|
|
473
|
+
await tekenKinderen(ctx, li, binnen, s);
|
|
474
|
+
tekenRegel(tk, bullet, vak.x, 4 * MM, Math.min(voor, tk.y + regelHoogte(s)) - regelHoogte(s) + s.grootte * 0.25, s);
|
|
475
|
+
}
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
// kader / achtergrond
|
|
479
|
+
const kader = cl.includes("kader") || cl.includes("rand") || cl.includes("omkaderd");
|
|
480
|
+
const achtergrond = st["background"] ? hexNaarRgb(st["background"].split(" ")[0] ?? "") : null;
|
|
481
|
+
const padding = kader ? 1.5 * MM : 0;
|
|
482
|
+
if (cl.includes("breekvoor") && tk.y < tk.hoogte - tk.marge - 1)
|
|
483
|
+
tk.nieuwePagina();
|
|
484
|
+
const binnenVak = { x: vak.x + padding, breedte: vak.breedte - padding * 2 };
|
|
485
|
+
if (!ctx.meten && (cl.includes("heelhouden") || achtergrond)) {
|
|
486
|
+
const nodig = (await meetHoogte(ctx, n, binnenVak, s)) + padding * 2;
|
|
487
|
+
if (cl.includes("heelhouden") && nodig > tk.ruimte && nodig < tk.hoogte - tk.marge * 2) {
|
|
488
|
+
tk.nieuwePagina();
|
|
489
|
+
}
|
|
490
|
+
if (achtergrond) {
|
|
491
|
+
// achtergrondvlak eerst tekenen, anders valt het over de tekst heen
|
|
492
|
+
tk.pagina.drawRectangle({
|
|
493
|
+
x: vak.x,
|
|
494
|
+
y: Math.max(tk.bodem, tk.y - nodig),
|
|
495
|
+
width: vak.breedte,
|
|
496
|
+
height: Math.min(nodig, tk.y - tk.bodem),
|
|
497
|
+
color: achtergrond,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
const startY = tk.y;
|
|
502
|
+
const startPagina = tk.pagina;
|
|
503
|
+
if (kader || achtergrond)
|
|
504
|
+
tk.y -= padding;
|
|
505
|
+
await tekenKinderen(ctx, n, binnenVak, s);
|
|
506
|
+
if (kader || achtergrond)
|
|
507
|
+
tk.y -= padding;
|
|
508
|
+
if (kader && tk.pagina === startPagina && startY - tk.y > 0) {
|
|
509
|
+
startPagina.drawRectangle({
|
|
510
|
+
x: vak.x,
|
|
511
|
+
y: tk.y,
|
|
512
|
+
width: vak.breedte,
|
|
513
|
+
height: startY - tk.y,
|
|
514
|
+
borderWidth: 0.6,
|
|
515
|
+
borderColor: LIJN,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
if (cl.includes("onder") || cl.includes("laagkop")) {
|
|
519
|
+
tk.pagina.drawLine({
|
|
520
|
+
start: { x: vak.x, y: tk.y + 0.6 * MM },
|
|
521
|
+
end: { x: vak.x + vak.breedte, y: tk.y + 0.6 * MM },
|
|
522
|
+
thickness: 0.5,
|
|
523
|
+
color: LIJN,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
if (cl.includes("totalen")) {
|
|
527
|
+
tk.pagina.drawLine({
|
|
528
|
+
start: { x: vak.x, y: startY },
|
|
529
|
+
end: { x: vak.x + vak.breedte, y: startY },
|
|
530
|
+
thickness: 0.7,
|
|
531
|
+
color: LIJN,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
if (cl.includes("blok") || cl.includes("groep") || cl.includes("karblok") || cl.includes("laag"))
|
|
535
|
+
tk.y -= 1.6 * MM;
|
|
536
|
+
}
|
|
537
|
+
async function tekenAfbeelding(ctx, n, vak, s) {
|
|
538
|
+
const tk = ctx.tk;
|
|
539
|
+
const src = n.attrs["src"] ?? "";
|
|
540
|
+
const afb = ctx.afbeeldingen.get(src);
|
|
541
|
+
const hoogte = lengte(inline(n)["height"]) ?? 18 * MM;
|
|
542
|
+
if (!afb) {
|
|
543
|
+
tk.y -= hoogte + 1 * MM;
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const schaal = hoogte / afb.height;
|
|
547
|
+
const breedte = Math.min(afb.width * schaal, vak.breedte);
|
|
548
|
+
const echteHoogte = (breedte / afb.width) * afb.height;
|
|
549
|
+
tk.zorgVoor(echteHoogte + 1 * MM);
|
|
550
|
+
tk.y -= echteHoogte;
|
|
551
|
+
const dx = s.uitlijning === "right"
|
|
552
|
+
? vak.breedte - breedte
|
|
553
|
+
: s.uitlijning === "center"
|
|
554
|
+
? (vak.breedte - breedte) / 2
|
|
555
|
+
: 0;
|
|
556
|
+
tk.pagina.drawImage(afb, { x: vak.x + Math.max(0, dx), y: tk.y, width: breedte, height: echteHoogte });
|
|
557
|
+
tk.y -= 1 * MM;
|
|
558
|
+
}
|
|
559
|
+
async function tekenTabel(ctx, tabel, vak, s) {
|
|
560
|
+
const tk = ctx.tk;
|
|
561
|
+
const rijen = rijenVan(tabel);
|
|
562
|
+
if (!rijen.length)
|
|
563
|
+
return;
|
|
564
|
+
const breedtes = kolomBreedtes(tabel, rijen, vak.breedte);
|
|
565
|
+
const compact = heeft(tabel, "compact");
|
|
566
|
+
const padY = compact ? 0.3 * MM : 0.7 * MM;
|
|
567
|
+
const padX = compact ? 0.8 * MM : 1 * MM;
|
|
568
|
+
const randen = heeft(tabel, "rand");
|
|
569
|
+
const kopRij = rijen.find((r) => r.kinderen.some((c) => c.tag === "th")) ?? null;
|
|
570
|
+
const tekenRij = (rij, herhaald = false) => {
|
|
571
|
+
const cellen = rij.kinderen.filter((c) => c.tag === "td" || c.tag === "th");
|
|
572
|
+
const cellenStijl = cellen.map((c) => stijlVan(c, s));
|
|
573
|
+
const teksten = cellen.map((c, i) => breekAf(tk, plattTekst(c), cellenStijl[i], Math.max(4, (breedtes[i] ?? 20) - padX * 2)));
|
|
574
|
+
const hoogte = Math.max(1, ...teksten.map((t) => t.length)) * regelHoogte(cellenStijl[0] ?? s) + padY * 2;
|
|
575
|
+
if (hoogte > tk.ruimte) {
|
|
576
|
+
tk.nieuwePagina();
|
|
577
|
+
if (kopRij && !herhaald && rij !== kopRij)
|
|
578
|
+
tekenRij(kopRij, true);
|
|
579
|
+
}
|
|
580
|
+
const bovenY = tk.y;
|
|
581
|
+
const zebra = heeft(rij, "zebra");
|
|
582
|
+
if (zebra) {
|
|
583
|
+
tk.pagina.drawRectangle({
|
|
584
|
+
x: vak.x,
|
|
585
|
+
y: bovenY - hoogte,
|
|
586
|
+
width: vak.breedte,
|
|
587
|
+
height: hoogte,
|
|
588
|
+
color: rgb(0.95, 0.96, 0.97),
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
let x = vak.x;
|
|
592
|
+
cellen.forEach((c, i) => {
|
|
593
|
+
const b = breedtes[i] ?? 20;
|
|
594
|
+
const cs = { ...cellenStijl[i], uitlijning: cellenStijl[i].uitlijning };
|
|
595
|
+
let y = bovenY - padY;
|
|
596
|
+
for (const regel of teksten[i]) {
|
|
597
|
+
y -= regelHoogte(cs);
|
|
598
|
+
tekenRegel(tk, regel, x + padX, b - padX * 2, y + cs.grootte * 0.25, cs);
|
|
599
|
+
}
|
|
600
|
+
if (randen) {
|
|
601
|
+
tk.pagina.drawRectangle({
|
|
602
|
+
x,
|
|
603
|
+
y: bovenY - hoogte,
|
|
604
|
+
width: b,
|
|
605
|
+
height: hoogte,
|
|
606
|
+
borderWidth: 0.5,
|
|
607
|
+
borderColor: LIJN,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
x += b;
|
|
611
|
+
});
|
|
612
|
+
if (!randen && (rij === kopRij || heeft(rij, "onder"))) {
|
|
613
|
+
tk.pagina.drawLine({
|
|
614
|
+
start: { x: vak.x, y: bovenY - hoogte },
|
|
615
|
+
end: { x: vak.x + vak.breedte, y: bovenY - hoogte },
|
|
616
|
+
thickness: 0.5,
|
|
617
|
+
color: LIJN,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
tk.y = bovenY - hoogte;
|
|
621
|
+
};
|
|
622
|
+
for (const rij of rijen)
|
|
623
|
+
tekenRij(rij);
|
|
624
|
+
tk.y -= 1 * MM;
|
|
625
|
+
}
|
|
626
|
+
/** Tekent een element dat met vaste coördinaten geplaatst is (position:absolute). */
|
|
627
|
+
async function tekenAbsoluut(ctx, n, s) {
|
|
628
|
+
const tk = ctx.tk;
|
|
629
|
+
const st = inline(n);
|
|
630
|
+
const eigen = stijlVan(n, s);
|
|
631
|
+
const x = lengte(st["left"]) ?? 0;
|
|
632
|
+
const top = lengte(st["top"]) ?? 0;
|
|
633
|
+
const breedte = lengte(st["width"]) ?? tk.breedte - x;
|
|
634
|
+
const hoogte = lengte(st["height"]) ?? regelHoogte(eigen);
|
|
635
|
+
const yBoven = tk.hoogte - top;
|
|
636
|
+
const yOnder = yBoven - hoogte;
|
|
637
|
+
const gradientKind = zoekGradient(n);
|
|
638
|
+
if (gradientKind) {
|
|
639
|
+
const gebied = gradientKind === "streepjes" ? hoogte * 0.72 : Math.min(breedte, hoogte);
|
|
640
|
+
const top = yBoven;
|
|
641
|
+
if (gradientKind === "streepjes") {
|
|
642
|
+
for (let bx = 0; bx < breedte - 1; bx += 2.2) {
|
|
643
|
+
tk.pagina.drawRectangle({ x: x + bx, y: top - gebied, width: 1, height: gebied, color: DONKER });
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
const stap = Math.max(2, gebied / 12);
|
|
648
|
+
for (let ix = 0; ix < gebied - 1; ix += stap * 2) {
|
|
649
|
+
for (let iy = 0; iy < gebied - 1; iy += stap * 2) {
|
|
650
|
+
tk.pagina.drawRectangle({
|
|
651
|
+
x: x + ix,
|
|
652
|
+
y: top - gebied + iy,
|
|
653
|
+
width: Math.min(stap, gebied - ix),
|
|
654
|
+
height: Math.min(stap, gebied - iy),
|
|
655
|
+
color: DONKER,
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const onderschrift = plattTekst(n).trim();
|
|
661
|
+
if (onderschrift && gradientKind === "streepjes") {
|
|
662
|
+
const kleinStijl = { ...eigen, grootte: Math.min(eigen.grootte, 7), uitlijning: "center" };
|
|
663
|
+
tekenRegel(tk, onderschrift, x, breedte, yOnder + 1, kleinStijl);
|
|
664
|
+
}
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const achtergrondCss = st["background"] ?? st["background-color"] ?? "";
|
|
668
|
+
const isStreepjes = /repeating-linear-gradient/i.test(achtergrondCss);
|
|
669
|
+
const isRaster = /repeating-conic-gradient/i.test(achtergrondCss);
|
|
670
|
+
const vlak = hexNaarRgb(achtergrondCss.split(/\s+/)[0] ?? "");
|
|
671
|
+
const randDeel = /(\d*\.?\d+)mm\s+(?:solid|dashed)\s+(#[0-9a-f]{3,6})/i.exec(st["border"] ?? st["border-bottom"] ?? "");
|
|
672
|
+
if (isStreepjes) {
|
|
673
|
+
for (let bx = 0; bx < breedte - 1; bx += 2.2) {
|
|
674
|
+
tk.pagina.drawRectangle({ x: x + bx, y: yOnder, width: 1, height: hoogte, color: DONKER });
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
else if (isRaster) {
|
|
678
|
+
const stap = Math.max(2, Math.min(breedte, hoogte) / 12);
|
|
679
|
+
for (let ix = 0; ix < breedte - 1; ix += stap * 2) {
|
|
680
|
+
for (let iy = 0; iy < hoogte - 1; iy += stap * 2) {
|
|
681
|
+
tk.pagina.drawRectangle({
|
|
682
|
+
x: x + ix,
|
|
683
|
+
y: yOnder + iy,
|
|
684
|
+
width: Math.min(stap, breedte - ix),
|
|
685
|
+
height: Math.min(stap, hoogte - iy),
|
|
686
|
+
color: DONKER,
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
else if (vlak) {
|
|
692
|
+
tk.pagina.drawRectangle({ x, y: yOnder, width: breedte, height: Math.max(hoogte, 0.4), color: vlak });
|
|
693
|
+
}
|
|
694
|
+
if (randDeel) {
|
|
695
|
+
const dikte = Number(randDeel[1]) * MM || 0.5;
|
|
696
|
+
const kleur = hexNaarRgb(randDeel[2] ?? "") ?? DONKER;
|
|
697
|
+
if (st["border-bottom"] && !st["border"]) {
|
|
698
|
+
tk.pagina.drawLine({
|
|
699
|
+
start: { x, y: yOnder },
|
|
700
|
+
end: { x: x + breedte, y: yOnder },
|
|
701
|
+
thickness: dikte,
|
|
702
|
+
color: kleur,
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
tk.pagina.drawRectangle({
|
|
707
|
+
x,
|
|
708
|
+
y: yOnder,
|
|
709
|
+
width: breedte,
|
|
710
|
+
height: hoogte,
|
|
711
|
+
borderWidth: dikte,
|
|
712
|
+
borderColor: kleur,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
const tabel = zoekTag(n, "table");
|
|
717
|
+
if (tabel) {
|
|
718
|
+
const bewaardY = tk.y;
|
|
719
|
+
tk.y = yBoven;
|
|
720
|
+
await tekenTabel(ctx, tabel, { x, breedte }, eigen);
|
|
721
|
+
tk.y = bewaardY;
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const afbeelding = zoekTag(n, "img");
|
|
725
|
+
if (afbeelding) {
|
|
726
|
+
const bron = afbeelding.attrs["src"] ?? "";
|
|
727
|
+
const beeld = ctx.afbeeldingen.get(bron);
|
|
728
|
+
if (beeld) {
|
|
729
|
+
const schaal = Math.min(breedte / beeld.width, hoogte / beeld.height);
|
|
730
|
+
const b = beeld.width * schaal;
|
|
731
|
+
const h = beeld.height * schaal;
|
|
732
|
+
tk.pagina.drawImage(beeld, {
|
|
733
|
+
x: x + (breedte - b) / 2,
|
|
734
|
+
y: yOnder + (hoogte - h) / 2,
|
|
735
|
+
width: b,
|
|
736
|
+
height: h,
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const tekst = plattTekst(n).trim();
|
|
742
|
+
if (!tekst)
|
|
743
|
+
return;
|
|
744
|
+
const regels = breekAf(tk, tekst, eigen, breedte - 1);
|
|
745
|
+
const padding = lengte(st["padding"]) ?? 0;
|
|
746
|
+
const gecentreerd = /center/.test(st["align-items"] ?? "") || /center/.test(st["justify-content"] ?? "");
|
|
747
|
+
const blokHoogte = regels.length * regelHoogte(eigen);
|
|
748
|
+
let y = yBoven - padding - (gecentreerd ? Math.max(0, (hoogte - blokHoogte) / 2) : 0);
|
|
749
|
+
for (const r of regels) {
|
|
750
|
+
y -= regelHoogte(eigen);
|
|
751
|
+
if (y < yOnder - regelHoogte(eigen))
|
|
752
|
+
break;
|
|
753
|
+
tekenRegel(tk, r, x + padding, breedte - padding * 2, y + eigen.grootte * 0.25, eigen);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
/** Zoekt een streepjes- of blokjespatroon (barcode/QR) in de knoop of kinderen. */
|
|
757
|
+
function zoekGradient(n) {
|
|
758
|
+
const css = `${inline(n)["background"] ?? ""} ${inline(n)["background-image"] ?? ""}`;
|
|
759
|
+
if (/repeating-linear-gradient/i.test(css))
|
|
760
|
+
return "streepjes";
|
|
761
|
+
if (/repeating-conic-gradient/i.test(css))
|
|
762
|
+
return "raster";
|
|
763
|
+
for (const k of n.kinderen) {
|
|
764
|
+
const diep = zoekGradient(k);
|
|
765
|
+
if (diep)
|
|
766
|
+
return diep;
|
|
767
|
+
}
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
function zoekTag(n, tag) {
|
|
771
|
+
for (const k of n.kinderen) {
|
|
772
|
+
if (k.tag === tag)
|
|
773
|
+
return k;
|
|
774
|
+
const diep = zoekTag(k, tag);
|
|
775
|
+
if (diep)
|
|
776
|
+
return diep;
|
|
777
|
+
}
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
/** Kinderen van een blok: tekst inline samenvoegen, rijen naast elkaar zetten. */
|
|
781
|
+
async function tekenKinderen(ctx, n, vak, s) {
|
|
782
|
+
const tk = ctx.tk;
|
|
783
|
+
const cl = klassen(n);
|
|
784
|
+
const alle = n.kinderen.filter((k) => k.tag !== "#tekst" || (k.tekst ?? "").trim().length);
|
|
785
|
+
const absolute = alle.filter((k) => /absolute/.test(inline(k)["position"] ?? ""));
|
|
786
|
+
for (const k of absolute)
|
|
787
|
+
await tekenAbsoluut(ctx, k, s);
|
|
788
|
+
const kinderen = alle.filter((k) => !absolute.includes(k));
|
|
789
|
+
if (!kinderen.length)
|
|
790
|
+
return;
|
|
791
|
+
const tweeKolommen = cl.includes("grid2") || cl.includes("tweekolom");
|
|
792
|
+
const rijModus = cl.includes("kv") ||
|
|
793
|
+
cl.includes("karkop") ||
|
|
794
|
+
cl.includes("laagkop") ||
|
|
795
|
+
cl.includes("meta") ||
|
|
796
|
+
cl.includes("totalen") ||
|
|
797
|
+
cl.includes("regelrij");
|
|
798
|
+
if (tweeKolommen) {
|
|
799
|
+
const gap = 6 * MM;
|
|
800
|
+
const kolomBreedte = (vak.breedte - gap) / 2;
|
|
801
|
+
const links = kinderen.filter((_, i) => i % 2 === 0);
|
|
802
|
+
const rechts = kinderen.filter((_, i) => i % 2 === 1);
|
|
803
|
+
const startY = tk.y;
|
|
804
|
+
let eindL = startY;
|
|
805
|
+
for (const k of links) {
|
|
806
|
+
await tekenBlok(ctx, k, { x: vak.x, breedte: kolomBreedte }, s);
|
|
807
|
+
eindL = tk.y;
|
|
808
|
+
}
|
|
809
|
+
const naL = tk.y;
|
|
810
|
+
tk.y = startY;
|
|
811
|
+
let eindR = startY;
|
|
812
|
+
for (const k of rechts) {
|
|
813
|
+
await tekenBlok(ctx, k, { x: vak.x + kolomBreedte + gap, breedte: kolomBreedte }, s);
|
|
814
|
+
eindR = tk.y;
|
|
815
|
+
}
|
|
816
|
+
tk.y = Math.min(naL, eindL, eindR, tk.y);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
if (rijModus) {
|
|
820
|
+
const stukken = kinderen.map((k) => ({ knoop: k, stijl: stijlVan(k, s), tekst: plattTekst(k) }));
|
|
821
|
+
const breedtesTekst = stukken.map((p) => tk.breedteVan(p.tekst, p.stijl));
|
|
822
|
+
const hoogte = Math.max(...stukken.map((p) => regelHoogte(p.stijl)), regelHoogte(s));
|
|
823
|
+
const totaalTekst = breedtesTekst.reduce((a, b) => a + b, 0);
|
|
824
|
+
const gap = 3 * MM;
|
|
825
|
+
tk.zorgVoor(hoogte);
|
|
826
|
+
tk.y -= hoogte;
|
|
827
|
+
const yTekst = tk.y + s.grootte * 0.3;
|
|
828
|
+
const spreiden = (cl.includes("kv") || cl.includes("karkop") || cl.includes("laagkop")) && stukken.length > 1;
|
|
829
|
+
const rechtsUit = cl.includes("totalen") && inline(n)["justify-content"] === "flex-end";
|
|
830
|
+
if (spreiden) {
|
|
831
|
+
let x = vak.x;
|
|
832
|
+
stukken.forEach((p, i) => {
|
|
833
|
+
const laatste = i === stukken.length - 1;
|
|
834
|
+
const b = breedtesTekst[i];
|
|
835
|
+
const px = laatste ? vak.x + vak.breedte - b : x;
|
|
836
|
+
tekenRegel(tk, p.tekst, px, b, yTekst, p.stijl);
|
|
837
|
+
x += b + gap;
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
const start = rechtsUit
|
|
842
|
+
? vak.x + vak.breedte - (totaalTekst + gap * (stukken.length - 1))
|
|
843
|
+
: vak.x;
|
|
844
|
+
let x = Math.max(vak.x, start);
|
|
845
|
+
stukken.forEach((p, i) => {
|
|
846
|
+
tekenRegel(tk, p.tekst, x, breedtesTekst[i], yTekst, p.stijl);
|
|
847
|
+
x += breedtesTekst[i] + gap;
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
// gewone blokstroom, met inline-tekst samengevoegd
|
|
853
|
+
let buffer = [];
|
|
854
|
+
const leegBuffer = async () => {
|
|
855
|
+
if (!buffer.length)
|
|
856
|
+
return;
|
|
857
|
+
const tekst = buffer.map(plattTekst).join("");
|
|
858
|
+
const eersteVet = buffer[0] && heeft(buffer[0], "vet");
|
|
859
|
+
const stijl = eersteVet && buffer.length === 1 ? stijlVan(buffer[0], s) : s;
|
|
860
|
+
buffer = [];
|
|
861
|
+
if (tekst.trim().length) {
|
|
862
|
+
await tekenBlok(ctx, { tag: "#tekst", attrs: {}, kinderen: [], tekst }, vak, stijl);
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
for (const k of kinderen) {
|
|
866
|
+
if (isInline(k))
|
|
867
|
+
buffer.push(k);
|
|
868
|
+
else {
|
|
869
|
+
await leegBuffer();
|
|
870
|
+
await tekenBlok(ctx, k, vak, s);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
await leegBuffer();
|
|
874
|
+
}
|
|
875
|
+
// --- 5. afbeeldingen ophalen ------------------------------------------------
|
|
876
|
+
function base64NaarBytes(b64) {
|
|
877
|
+
const bin = typeof atob === "function" ? atob(b64) : Buffer.from(b64, "base64").toString("binary");
|
|
878
|
+
const out = new Uint8Array(bin.length);
|
|
879
|
+
for (let i = 0; i < bin.length; i++)
|
|
880
|
+
out[i] = bin.charCodeAt(i);
|
|
881
|
+
return out;
|
|
882
|
+
}
|
|
883
|
+
function verzamelBronnen(n, uit) {
|
|
884
|
+
if (n.tag === "img" && n.attrs["src"])
|
|
885
|
+
uit.add(n.attrs["src"]);
|
|
886
|
+
for (const k of n.kinderen)
|
|
887
|
+
verzamelBronnen(k, uit);
|
|
888
|
+
}
|
|
889
|
+
async function laadAfbeeldingen(pdf, wortel) {
|
|
890
|
+
const bronnen = new Set();
|
|
891
|
+
verzamelBronnen(wortel, bronnen);
|
|
892
|
+
const uit = new Map();
|
|
893
|
+
await Promise.all([...bronnen].slice(0, 20).map(async (src) => {
|
|
894
|
+
try {
|
|
895
|
+
let bytes;
|
|
896
|
+
let png = /\.png(\?|$)/i.test(src);
|
|
897
|
+
if (src.startsWith("data:")) {
|
|
898
|
+
const komma = src.indexOf(",");
|
|
899
|
+
png = /image\/png/i.test(src.slice(0, komma));
|
|
900
|
+
bytes = base64NaarBytes(src.slice(komma + 1));
|
|
901
|
+
}
|
|
902
|
+
else if (/^https?:/i.test(src)) {
|
|
903
|
+
const res = await fetch(src);
|
|
904
|
+
if (!res.ok)
|
|
905
|
+
return;
|
|
906
|
+
const type = res.headers.get("content-type") ?? "";
|
|
907
|
+
png = /png/i.test(type) || png;
|
|
908
|
+
bytes = new Uint8Array(await res.arrayBuffer());
|
|
909
|
+
}
|
|
910
|
+
else
|
|
911
|
+
return;
|
|
912
|
+
const afb = png ? await pdf.embedPng(bytes) : await pdf.embedJpg(bytes);
|
|
913
|
+
uit.set(src, afb);
|
|
914
|
+
}
|
|
915
|
+
catch {
|
|
916
|
+
// afbeelding overslaan: het document moet altijd afdrukbaar blijven
|
|
917
|
+
}
|
|
918
|
+
}));
|
|
919
|
+
return uit;
|
|
920
|
+
}
|
|
921
|
+
/** Zet print-HTML om naar een PDF en levert die als base64 (zonder prefix). */
|
|
922
|
+
export async function htmlNaarPdfBytes(opties) {
|
|
923
|
+
const bMm = Number(opties.breedteMm ?? 210);
|
|
924
|
+
const hMm = Number(opties.hoogteMm ?? 297);
|
|
925
|
+
const marge = Number(opties.margeMm ?? 10) * MM;
|
|
926
|
+
const breedte = (opties.landschap ? Math.max(bMm, hMm) : Math.min(bMm, hMm)) * MM;
|
|
927
|
+
const hoogte = (opties.landschap ? Math.min(bMm, hMm) : Math.max(bMm, hMm)) * MM;
|
|
928
|
+
const pdf = await PDFDocument.create();
|
|
929
|
+
const normaal = await pdf.embedFont(StandardFonts.Helvetica);
|
|
930
|
+
const vet = await pdf.embedFont(StandardFonts.HelveticaBold);
|
|
931
|
+
const wortel = ontleedHtml(opties.html);
|
|
932
|
+
const afbeeldingen = await laadAfbeeldingen(pdf, wortel);
|
|
933
|
+
const basis = basisGrootte(opties.html);
|
|
934
|
+
const tk = new Tekenaar(pdf, breedte, hoogte, marge, normaal, vet);
|
|
935
|
+
const ctx = { tk, afbeeldingen };
|
|
936
|
+
const stijl = { grootte: basis, vet: false, kleur: DONKER, uitlijning: "left" };
|
|
937
|
+
// vaste paginavoet (bijv. totalen op elke pagina)
|
|
938
|
+
const voetKnoop = zoekVoet(wortel);
|
|
939
|
+
if (voetKnoop) {
|
|
940
|
+
verwijder(wortel, voetKnoop);
|
|
941
|
+
tk.zetVoet(regelHoogte(stijl) + 2 * MM, () => { });
|
|
942
|
+
}
|
|
943
|
+
await tekenKinderen(ctx, wortel, { x: marge, breedte: breedte - marge * 2 }, stijl);
|
|
944
|
+
if (voetKnoop) {
|
|
945
|
+
const tekst = plattTekst(voetKnoop).trim();
|
|
946
|
+
for (const p of tk.paginas) {
|
|
947
|
+
p.drawLine({
|
|
948
|
+
start: { x: marge, y: marge + regelHoogte(stijl) + 1.5 * MM },
|
|
949
|
+
end: { x: breedte - marge, y: marge + regelHoogte(stijl) + 1.5 * MM },
|
|
950
|
+
thickness: 0.6,
|
|
951
|
+
color: LIJN,
|
|
952
|
+
});
|
|
953
|
+
p.drawText(veiligeTekst(tekst).slice(0, 300), {
|
|
954
|
+
x: marge,
|
|
955
|
+
y: marge + 2,
|
|
956
|
+
size: stijl.grootte,
|
|
957
|
+
font: normaal,
|
|
958
|
+
color: DONKER,
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (opties.paginanummers !== false && tk.paginas.length > 1) {
|
|
963
|
+
tk.paginas.forEach((p, i) => {
|
|
964
|
+
const t = `Pagina ${i + 1} van ${tk.paginas.length}`;
|
|
965
|
+
p.drawText(t, {
|
|
966
|
+
x: breedte - marge - normaal.widthOfTextAtSize(t, 7),
|
|
967
|
+
y: marge / 2,
|
|
968
|
+
size: 7,
|
|
969
|
+
font: normaal,
|
|
970
|
+
color: GRIJS,
|
|
971
|
+
});
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
tk.rondAf();
|
|
975
|
+
return pdf.save();
|
|
976
|
+
}
|
|
977
|
+
function basisGrootte(html) {
|
|
978
|
+
const m = /body\s*\{[^}]*font-size:\s*([\d.]+)pt/i.exec(html);
|
|
979
|
+
const n = m ? Number(m[1]) : NaN;
|
|
980
|
+
return Number.isFinite(n) && n > 3 && n < 30 ? n : 8.5;
|
|
981
|
+
}
|
|
982
|
+
function zoekVoet(n) {
|
|
983
|
+
for (const k of n.kinderen) {
|
|
984
|
+
if (heeft(k, "paginavoet"))
|
|
985
|
+
return k;
|
|
986
|
+
const diep = zoekVoet(k);
|
|
987
|
+
if (diep)
|
|
988
|
+
return diep;
|
|
989
|
+
}
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
function verwijder(n, doel) {
|
|
993
|
+
const i = n.kinderen.indexOf(doel);
|
|
994
|
+
if (i >= 0) {
|
|
995
|
+
n.kinderen.splice(i, 1);
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
998
|
+
return n.kinderen.some((k) => verwijder(k, doel));
|
|
999
|
+
}
|
|
1000
|
+
export function bytesNaarBase64(bytes) {
|
|
1001
|
+
let bin = "";
|
|
1002
|
+
const stap = 0x8000;
|
|
1003
|
+
for (let i = 0; i < bytes.length; i += stap) {
|
|
1004
|
+
bin += String.fromCharCode(...bytes.subarray(i, i + stap));
|
|
1005
|
+
}
|
|
1006
|
+
if (typeof btoa === "function")
|
|
1007
|
+
return btoa(bin);
|
|
1008
|
+
return Buffer.from(bin, "binary").toString("base64");
|
|
1009
|
+
}
|