@flowselections/layout-maker 1.26.0 → 1.29.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;AAw9BD,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,CAiErF;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) {
@@ -151,7 +275,7 @@ function stijlVan(n, ouder) {
151
275
  s.vet = true;
152
276
  if (cl.includes("karkop") || cl.includes("laagkop") || cl.includes("totalen"))
153
277
  s.vet = true;
154
- const st = inline(n);
278
+ const st = eig(n);
155
279
  const fs = st["font-size"];
156
280
  if (fs) {
157
281
  const g = /%$/.test(fs) ? (parseFloat(fs) / 100) * ouder.grootte : lengte(fs);
@@ -166,6 +290,11 @@ function stijlVan(n, ouder) {
166
290
  const ta = st["text-align"];
167
291
  if (ta === "left" || ta === "center" || ta === "right")
168
292
  s.uitlijning = ta;
293
+ const tt = st["text-transform"];
294
+ if (tt === "uppercase")
295
+ s.hoofdletters = true;
296
+ else if (tt === "none" || tt === "lowercase")
297
+ s.hoofdletters = false;
169
298
  return s;
170
299
  }
171
300
  // --- 3. tekenhulp -----------------------------------------------------------
@@ -192,6 +321,9 @@ function veiligeTekst(t) {
192
321
  }
193
322
  return out;
194
323
  }
324
+ function metTransform(t, s) {
325
+ return s.hoofdletters ? t.toUpperCase() : t;
326
+ }
195
327
  class Tekenaar {
196
328
  constructor(pdf, breedte, hoogte, marge, normaal, vet) {
197
329
  this.pdf = pdf;
@@ -211,7 +343,7 @@ class Tekenaar {
211
343
  return s.vet ? this.vet : this.normaal;
212
344
  }
213
345
  breedteVan(t, s) {
214
- return this.font(s).widthOfTextAtSize(veiligeTekst(t), s.grootte);
346
+ return this.font(s).widthOfTextAtSize(metTransform(veiligeTekst(t), s), s.grootte);
215
347
  }
216
348
  get bodem() {
217
349
  return this.marge + this.voetHoogte;
@@ -291,7 +423,7 @@ function breekAf(tk, tekst, s, breedte) {
291
423
  return regels;
292
424
  }
293
425
  function tekenRegel(tk, regel, x, breedte, y, s) {
294
- const t = veiligeTekst(regel);
426
+ const t = metTransform(veiligeTekst(regel), s);
295
427
  const w = tk.breedteVan(t, s);
296
428
  const dx = s.uitlijning === "right" ? breedte - w : s.uitlijning === "center" ? (breedte - w) / 2 : 0;
297
429
  tk.pagina.drawText(t, {
@@ -306,7 +438,7 @@ const inlineTags = new Set(["#tekst", "span", "b", "strong", "em", "i", "small",
306
438
  function isInline(n) {
307
439
  if (!inlineTags.has(n.tag))
308
440
  return false;
309
- const st = inline(n);
441
+ const st = eig(n);
310
442
  return !st["height"] && !st["width"];
311
443
  }
312
444
  /** Verzamelt de platte tekst van een knoop (met spatie tussen losse stukjes). */
@@ -354,7 +486,7 @@ function kolomBreedtes(tabel, rijen, totaal) {
354
486
  if (!aantal)
355
487
  return [totaal];
356
488
  if (cols.length === aantal) {
357
- const rauw = cols.map((c) => lengte(inline(c)["width"] ?? c.attrs["width"], totaal));
489
+ const rauw = cols.map((c) => lengte(eig(c)["width"] ?? c.attrs["width"], totaal));
358
490
  const som = rauw.reduce((a, b) => a + (b ?? 0), 0);
359
491
  if (som > 1) {
360
492
  const factor = totaal / som;
@@ -366,7 +498,7 @@ function kolomBreedtes(tabel, rijen, totaal) {
366
498
  async function tekenBlok(ctx, n, vak, ouderStijl) {
367
499
  const tk = ctx.tk;
368
500
  const s = stijlVan(n, ouderStijl);
369
- const st = inline(n);
501
+ const st = eig(n);
370
502
  const cl = klassen(n);
371
503
  if (n.tag === "#tekst") {
372
504
  const regels = breekAf(tk, n.tekst ?? "", s, vak.breedte);
@@ -475,16 +607,26 @@ async function tekenBlok(ctx, n, vak, ouderStijl) {
475
607
  }
476
608
  return;
477
609
  }
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)
610
+ // kader / achtergrond / afstand — uit de stylesheet, met de bekende klassen
611
+ // als terugval voor HTML zonder eigen opmaak.
612
+ const randRondom = randVan(st["border"]);
613
+ const randOnder = randVan(st["border-bottom"]);
614
+ const randBoven = randVan(st["border-top"]);
615
+ const kader = !!randRondom || cl.includes("kader") || cl.includes("rand") || cl.includes("omkaderd");
616
+ const achtergrond = vlakKleur(st["background"] ?? st["background-color"]);
617
+ const padding = lengte(st["padding"]?.split(/\s+/)[0]) ?? (kader ? 1.5 * MM : 0);
618
+ const margeOnder = lengte(st["margin-bottom"]);
619
+ const margeBoven = lengte(st["margin-top"]);
620
+ const breekVoor = cl.includes("breekvoor") || /page|always/.test(st["break-before"] ?? st["page-break-before"] ?? "");
621
+ if (breekVoor && tk.y < tk.hoogte - tk.marge - 1)
483
622
  tk.nieuwePagina();
623
+ if (margeBoven)
624
+ tk.y -= margeBoven;
625
+ const heelHouden = cl.includes("heelhouden") || /avoid/.test(st["break-inside"] ?? st["page-break-inside"] ?? "");
484
626
  const binnenVak = { x: vak.x + padding, breedte: vak.breedte - padding * 2 };
485
- if (!ctx.meten && (cl.includes("heelhouden") || achtergrond)) {
627
+ if (!ctx.meten && (heelHouden || achtergrond)) {
486
628
  const nodig = (await meetHoogte(ctx, n, binnenVak, s)) + padding * 2;
487
- if (cl.includes("heelhouden") && nodig > tk.ruimte && nodig < tk.hoogte - tk.marge * 2) {
629
+ if (heelHouden && nodig > tk.ruimte && nodig < tk.hoogte - tk.marge * 2) {
488
630
  tk.nieuwePagina();
489
631
  }
490
632
  if (achtergrond) {
@@ -500,10 +642,10 @@ async function tekenBlok(ctx, n, vak, ouderStijl) {
500
642
  }
501
643
  const startY = tk.y;
502
644
  const startPagina = tk.pagina;
503
- if (kader || achtergrond)
645
+ if (padding)
504
646
  tk.y -= padding;
505
647
  await tekenKinderen(ctx, n, binnenVak, s);
506
- if (kader || achtergrond)
648
+ if (padding)
507
649
  tk.y -= padding;
508
650
  if (kader && tk.pagina === startPagina && startY - tk.y > 0) {
509
651
  startPagina.drawRectangle({
@@ -511,36 +653,62 @@ async function tekenBlok(ctx, n, vak, ouderStijl) {
511
653
  y: tk.y,
512
654
  width: vak.breedte,
513
655
  height: startY - tk.y,
514
- borderWidth: 0.6,
515
- borderColor: LIJN,
656
+ borderWidth: randRondom?.dikte ?? 0.6,
657
+ borderColor: randRondom?.kleur ?? LIJN,
516
658
  });
517
659
  }
518
- if (cl.includes("onder") || cl.includes("laagkop")) {
660
+ if (randOnder || cl.includes("onder") || cl.includes("laagkop")) {
519
661
  tk.pagina.drawLine({
520
662
  start: { x: vak.x, y: tk.y + 0.6 * MM },
521
663
  end: { x: vak.x + vak.breedte, y: tk.y + 0.6 * MM },
522
- thickness: 0.5,
523
- color: LIJN,
664
+ thickness: randOnder?.dikte ?? 0.5,
665
+ color: randOnder?.kleur ?? LIJN,
524
666
  });
525
667
  }
526
- if (cl.includes("totalen")) {
668
+ if (randBoven || cl.includes("totalen")) {
527
669
  tk.pagina.drawLine({
528
670
  start: { x: vak.x, y: startY },
529
671
  end: { x: vak.x + vak.breedte, y: startY },
530
- thickness: 0.7,
531
- color: LIJN,
672
+ thickness: randBoven?.dikte ?? 0.7,
673
+ color: randBoven?.kleur ?? LIJN,
532
674
  });
533
675
  }
534
- if (cl.includes("blok") || cl.includes("groep") || cl.includes("karblok") || cl.includes("laag"))
676
+ if (margeOnder !== null)
677
+ tk.y -= margeOnder;
678
+ else if (cl.includes("blok") || cl.includes("groep") || cl.includes("karblok") || cl.includes("laag"))
535
679
  tk.y -= 1.6 * MM;
536
680
  }
681
+ /** Leest `0.25mm solid #d1d5db` (of px/pt) uit een border-eigenschap. */
682
+ function randVan(v) {
683
+ if (!v || /^none$/i.test(v.trim()))
684
+ return null;
685
+ const m = /(-?[\d.]+\s*(?:mm|pt|px))?\s*(solid|dashed|dotted)?\s*(#[0-9a-f]{3,6}|black|white)?/i.exec(v);
686
+ if (!m || (!m[1] && !m[3]))
687
+ return null;
688
+ return { dikte: Math.max(0.3, lengte(m[1]?.trim()) ?? 0.5), kleur: hexNaarRgb(m[3] ?? "") ?? LIJN };
689
+ }
690
+ /** Achtergrondkleur uit een background(-color); verlopen tellen niet mee. */
691
+ function vlakKleur(v) {
692
+ if (!v || /gradient/i.test(v))
693
+ return null;
694
+ return hexNaarRgb(v.split(/\s+/)[0] ?? "");
695
+ }
537
696
  async function tekenAfbeelding(ctx, n, vak, s) {
538
697
  const tk = ctx.tk;
539
698
  const src = n.attrs["src"] ?? "";
540
699
  const afb = ctx.afbeeldingen.get(src);
541
- const hoogte = lengte(inline(n)["height"]) ?? 18 * MM;
700
+ const hoogte = lengte(eig(n)["height"]) ?? 18 * MM;
542
701
  if (!afb) {
543
- tk.y -= hoogte + 1 * MM;
702
+ // Een leeg vlak vertelt niemand dat er iets misging: melding op de afdruk.
703
+ const reden = ctx.beeldFouten?.get(src) ?? "Afbeelding niet beschikbaar";
704
+ const melding = { ...s, grootte: Math.min(s.grootte, 7), vet: false, kleur: rgb(0.7, 0.15, 0.15) };
705
+ const regels = breekAf(tk, `[${reden}]`, melding, vak.breedte);
706
+ tk.zorgVoor(Math.max(hoogte, regels.length * regelHoogte(melding)) + 1 * MM);
707
+ for (const r of regels) {
708
+ tk.y -= regelHoogte(melding);
709
+ tekenRegel(tk, r, vak.x, vak.breedte, tk.y + melding.grootte * 0.25, melding);
710
+ }
711
+ tk.y -= 1 * MM;
544
712
  return;
545
713
  }
546
714
  const schaal = hoogte / afb.height;
@@ -626,7 +794,7 @@ async function tekenTabel(ctx, tabel, vak, s) {
626
794
  /** Tekent een element dat met vaste coördinaten geplaatst is (position:absolute). */
627
795
  async function tekenAbsoluut(ctx, n, s) {
628
796
  const tk = ctx.tk;
629
- const st = inline(n);
797
+ const st = eig(n);
630
798
  const eigen = stijlVan(n, s);
631
799
  const x = lengte(st["left"]) ?? 0;
632
800
  const top = lengte(st["top"]) ?? 0;
@@ -755,7 +923,7 @@ async function tekenAbsoluut(ctx, n, s) {
755
923
  }
756
924
  /** Zoekt een streepjes- of blokjespatroon (barcode/QR) in de knoop of kinderen. */
757
925
  function zoekGradient(n) {
758
- const css = `${inline(n)["background"] ?? ""} ${inline(n)["background-image"] ?? ""}`;
926
+ const css = `${eig(n)["background"] ?? ""} ${eig(n)["background-image"] ?? ""}`;
759
927
  if (/repeating-linear-gradient/i.test(css))
760
928
  return "streepjes";
761
929
  if (/repeating-conic-gradient/i.test(css))
@@ -782,21 +950,28 @@ async function tekenKinderen(ctx, n, vak, s) {
782
950
  const tk = ctx.tk;
783
951
  const cl = klassen(n);
784
952
  const alle = n.kinderen.filter((k) => k.tag !== "#tekst" || (k.tekst ?? "").trim().length);
785
- const absolute = alle.filter((k) => /absolute/.test(inline(k)["position"] ?? ""));
953
+ const absolute = alle.filter((k) => /absolute/.test(eig(k)["position"] ?? ""));
786
954
  for (const k of absolute)
787
955
  await tekenAbsoluut(ctx, k, s);
788
956
  const kinderen = alle.filter((k) => !absolute.includes(k));
789
957
  if (!kinderen.length)
790
958
  return;
791
- const tweeKolommen = cl.includes("grid2") || cl.includes("tweekolom");
792
- const rijModus = cl.includes("kv") ||
959
+ const st = eig(n);
960
+ const display = st["display"] ?? "";
961
+ const kolommen = st["grid-template-columns"] ?? "";
962
+ const gapCss = lengte((st["gap"] ?? "").split(/\s+/).slice(-1)[0]);
963
+ const tweeKolommen = cl.includes("grid2") ||
964
+ cl.includes("tweekolom") ||
965
+ (/grid/.test(display) && kolommen.trim().split(/\s+/).length === 2);
966
+ const rijModus = /flex/.test(display) ||
967
+ cl.includes("kv") ||
793
968
  cl.includes("karkop") ||
794
969
  cl.includes("laagkop") ||
795
970
  cl.includes("meta") ||
796
971
  cl.includes("totalen") ||
797
972
  cl.includes("regelrij");
798
973
  if (tweeKolommen) {
799
- const gap = 6 * MM;
974
+ const gap = gapCss ?? 6 * MM;
800
975
  const kolomBreedte = (vak.breedte - gap) / 2;
801
976
  const links = kinderen.filter((_, i) => i % 2 === 0);
802
977
  const rechts = kinderen.filter((_, i) => i % 2 === 1);
@@ -821,12 +996,17 @@ async function tekenKinderen(ctx, n, vak, s) {
821
996
  const breedtesTekst = stukken.map((p) => tk.breedteVan(p.tekst, p.stijl));
822
997
  const hoogte = Math.max(...stukken.map((p) => regelHoogte(p.stijl)), regelHoogte(s));
823
998
  const totaalTekst = breedtesTekst.reduce((a, b) => a + b, 0);
824
- const gap = 3 * MM;
999
+ const gap = gapCss ?? 3 * MM;
825
1000
  tk.zorgVoor(hoogte);
826
1001
  tk.y -= hoogte;
827
1002
  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";
1003
+ const verdeel = st["justify-content"] ?? "";
1004
+ const spreiden = stukken.length > 1 &&
1005
+ (verdeel === "space-between" ||
1006
+ verdeel === "space-around" ||
1007
+ ((!verdeel || verdeel === "normal") &&
1008
+ (cl.includes("kv") || cl.includes("karkop") || cl.includes("laagkop"))));
1009
+ const rechtsUit = verdeel === "flex-end" || verdeel === "right" || verdeel === "end";
830
1010
  if (spreiden) {
831
1011
  let x = vak.x;
832
1012
  stukken.forEach((p, i) => {
@@ -889,11 +1069,16 @@ function verzamelBronnen(n, uit) {
889
1069
  async function laadAfbeeldingen(pdf, wortel) {
890
1070
  const bronnen = new Set();
891
1071
  verzamelBronnen(wortel, bronnen);
892
- const uit = new Map();
1072
+ const beelden = new Map();
1073
+ const fouten = new Map();
893
1074
  await Promise.all([...bronnen].slice(0, 20).map(async (src) => {
894
1075
  try {
895
1076
  let bytes;
896
1077
  let png = /\.png(\?|$)/i.test(src);
1078
+ if (/^data:image\/svg|\.svg(\?|$)/i.test(src)) {
1079
+ fouten.set(src, "SVG-afbeelding wordt niet ondersteund - gebruik PNG of JPG");
1080
+ return;
1081
+ }
897
1082
  if (src.startsWith("data:")) {
898
1083
  const komma = src.indexOf(",");
899
1084
  png = /image\/png/i.test(src.slice(0, komma));
@@ -901,22 +1086,30 @@ async function laadAfbeeldingen(pdf, wortel) {
901
1086
  }
902
1087
  else if (/^https?:/i.test(src)) {
903
1088
  const res = await fetch(src);
904
- if (!res.ok)
1089
+ if (!res.ok) {
1090
+ fouten.set(src, `Afbeelding niet opgehaald (${res.status})`);
905
1091
  return;
1092
+ }
906
1093
  const type = res.headers.get("content-type") ?? "";
1094
+ if (/svg/i.test(type)) {
1095
+ fouten.set(src, "SVG-afbeelding wordt niet ondersteund - gebruik PNG of JPG");
1096
+ return;
1097
+ }
907
1098
  png = /png/i.test(type) || png;
908
1099
  bytes = new Uint8Array(await res.arrayBuffer());
909
1100
  }
910
- else
1101
+ else {
1102
+ fouten.set(src, "Onbekende afbeeldingsbron");
911
1103
  return;
1104
+ }
912
1105
  const afb = png ? await pdf.embedPng(bytes) : await pdf.embedJpg(bytes);
913
- uit.set(src, afb);
1106
+ beelden.set(src, afb);
914
1107
  }
915
- catch {
916
- // afbeelding overslaan: het document moet altijd afdrukbaar blijven
1108
+ catch (e) {
1109
+ fouten.set(src, e instanceof Error ? `Afbeelding onleesbaar: ${e.message}` : "Afbeelding onleesbaar");
917
1110
  }
918
1111
  }));
919
- return uit;
1112
+ return { beelden, fouten };
920
1113
  }
921
1114
  /** Zet print-HTML om naar een PDF en levert die als base64 (zonder prefix). */
922
1115
  export async function htmlNaarPdfBytes(opties) {
@@ -929,10 +1122,13 @@ export async function htmlNaarPdfBytes(opties) {
929
1122
  const normaal = await pdf.embedFont(StandardFonts.Helvetica);
930
1123
  const vet = await pdf.embedFont(StandardFonts.HelveticaBold);
931
1124
  const wortel = ontleedHtml(opties.html);
932
- const afbeeldingen = await laadAfbeeldingen(pdf, wortel);
1125
+ // De opmaak uit het <style>-blok wordt hier per element uitgerekend, zodat
1126
+ // het document er hetzelfde uitziet als in het voorbeeld op het scherm.
1127
+ stempelCss(wortel, ontleedCss(leesStylesheets(opties.html)));
1128
+ const { beelden: afbeeldingen, fouten: beeldFouten } = await laadAfbeeldingen(pdf, wortel);
933
1129
  const basis = basisGrootte(opties.html);
934
1130
  const tk = new Tekenaar(pdf, breedte, hoogte, marge, normaal, vet);
935
- const ctx = { tk, afbeeldingen };
1131
+ const ctx = { tk, afbeeldingen, beeldFouten };
936
1132
  const stijl = { grootte: basis, vet: false, kleur: DONKER, uitlijning: "left" };
937
1133
  // vaste paginavoet (bijv. totalen op elke pagina)
938
1134
  const voetKnoop = zoekVoet(wortel);
@@ -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"}
@@ -215,7 +215,7 @@ export async function laadPakbonData(db, opties) {
215
215
  const extra = (p["custom_fields"] ?? {});
216
216
  return {
217
217
  artikelnummer: String(p["article_number"] ?? ""),
218
- artikelnaam: String(bron["product_name"] ?? p["product"] ?? ""),
218
+ artikelnaam: taalTekst(bron["product_name"]) || taalTekst(p["product"]),
219
219
  potmaat: String(extra["potmaat"] ?? extra["Potmaat"] ?? ""),
220
220
  aantal: aantalPlanten,
221
221
  barcode: String(p["barcode"] ?? ""),
@@ -277,6 +277,37 @@ export async function laadPakbonData(db, opties) {
277
277
  * Kopsecties bij één of meerdere orders. Conservatief: bij meerdere orders
278
278
  * tonen we de klant (als die gelijk is) en géén willekeurig ordernummer.
279
279
  */
280
+ /** Leest een naam die als JSON met taalvarianten in de database staat. */
281
+ function taalTekst(waarde) {
282
+ if (waarde == null)
283
+ return "";
284
+ if (typeof waarde === "object") {
285
+ const o = waarde;
286
+ const keus = o["nl"] ?? Object.values(o)[0];
287
+ return keus == null ? "" : String(keus);
288
+ }
289
+ const tekst = String(waarde);
290
+ if (tekst.trim().startsWith("{")) {
291
+ try {
292
+ return taalTekst(JSON.parse(tekst));
293
+ }
294
+ catch {
295
+ return tekst;
296
+ }
297
+ }
298
+ return tekst;
299
+ }
300
+ /** Zet een ISO-timestamp of datumtekst om naar een leesbare datum (dd-mm-jjjj). */
301
+ function datumTekst(waarde) {
302
+ const tekst = waarde == null ? "" : String(waarde).trim();
303
+ if (!tekst)
304
+ return "";
305
+ const d = new Date(tekst);
306
+ if (Number.isNaN(d.getTime()))
307
+ return tekst;
308
+ const p = (n) => String(n).padStart(2, "0");
309
+ return `${p(d.getUTCDate())}-${p(d.getUTCMonth() + 1)}-${d.getUTCFullYear()}`;
310
+ }
280
311
  function maakKopsecties(orders, scope) {
281
312
  const nummers = uniek(orders.map((o) => o["order_number"]));
282
313
  const klanten = uniek(orders.map((o) => o["customer_name"]));
@@ -287,8 +318,8 @@ function maakKopsecties(orders, scope) {
287
318
  return {
288
319
  order: {
289
320
  nummer: meer ? "" : String(eerste["order_number"] ?? ""),
290
- datum: String(eerste["order_date"] ?? ""),
291
- leverdatum: String(eerste["delivery_date"] ?? ""),
321
+ datum: datumTekst(eerste["order_date"]),
322
+ leverdatum: datumTekst(eerste["delivery_date"]),
292
323
  referentie: lijst(nummers),
293
324
  opmerking: meer ? "" : String(eerste["notes"] ?? ""),
294
325
  aantal_orders: orders.length,
@@ -8,9 +8,21 @@ export type PrintDb = SupabaseClient<any, any, any>;
8
8
  * meer nu de shell de client levert.
9
9
  */
10
10
  export declare function controleerClient(db: PrintDb): Promise<string | null>;
11
+ /**
12
+ * `ontwerpWint` = deze layout is een echt ontwerp van deze module (actief, geen
13
+ * Floriday-systeemlayout) en gaat daarom vóór meegestuurde HTML. Er is bewust
14
+ * maar één schakelaar voor dit besluit: bron/systeem. Een tweede schakelaar
15
+ * ("stond er een layout_id op de opdracht?") is verwijderd.
16
+ */
11
17
  type LayoutKeuze = {
12
18
  layout: Job | null;
13
- expliciet: boolean;
19
+ ontwerpWint: boolean;
20
+ };
21
+ /** Waar de uiteindelijke opmaak vandaan kwam (uitkomst, geen invoer). */
22
+ export type GebruikteBron = "extern_pdf" | "eigen_layout" | "render_html" | "klantlayout";
23
+ /** Meelifter waarin `bouwHtml` vastlegt welke bron het uiteindelijk werd. */
24
+ export type BronUitkomst = {
25
+ bron: GebruikteBron;
14
26
  };
15
27
  /** Zet een binnenkomend documenttype om naar de sleutel van deze module. */
16
28
  export declare function normaliseerDocumenttype(type: unknown): string;
@@ -24,7 +36,7 @@ export declare function kantEnKlarePdf(_db: PrintDb, job: Job): Promise<string |
24
36
  * Volgorde: gekoppelde layout wint > meegestuurde HTML > actieve layout van
25
37
  * het documenttype. Zo werkt een bijgewerkte layout meteen door.
26
38
  */
27
- export declare function bouwHtml(db: PrintDb, job: Job, keuze: LayoutKeuze): Promise<string>;
39
+ export declare function bouwHtml(db: PrintDb, job: Job, keuze: LayoutKeuze, uitkomst?: BronUitkomst): Promise<string>;
28
40
  /**
29
41
  * Controleert of het document echt iets afdrukt. Zonder deze controle kan een
30
42
  * lege layout of een opdracht zonder gegevens een blanco vel opleveren.
@@ -36,7 +48,7 @@ export interface WachtrijResultaat {
36
48
  mislukt: number;
37
49
  bijgewerkt: number;
38
50
  }
39
- /** Eén ronde: wachtende opdrachten afdrukken en statussen bijwerken. */
51
+ /** Eén ronde: vastgelopen opdrachten opruimen, afdrukken en statussen bijwerken. */
40
52
  export declare function verwerkWachtrij(db: PrintDb, max?: number): Promise<WachtrijResultaat>;
41
53
  export {};
42
54
  //# sourceMappingURL=printQueue.server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"printQueue.server.d.ts","sourceRoot":"","sources":["../../src/lib/printQueue.server.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAsB5D,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE/B,mFAAmF;AACnF,MAAM,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAEpD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAW1E;AAeD,KAAK,WAAW,GAAG;IAAE,MAAM,EAAE,GAAG,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC;AAc9D,4EAA4E;AAC5E,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAI7D;AA6FD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA+BnF;AAgPD;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CA8HzF;AAGD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAY1D;AAwLD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wEAAwE;AACxE,wBAAsB,eAAe,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,SAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAmBtF"}
1
+ {"version":3,"file":"printQueue.server.d.ts","sourceRoot":"","sources":["../../src/lib/printQueue.server.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAsB5D,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE/B,mFAAmF;AACnF,MAAM,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAEpD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAW1E;AAeD;;;;;GAKG;AACH,KAAK,WAAW,GAAG;IAAE,MAAM,EAAE,GAAG,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC;AAEhE,yEAAyE;AACzE,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,cAAc,GAAG,aAAa,GAAG,aAAa,CAAC;AAE1F,6EAA6E;AAC7E,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAcnD,4EAA4E;AAC5E,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAI7D;AAkHD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA+BnF;AA+PD;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,OAAO,EACX,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,WAAW,EAClB,QAAQ,CAAC,EAAE,YAAY,GACtB,OAAO,CAAC,MAAM,CAAC,CA8IjB;AAGD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAY1D;AAmRD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,oFAAoF;AACpF,wBAAsB,eAAe,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,SAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA4BtF"}