@flowselections/layout-maker 1.28.0 → 1.31.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.
@@ -1 +1 @@
1
- {"version":3,"file":"htmlPdf.server.d.ts","sourceRoot":"","sources":["../../src/lib/htmlPdf.server.ts"],"names":[],"mappings":"AAWA,OAAO,EAOL,KAAK,GAAG,EACT,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,EAAE,oBAAoB,CAAC;AAIpC,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAuBD,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAuC/C;AAID,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,GAAG,CAAC;IACX,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;CACzC;AAg4BD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,+EAA+E;AAC/E,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CA8DrF;AA0BD,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAQzD"}
1
+ {"version":3,"file":"htmlPdf.server.d.ts","sourceRoot":"","sources":["../../src/lib/htmlPdf.server.ts"],"names":[],"mappings":"AAWA,OAAO,EAOL,KAAK,GAAG,EACT,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,EAAE,oBAAoB,CAAC;AAIpC,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAuBD,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAuC/C;AAUD,UAAU,SAAS;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AACD,UAAU,QAAQ;IAChB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMpD;AA2BD,yFAAyF;AACzF,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,EAAE,CAgClD;AAqBD,6EAA6E;AAC7E,wBAAgB,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAiBlE;AAKD,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,GAAG,CAAC;IACX,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACxC,mDAAmD;IACnD,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AA4gCD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,+EAA+E;AAC/E,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CA+DrF;AA0BD,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAQzD"}
@@ -73,6 +73,124 @@ export function ontleedHtml(html) {
73
73
  }
74
74
  return wortel;
75
75
  }
76
+ /** Haalt de inhoud van alle <style>-blokken uit een HTML-document. */
77
+ export function leesStylesheets(html) {
78
+ let uit = "";
79
+ const re = /<style[^>]*>([\s\S]*?)<\/style>/gi;
80
+ let m;
81
+ while ((m = re.exec(html)))
82
+ uit += `\n${m[1] ?? ""}`;
83
+ return uit;
84
+ }
85
+ function leesDecls(blok) {
86
+ const uit = {};
87
+ for (const deel of blok.split(";")) {
88
+ const i = deel.indexOf(":");
89
+ if (i <= 0)
90
+ continue;
91
+ const naam = deel.slice(0, i).trim().toLowerCase();
92
+ const waarde = deel.slice(i + 1).replace(/!important/i, "").trim();
93
+ if (naam && waarde)
94
+ uit[naam] = waarde;
95
+ }
96
+ return uit;
97
+ }
98
+ function leesSelector(sel) {
99
+ const schoon = sel.trim();
100
+ if (!schoon || /[:\[\]>+~#]/.test(schoon))
101
+ return null;
102
+ const keten = [];
103
+ for (const stuk of schoon.split(/\s+/)) {
104
+ const klassen = (stuk.match(/\.[-\w]+/g) ?? []).map((k) => k.slice(1));
105
+ const tag = stuk.replace(/\.[-\w]+/g, "").trim().toLowerCase();
106
+ if (!tag && !klassen.length)
107
+ return null;
108
+ keten.push({ tag: tag && tag !== "*" ? tag : undefined, klassen });
109
+ }
110
+ return keten.length ? keten : null;
111
+ }
112
+ /** Ontleedt een stylesheet naar regels. At-rules (@page, @media) worden overgeslagen. */
113
+ export function ontleedCss(css) {
114
+ const tekst = css.replace(/\/\*[\s\S]*?\*\//g, "");
115
+ const regels = [];
116
+ let i = 0;
117
+ let volgorde = 0;
118
+ while (i < tekst.length) {
119
+ const open = tekst.indexOf("{", i);
120
+ if (open < 0)
121
+ break;
122
+ const kop = tekst.slice(i, open).trim();
123
+ // bijbehorend sluithaakje zoeken (at-rules kunnen geneste blokken hebben)
124
+ let diepte = 0;
125
+ let j = open;
126
+ for (; j < tekst.length; j++) {
127
+ if (tekst[j] === "{")
128
+ diepte++;
129
+ else if (tekst[j] === "}") {
130
+ diepte--;
131
+ if (!diepte)
132
+ break;
133
+ }
134
+ }
135
+ const inhoud = tekst.slice(open + 1, j);
136
+ i = j + 1;
137
+ if (kop.startsWith("@"))
138
+ continue;
139
+ const decls = leesDecls(inhoud);
140
+ if (!Object.keys(decls).length)
141
+ continue;
142
+ for (const sel of kop.split(",")) {
143
+ const keten = leesSelector(sel);
144
+ if (!keten)
145
+ continue;
146
+ const gewicht = keten.reduce((n, s) => n + s.klassen.length * 10 + (s.tag ? 1 : 0), 0);
147
+ regels.push({ keten, gewicht, volgorde: volgorde++, decls });
148
+ }
149
+ }
150
+ return regels;
151
+ }
152
+ function past(n, s) {
153
+ if (s.tag && n.tag !== s.tag)
154
+ return false;
155
+ if (!s.klassen.length)
156
+ return true;
157
+ const eigen = klassen(n);
158
+ return s.klassen.every((k) => eigen.includes(k));
159
+ }
160
+ function ketenPast(keten, pad) {
161
+ const laatste = pad[pad.length - 1];
162
+ if (!past(laatste, keten[keten.length - 1]))
163
+ return false;
164
+ let idx = keten.length - 2;
165
+ for (let p = pad.length - 2; p >= 0 && idx >= 0; p--) {
166
+ if (past(pad[p], keten[idx]))
167
+ idx--;
168
+ }
169
+ return idx < 0;
170
+ }
171
+ const BEREKEND = new WeakMap();
172
+ /** Rekent de stylesheet uit voor elk element en bewaart dat bij de knoop. */
173
+ export function stempelCss(wortel, regels) {
174
+ if (!regels.length)
175
+ return;
176
+ const loop = (n, pad) => {
177
+ const eigenPad = [...pad, n];
178
+ if (n.tag !== "#tekst") {
179
+ const raak = regels
180
+ .filter((r) => ketenPast(r.keten, eigenPad))
181
+ .sort((a, b) => a.gewicht - b.gewicht || a.volgorde - b.volgorde);
182
+ if (raak.length) {
183
+ const uit = {};
184
+ for (const r of raak)
185
+ Object.assign(uit, r.decls);
186
+ BEREKEND.set(n, uit);
187
+ }
188
+ }
189
+ for (const k of n.kinderen)
190
+ loop(k, eigenPad);
191
+ };
192
+ loop(wortel, []);
193
+ }
76
194
  const GRIJS = rgb(0.42, 0.45, 0.5);
77
195
  const DONKER = rgb(0.07, 0.09, 0.15);
78
196
  const LIJN = rgb(0.82, 0.84, 0.86);
@@ -89,6 +207,12 @@ function inline(n) {
89
207
  }
90
208
  return out;
91
209
  }
210
+ /** Alle stijlwaarden van een element: stylesheet eerst, inline wint. */
211
+ function eig(n) {
212
+ const uit = BEREKEND.get(n);
213
+ const eigen = inline(n);
214
+ return uit ? { ...uit, ...eigen } : eigen;
215
+ }
92
216
  function hexNaarRgb(v) {
93
217
  const m = /^#?([0-9a-f]{6})$/i.exec(v.trim());
94
218
  if (m) {
@@ -134,7 +258,9 @@ function stijlVan(n, ouder) {
134
258
  const cl = klassen(n);
135
259
  if (n.tag === "th" || cl.includes("vet") || n.tag === "b" || n.tag === "strong")
136
260
  s.vet = true;
137
- if (cl.includes("sectitel")) {
261
+ // De stylesheet is leidend voor .sectitel; alleen zonder opmaakregels valt de
262
+ // motor terug op de bekende grijze kop.
263
+ if (cl.includes("sectitel") && !Object.keys(eig(n)).length) {
138
264
  s.grootte = ouder.grootte * 0.9;
139
265
  s.vet = true;
140
266
  s.kleur = GRIJS;
@@ -151,7 +277,7 @@ function stijlVan(n, ouder) {
151
277
  s.vet = true;
152
278
  if (cl.includes("karkop") || cl.includes("laagkop") || cl.includes("totalen"))
153
279
  s.vet = true;
154
- const st = inline(n);
280
+ const st = eig(n);
155
281
  const fs = st["font-size"];
156
282
  if (fs) {
157
283
  const g = /%$/.test(fs) ? (parseFloat(fs) / 100) * ouder.grootte : lengte(fs);
@@ -166,6 +292,11 @@ function stijlVan(n, ouder) {
166
292
  const ta = st["text-align"];
167
293
  if (ta === "left" || ta === "center" || ta === "right")
168
294
  s.uitlijning = ta;
295
+ const tt = st["text-transform"];
296
+ if (tt === "uppercase")
297
+ s.hoofdletters = true;
298
+ else if (tt === "none" || tt === "lowercase")
299
+ s.hoofdletters = false;
169
300
  return s;
170
301
  }
171
302
  // --- 3. tekenhulp -----------------------------------------------------------
@@ -192,6 +323,9 @@ function veiligeTekst(t) {
192
323
  }
193
324
  return out;
194
325
  }
326
+ function metTransform(t, s) {
327
+ return s.hoofdletters ? t.toUpperCase() : t;
328
+ }
195
329
  class Tekenaar {
196
330
  constructor(pdf, breedte, hoogte, marge, normaal, vet) {
197
331
  this.pdf = pdf;
@@ -203,6 +337,8 @@ class Tekenaar {
203
337
  this.paginas = [];
204
338
  this.voetHoogte = 0;
205
339
  this.voetTekenaar = null;
340
+ /** Zolang dit aanstaat komt er geen nieuwe pagina bij (voet tekenen). */
341
+ this.vast = false;
206
342
  this.pagina = pdf.addPage([breedte, hoogte]);
207
343
  this.paginas.push(this.pagina);
208
344
  this.y = hoogte - marge;
@@ -211,7 +347,7 @@ class Tekenaar {
211
347
  return s.vet ? this.vet : this.normaal;
212
348
  }
213
349
  breedteVan(t, s) {
214
- return this.font(s).widthOfTextAtSize(veiligeTekst(t), s.grootte);
350
+ return this.font(s).widthOfTextAtSize(metTransform(veiligeTekst(t), s), s.grootte);
215
351
  }
216
352
  get bodem() {
217
353
  return this.marge + this.voetHoogte;
@@ -224,6 +360,8 @@ class Tekenaar {
224
360
  this.voetTekenaar = teken;
225
361
  }
226
362
  nieuwePagina() {
363
+ if (this.vast)
364
+ return;
227
365
  this.pagina = this.pdf.addPage([this.breedte, this.hoogte]);
228
366
  this.paginas.push(this.pagina);
229
367
  this.y = this.hoogte - this.marge;
@@ -260,6 +398,26 @@ class Tekenaar {
260
398
  this.voetHoogte = bewaard.voet;
261
399
  return hoogte;
262
400
  }
401
+ /** Tekent iets op een bestaande pagina, bijvoorbeeld de vaste voet. */
402
+ async opPagina(p, y, teken) {
403
+ const bewaard = { pagina: this.pagina, y: this.y, paginas: this.paginas, voet: this.voetHoogte };
404
+ this.pagina = p;
405
+ this.y = y;
406
+ this.paginas = [p];
407
+ this.voetHoogte = 0;
408
+ this.vast = true;
409
+ try {
410
+ await teken();
411
+ }
412
+ catch {
413
+ // een voet mag het document nooit blokkeren
414
+ }
415
+ this.pagina = bewaard.pagina;
416
+ this.y = bewaard.y;
417
+ this.paginas = bewaard.paginas;
418
+ this.voetHoogte = bewaard.voet;
419
+ this.vast = false;
420
+ }
263
421
  rondAf() {
264
422
  if (!this.voetTekenaar)
265
423
  return;
@@ -291,7 +449,7 @@ function breekAf(tk, tekst, s, breedte) {
291
449
  return regels;
292
450
  }
293
451
  function tekenRegel(tk, regel, x, breedte, y, s) {
294
- const t = veiligeTekst(regel);
452
+ const t = metTransform(veiligeTekst(regel), s);
295
453
  const w = tk.breedteVan(t, s);
296
454
  const dx = s.uitlijning === "right" ? breedte - w : s.uitlijning === "center" ? (breedte - w) / 2 : 0;
297
455
  tk.pagina.drawText(t, {
@@ -306,7 +464,7 @@ const inlineTags = new Set(["#tekst", "span", "b", "strong", "em", "i", "small",
306
464
  function isInline(n) {
307
465
  if (!inlineTags.has(n.tag))
308
466
  return false;
309
- const st = inline(n);
467
+ const st = eig(n);
310
468
  return !st["height"] && !st["width"];
311
469
  }
312
470
  /** Verzamelt de platte tekst van een knoop (met spatie tussen losse stukjes). */
@@ -354,7 +512,7 @@ function kolomBreedtes(tabel, rijen, totaal) {
354
512
  if (!aantal)
355
513
  return [totaal];
356
514
  if (cols.length === aantal) {
357
- const rauw = cols.map((c) => lengte(inline(c)["width"] ?? c.attrs["width"], totaal));
515
+ const rauw = cols.map((c) => lengte(eig(c)["width"] ?? c.attrs["width"], totaal));
358
516
  const som = rauw.reduce((a, b) => a + (b ?? 0), 0);
359
517
  if (som > 1) {
360
518
  const factor = totaal / som;
@@ -366,7 +524,7 @@ function kolomBreedtes(tabel, rijen, totaal) {
366
524
  async function tekenBlok(ctx, n, vak, ouderStijl) {
367
525
  const tk = ctx.tk;
368
526
  const s = stijlVan(n, ouderStijl);
369
- const st = inline(n);
527
+ const st = eig(n);
370
528
  const cl = klassen(n);
371
529
  if (n.tag === "#tekst") {
372
530
  const regels = breekAf(tk, n.tekst ?? "", s, vak.breedte);
@@ -475,16 +633,26 @@ async function tekenBlok(ctx, n, vak, ouderStijl) {
475
633
  }
476
634
  return;
477
635
  }
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)
636
+ // kader / achtergrond / afstand — uit de stylesheet, met de bekende klassen
637
+ // als terugval voor HTML zonder eigen opmaak.
638
+ const randRondom = randVan(st["border"]);
639
+ const randOnder = randVan(st["border-bottom"]);
640
+ const randBoven = randVan(st["border-top"]);
641
+ const kader = !!randRondom || cl.includes("kader") || cl.includes("rand") || cl.includes("omkaderd");
642
+ const achtergrond = vlakKleur(st["background"] ?? st["background-color"]);
643
+ const padding = lengte(st["padding"]?.split(/\s+/)[0]) ?? (kader ? 1.5 * MM : 0);
644
+ const margeOnder = lengte(st["margin-bottom"]);
645
+ const margeBoven = lengte(st["margin-top"]);
646
+ const breekVoor = cl.includes("breekvoor") || /page|always/.test(st["break-before"] ?? st["page-break-before"] ?? "");
647
+ if (breekVoor && tk.y < tk.hoogte - tk.marge - 1)
483
648
  tk.nieuwePagina();
649
+ if (margeBoven)
650
+ tk.y -= margeBoven;
651
+ const heelHouden = cl.includes("heelhouden") || /avoid/.test(st["break-inside"] ?? st["page-break-inside"] ?? "");
484
652
  const binnenVak = { x: vak.x + padding, breedte: vak.breedte - padding * 2 };
485
- if (!ctx.meten && (cl.includes("heelhouden") || achtergrond)) {
653
+ if (!ctx.meten && (heelHouden || achtergrond)) {
486
654
  const nodig = (await meetHoogte(ctx, n, binnenVak, s)) + padding * 2;
487
- if (cl.includes("heelhouden") && nodig > tk.ruimte && nodig < tk.hoogte - tk.marge * 2) {
655
+ if (heelHouden && nodig > tk.ruimte && nodig < tk.hoogte - tk.marge * 2) {
488
656
  tk.nieuwePagina();
489
657
  }
490
658
  if (achtergrond) {
@@ -500,10 +668,10 @@ async function tekenBlok(ctx, n, vak, ouderStijl) {
500
668
  }
501
669
  const startY = tk.y;
502
670
  const startPagina = tk.pagina;
503
- if (kader || achtergrond)
671
+ if (padding)
504
672
  tk.y -= padding;
505
673
  await tekenKinderen(ctx, n, binnenVak, s);
506
- if (kader || achtergrond)
674
+ if (padding)
507
675
  tk.y -= padding;
508
676
  if (kader && tk.pagina === startPagina && startY - tk.y > 0) {
509
677
  startPagina.drawRectangle({
@@ -511,36 +679,62 @@ async function tekenBlok(ctx, n, vak, ouderStijl) {
511
679
  y: tk.y,
512
680
  width: vak.breedte,
513
681
  height: startY - tk.y,
514
- borderWidth: 0.6,
515
- borderColor: LIJN,
682
+ borderWidth: randRondom?.dikte ?? 0.6,
683
+ borderColor: randRondom?.kleur ?? LIJN,
516
684
  });
517
685
  }
518
- if (cl.includes("onder") || cl.includes("laagkop")) {
686
+ if (randOnder || cl.includes("onder") || cl.includes("laagkop")) {
519
687
  tk.pagina.drawLine({
520
688
  start: { x: vak.x, y: tk.y + 0.6 * MM },
521
689
  end: { x: vak.x + vak.breedte, y: tk.y + 0.6 * MM },
522
- thickness: 0.5,
523
- color: LIJN,
690
+ thickness: randOnder?.dikte ?? 0.5,
691
+ color: randOnder?.kleur ?? LIJN,
524
692
  });
525
693
  }
526
- if (cl.includes("totalen")) {
694
+ if (randBoven || cl.includes("totalen")) {
527
695
  tk.pagina.drawLine({
528
696
  start: { x: vak.x, y: startY },
529
697
  end: { x: vak.x + vak.breedte, y: startY },
530
- thickness: 0.7,
531
- color: LIJN,
698
+ thickness: randBoven?.dikte ?? 0.7,
699
+ color: randBoven?.kleur ?? LIJN,
532
700
  });
533
701
  }
534
- if (cl.includes("blok") || cl.includes("groep") || cl.includes("karblok") || cl.includes("laag"))
702
+ if (margeOnder !== null)
703
+ tk.y -= margeOnder;
704
+ else if (cl.includes("blok") || cl.includes("groep") || cl.includes("karblok") || cl.includes("laag"))
535
705
  tk.y -= 1.6 * MM;
536
706
  }
707
+ /** Leest `0.25mm solid #d1d5db` (of px/pt) uit een border-eigenschap. */
708
+ function randVan(v) {
709
+ if (!v || /^none$/i.test(v.trim()))
710
+ return null;
711
+ const m = /(-?[\d.]+\s*(?:mm|pt|px))?\s*(solid|dashed|dotted)?\s*(#[0-9a-f]{3,6}|black|white)?/i.exec(v);
712
+ if (!m || (!m[1] && !m[3]))
713
+ return null;
714
+ return { dikte: Math.max(0.3, lengte(m[1]?.trim()) ?? 0.5), kleur: hexNaarRgb(m[3] ?? "") ?? LIJN };
715
+ }
716
+ /** Achtergrondkleur uit een background(-color); verlopen tellen niet mee. */
717
+ function vlakKleur(v) {
718
+ if (!v || /gradient/i.test(v))
719
+ return null;
720
+ return hexNaarRgb(v.split(/\s+/)[0] ?? "");
721
+ }
537
722
  async function tekenAfbeelding(ctx, n, vak, s) {
538
723
  const tk = ctx.tk;
539
724
  const src = n.attrs["src"] ?? "";
540
725
  const afb = ctx.afbeeldingen.get(src);
541
- const hoogte = lengte(inline(n)["height"]) ?? 18 * MM;
726
+ const hoogte = lengte(eig(n)["height"]) ?? 18 * MM;
542
727
  if (!afb) {
543
- tk.y -= hoogte + 1 * MM;
728
+ // Een leeg vlak vertelt niemand dat er iets misging: melding op de afdruk.
729
+ const reden = ctx.beeldFouten?.get(src) ?? "Afbeelding niet beschikbaar";
730
+ const melding = { ...s, grootte: Math.min(s.grootte, 7), vet: false, kleur: rgb(0.7, 0.15, 0.15) };
731
+ const regels = breekAf(tk, `[${reden}]`, melding, vak.breedte);
732
+ tk.zorgVoor(Math.max(hoogte, regels.length * regelHoogte(melding)) + 1 * MM);
733
+ for (const r of regels) {
734
+ tk.y -= regelHoogte(melding);
735
+ tekenRegel(tk, r, vak.x, vak.breedte, tk.y + melding.grootte * 0.25, melding);
736
+ }
737
+ tk.y -= 1 * MM;
544
738
  return;
545
739
  }
546
740
  const schaal = hoogte / afb.height;
@@ -567,6 +761,13 @@ async function tekenTabel(ctx, tabel, vak, s) {
567
761
  const padX = compact ? 0.8 * MM : 1 * MM;
568
762
  const randen = heeft(tabel, "rand");
569
763
  const kopRij = rijen.find((r) => r.kinderen.some((c) => c.tag === "th")) ?? null;
764
+ /** Celrand onderaan: uit de stylesheet, met de klasse `onder` als terugval. */
765
+ const celRandOnder = (c) => randVan(eig(c)["border-bottom"]) ??
766
+ randVan(eig(c)["border"]) ??
767
+ (klassen(c).includes("onder") || klassen(c).includes("rand") ? { dikte: 0.5, kleur: LIJN } : null);
768
+ // Zodra de cellen zelf randen dragen, is de automatische lijn onder de koprij
769
+ // dubbelop en landt hij op de verkeerde regel.
770
+ const eigenCelRanden = rijen.some((r) => r.kinderen.some((c) => (c.tag === "td" || c.tag === "th") && !!celRandOnder(c)));
570
771
  const tekenRij = (rij, herhaald = false) => {
571
772
  const cellen = rij.kinderen.filter((c) => c.tag === "td" || c.tag === "th");
572
773
  const cellenStijl = cellen.map((c) => stijlVan(c, s));
@@ -607,14 +808,28 @@ async function tekenTabel(ctx, tabel, vak, s) {
607
808
  borderColor: LIJN,
608
809
  });
609
810
  }
811
+ else {
812
+ const cr = celRandOnder(c);
813
+ if (cr) {
814
+ tk.pagina.drawLine({
815
+ start: { x, y: bovenY - hoogte },
816
+ end: { x: x + b, y: bovenY - hoogte },
817
+ thickness: cr.dikte,
818
+ color: cr.kleur,
819
+ });
820
+ }
821
+ }
610
822
  x += b;
611
823
  });
612
- if (!randen && (rij === kopRij || heeft(rij, "onder"))) {
824
+ const rijRand = randVan(eig(rij)["border-bottom"]) ?? (heeft(rij, "onder") ? { dikte: 0.5, kleur: LIJN } : null);
825
+ const automatisch = !eigenCelRanden && rij === kopRij ? { dikte: 0.5, kleur: LIJN } : null;
826
+ const lijn = !randen ? (rijRand ?? automatisch) : null;
827
+ if (lijn) {
613
828
  tk.pagina.drawLine({
614
829
  start: { x: vak.x, y: bovenY - hoogte },
615
830
  end: { x: vak.x + vak.breedte, y: bovenY - hoogte },
616
- thickness: 0.5,
617
- color: LIJN,
831
+ thickness: lijn.dikte,
832
+ color: lijn.kleur,
618
833
  });
619
834
  }
620
835
  tk.y = bovenY - hoogte;
@@ -626,7 +841,7 @@ async function tekenTabel(ctx, tabel, vak, s) {
626
841
  /** Tekent een element dat met vaste coördinaten geplaatst is (position:absolute). */
627
842
  async function tekenAbsoluut(ctx, n, s) {
628
843
  const tk = ctx.tk;
629
- const st = inline(n);
844
+ const st = eig(n);
630
845
  const eigen = stijlVan(n, s);
631
846
  const x = lengte(st["left"]) ?? 0;
632
847
  const top = lengte(st["top"]) ?? 0;
@@ -755,7 +970,7 @@ async function tekenAbsoluut(ctx, n, s) {
755
970
  }
756
971
  /** Zoekt een streepjes- of blokjespatroon (barcode/QR) in de knoop of kinderen. */
757
972
  function zoekGradient(n) {
758
- const css = `${inline(n)["background"] ?? ""} ${inline(n)["background-image"] ?? ""}`;
973
+ const css = `${eig(n)["background"] ?? ""} ${eig(n)["background-image"] ?? ""}`;
759
974
  if (/repeating-linear-gradient/i.test(css))
760
975
  return "streepjes";
761
976
  if (/repeating-conic-gradient/i.test(css))
@@ -782,21 +997,28 @@ async function tekenKinderen(ctx, n, vak, s) {
782
997
  const tk = ctx.tk;
783
998
  const cl = klassen(n);
784
999
  const alle = n.kinderen.filter((k) => k.tag !== "#tekst" || (k.tekst ?? "").trim().length);
785
- const absolute = alle.filter((k) => /absolute/.test(inline(k)["position"] ?? ""));
1000
+ const absolute = alle.filter((k) => /absolute/.test(eig(k)["position"] ?? ""));
786
1001
  for (const k of absolute)
787
1002
  await tekenAbsoluut(ctx, k, s);
788
1003
  const kinderen = alle.filter((k) => !absolute.includes(k));
789
1004
  if (!kinderen.length)
790
1005
  return;
791
- const tweeKolommen = cl.includes("grid2") || cl.includes("tweekolom");
792
- const rijModus = cl.includes("kv") ||
1006
+ const st = eig(n);
1007
+ const display = st["display"] ?? "";
1008
+ const kolommen = st["grid-template-columns"] ?? "";
1009
+ const gapCss = lengte((st["gap"] ?? "").split(/\s+/).slice(-1)[0]);
1010
+ const tweeKolommen = cl.includes("grid2") ||
1011
+ cl.includes("tweekolom") ||
1012
+ (/grid/.test(display) && kolommen.trim().split(/\s+/).length === 2);
1013
+ const rijModus = /flex/.test(display) ||
1014
+ cl.includes("kv") ||
793
1015
  cl.includes("karkop") ||
794
1016
  cl.includes("laagkop") ||
795
1017
  cl.includes("meta") ||
796
1018
  cl.includes("totalen") ||
797
1019
  cl.includes("regelrij");
798
1020
  if (tweeKolommen) {
799
- const gap = 6 * MM;
1021
+ const gap = gapCss ?? 6 * MM;
800
1022
  const kolomBreedte = (vak.breedte - gap) / 2;
801
1023
  const links = kinderen.filter((_, i) => i % 2 === 0);
802
1024
  const rechts = kinderen.filter((_, i) => i % 2 === 1);
@@ -821,12 +1043,17 @@ async function tekenKinderen(ctx, n, vak, s) {
821
1043
  const breedtesTekst = stukken.map((p) => tk.breedteVan(p.tekst, p.stijl));
822
1044
  const hoogte = Math.max(...stukken.map((p) => regelHoogte(p.stijl)), regelHoogte(s));
823
1045
  const totaalTekst = breedtesTekst.reduce((a, b) => a + b, 0);
824
- const gap = 3 * MM;
1046
+ const gap = gapCss ?? 3 * MM;
825
1047
  tk.zorgVoor(hoogte);
826
1048
  tk.y -= hoogte;
827
1049
  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";
1050
+ const verdeel = st["justify-content"] ?? "";
1051
+ const spreiden = stukken.length > 1 &&
1052
+ (verdeel === "space-between" ||
1053
+ verdeel === "space-around" ||
1054
+ ((!verdeel || verdeel === "normal") &&
1055
+ (cl.includes("kv") || cl.includes("karkop") || cl.includes("laagkop"))));
1056
+ const rechtsUit = verdeel === "flex-end" || verdeel === "right" || verdeel === "end";
830
1057
  if (spreiden) {
831
1058
  let x = vak.x;
832
1059
  stukken.forEach((p, i) => {
@@ -889,11 +1116,16 @@ function verzamelBronnen(n, uit) {
889
1116
  async function laadAfbeeldingen(pdf, wortel) {
890
1117
  const bronnen = new Set();
891
1118
  verzamelBronnen(wortel, bronnen);
892
- const uit = new Map();
1119
+ const beelden = new Map();
1120
+ const fouten = new Map();
893
1121
  await Promise.all([...bronnen].slice(0, 20).map(async (src) => {
894
1122
  try {
895
1123
  let bytes;
896
1124
  let png = /\.png(\?|$)/i.test(src);
1125
+ if (/^data:image\/svg|\.svg(\?|$)/i.test(src)) {
1126
+ fouten.set(src, "SVG-afbeelding wordt niet ondersteund - gebruik PNG of JPG");
1127
+ return;
1128
+ }
897
1129
  if (src.startsWith("data:")) {
898
1130
  const komma = src.indexOf(",");
899
1131
  png = /image\/png/i.test(src.slice(0, komma));
@@ -901,22 +1133,30 @@ async function laadAfbeeldingen(pdf, wortel) {
901
1133
  }
902
1134
  else if (/^https?:/i.test(src)) {
903
1135
  const res = await fetch(src);
904
- if (!res.ok)
1136
+ if (!res.ok) {
1137
+ fouten.set(src, `Afbeelding niet opgehaald (${res.status})`);
905
1138
  return;
1139
+ }
906
1140
  const type = res.headers.get("content-type") ?? "";
1141
+ if (/svg/i.test(type)) {
1142
+ fouten.set(src, "SVG-afbeelding wordt niet ondersteund - gebruik PNG of JPG");
1143
+ return;
1144
+ }
907
1145
  png = /png/i.test(type) || png;
908
1146
  bytes = new Uint8Array(await res.arrayBuffer());
909
1147
  }
910
- else
1148
+ else {
1149
+ fouten.set(src, "Onbekende afbeeldingsbron");
911
1150
  return;
1151
+ }
912
1152
  const afb = png ? await pdf.embedPng(bytes) : await pdf.embedJpg(bytes);
913
- uit.set(src, afb);
1153
+ beelden.set(src, afb);
914
1154
  }
915
- catch {
916
- // afbeelding overslaan: het document moet altijd afdrukbaar blijven
1155
+ catch (e) {
1156
+ fouten.set(src, e instanceof Error ? `Afbeelding onleesbaar: ${e.message}` : "Afbeelding onleesbaar");
917
1157
  }
918
1158
  }));
919
- return uit;
1159
+ return { beelden, fouten };
920
1160
  }
921
1161
  /** Zet print-HTML om naar een PDF en levert die als base64 (zonder prefix). */
922
1162
  export async function htmlNaarPdfBytes(opties) {
@@ -929,37 +1169,37 @@ export async function htmlNaarPdfBytes(opties) {
929
1169
  const normaal = await pdf.embedFont(StandardFonts.Helvetica);
930
1170
  const vet = await pdf.embedFont(StandardFonts.HelveticaBold);
931
1171
  const wortel = ontleedHtml(opties.html);
932
- const afbeeldingen = await laadAfbeeldingen(pdf, wortel);
1172
+ // De opmaak uit het <style>-blok wordt hier per element uitgerekend, zodat
1173
+ // het document er hetzelfde uitziet als in het voorbeeld op het scherm.
1174
+ stempelCss(wortel, ontleedCss(leesStylesheets(opties.html)));
1175
+ const { beelden: afbeeldingen, fouten: beeldFouten } = await laadAfbeeldingen(pdf, wortel);
933
1176
  const basis = basisGrootte(opties.html);
934
1177
  const tk = new Tekenaar(pdf, breedte, hoogte, marge, normaal, vet);
935
- const ctx = { tk, afbeeldingen };
1178
+ const ctx = { tk, afbeeldingen, beeldFouten };
936
1179
  const stijl = { grootte: basis, vet: false, kleur: DONKER, uitlijning: "left" };
937
- // vaste paginavoet (bijv. totalen op elke pagina)
1180
+ // vaste paginavoet (bijv. totalen op elke pagina) — als volwaardig blok
1181
+ const voetVak = { x: marge, breedte: breedte - marge * 2 };
938
1182
  const voetKnoop = zoekVoet(wortel);
939
- if (voetKnoop) {
1183
+ const voetOuder = voetKnoop
1184
+ ? { tag: "div", attrs: {}, kinderen: [voetKnoop] }
1185
+ : null;
1186
+ let voetHoogte = 0;
1187
+ if (voetOuder) {
940
1188
  verwijder(wortel, voetKnoop);
941
- tk.zetVoet(regelHoogte(stijl) + 2 * MM, () => { });
1189
+ voetHoogte = await tk.meet(() => tekenKinderen(ctx, voetOuder, voetVak, stijl));
1190
+ tk.zetVoet(voetHoogte + 2 * MM, () => { });
942
1191
  }
943
1192
  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
- });
1193
+ if (voetOuder) {
1194
+ for (const p of [...tk.paginas]) {
1195
+ await tk.opPagina(p, marge + voetHoogte + 1 * MM, () => tekenKinderen(ctx, voetOuder, voetVak, stijl));
960
1196
  }
961
1197
  }
962
- if (opties.paginanummers !== false && tk.paginas.length > 1) {
1198
+ // De layout bepaalt of paginanummers meegaan; zonder voorkeur alleen bij
1199
+ // meerdere pagina's.
1200
+ const uitMeta = /name=["']pdf-paginanummers["']\s+content=["']ja["']/i.test(opties.html);
1201
+ const nummers = opties.paginanummers ?? (uitMeta ? true : tk.paginas.length > 1);
1202
+ if (nummers) {
963
1203
  tk.paginas.forEach((p, i) => {
964
1204
  const t = `Pagina ${i + 1} van ${tk.paginas.length}`;
965
1205
  p.drawText(t, {
@@ -1 +1 @@
1
- {"version":3,"file":"orderData.server.d.ts","sourceRoot":"","sources":["../../src/lib/orderData.server.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE/B,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAwBtD,mEAAmE;AACnE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAKvD;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG;IACrD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAgCA;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG;IAC7C,KAAK,EAAE,WAAW,CAAC;IACnB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAMA;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,OAAO,EACX,MAAM,EAAE;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC7E,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CA2OrB;AAkCD;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAGhG"}
1
+ {"version":3,"file":"orderData.server.d.ts","sourceRoot":"","sources":["../../src/lib/orderData.server.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE/B,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAwBtD,mEAAmE;AACnE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAKvD;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG;IACrD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAgCA;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG;IAC7C,KAAK,EAAE,WAAW,CAAC;IACnB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAMA;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,OAAO,EACX,MAAM,EAAE;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC7E,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CA2OrB;AA+DD;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAGhG"}