@softwear/latestcollectioncore 1.0.184 → 1.0.186

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/.prettierrc.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "htmlWhitespaceSensitivity": "ignore",
5
5
  "semi": false,
6
6
  "arrowParens": "always",
7
- "printWidth": 180
8
- }
7
+ "printWidth": 280
8
+ }
package/dist/pdf.d.ts CHANGED
@@ -54,7 +54,10 @@ export interface ContainerLayoutObject extends BaseLayoutObject {
54
54
  minHeightBeforeBreak?: number;
55
55
  /** When true, container and children are drawn only once at the end of the report, not on every page */
56
56
  printOnlyAtEnd?: boolean;
57
- /** Only effective on top-level `layout.objects` (PDF engine ignores this flag on nested containers). */
57
+ /**
58
+ * Only on top-level `layout.objects`. Same intent as onlyRenderOnFirstPage: drawn once on the first printable page,
59
+ * not repeated after overflow page breaks.
60
+ */
58
61
  printOnlyAtStart?: boolean;
59
62
  }
60
63
  export type LayoutObject = TextLayoutObject | FieldLayoutObject | RectangleLayoutObject | ImageLayoutObject | ContainerLayoutObject;
package/dist/reports.js CHANGED
@@ -16,6 +16,14 @@ exports.genPDF = void 0;
16
16
  const jspdf_1 = require("jspdf");
17
17
  const date_fns_1 = require("date-fns");
18
18
  const deepCopy_1 = __importDefault(require("./deepCopy"));
19
+ /** Measurement uses context `currentPageNumber` (1-based). Rendering normalizes jsPDF’s internal 0-based page index to 1-based so overlap rules match `pageNumber` fields. */
20
+ function getCurrentPageNumber(doc, context) {
21
+ var _a;
22
+ if (context.mode === 'measurement') {
23
+ return (_a = context.currentPageNumber) !== null && _a !== void 0 ? _a : 1;
24
+ }
25
+ return doc.getCurrentPageInfo().pageNumber + 1;
26
+ }
19
27
  // We have to declare some functions that will be called by other functions but also have to call those other functions
20
28
  // We overwrite the function definition as soon as those other functions have been declared
21
29
  /* eslint-disable @typescript-eslint/no-unused-vars */
@@ -24,7 +32,7 @@ let drawStaticPartOfPage = function (doc, layout, printBuffer, paperSize, option
24
32
  };
25
33
  // Forward declaration - parameters intentionally unused, function is overwritten below
26
34
  /* eslint-disable @typescript-eslint/no-unused-vars */
27
- let addObjectToPDF = function (_originX, _originY, _doc, _object, _printBuffer, _paperSize, _layout, _options, _rootPrintBuffer, _containerChain, _context) {
35
+ let addObjectToPDF = function (_originX, _originY, _doc, _object, _printBuffer, _paperSize, _layout, _options, _rootPrintBuffer, _containerChain, _context, _printOnlyAtStartContainersOffset) {
28
36
  // to be assigned later
29
37
  return { x: 0, y: 0 };
30
38
  };
@@ -400,18 +408,12 @@ function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options
400
408
  }
401
409
  }
402
410
  function addPage(originX, originY, doc, layout, rootPrintBuffer, paperSize, options, containerChain, context) {
411
+ var _a;
403
412
  /** Absolute tenths where the overflowing repeat resumes; callers already computed this correctly. */
404
413
  const continuationOxTenths = originX;
405
414
  const continuationOyTenths = originY;
406
- context.pendingRootStartBumpTenths = 0;
407
- context.applyRootStartBumpToThisCall = false;
408
- context.remainingRootStartBumpTenths = 0;
409
415
  if (context.mode === 'measurement') {
410
- // Track page count in measurement mode
411
- if (context.currentPageCount === undefined) {
412
- context.currentPageCount = 1;
413
- }
414
- context.currentPageCount++;
416
+ context.currentPageNumber = ((_a = context.currentPageNumber) !== null && _a !== void 0 ? _a : 1) + 1;
415
417
  }
416
418
  else if (!context.measureOnly) {
417
419
  // Actually add page in rendering mode (skip when measuring for fillContainer)
@@ -438,10 +440,7 @@ function addPage(originX, originY, doc, layout, rootPrintBuffer, paperSize, opti
438
440
  if (child.snapToBottom)
439
441
  return;
440
442
  // Skip inner repeat container on chain: advancing origin by x,y is enough; the live loop draws rows.
441
- if (child.type === 'container' &&
442
- deeperLinkChild &&
443
- child === deeperLinkChild &&
444
- !containerRedrawsAfterContinuationPage(child)) {
443
+ if (child.type === 'container' && deeperLinkChild && child === deeperLinkChild && !containerRedrawsAfterContinuationPage(child)) {
445
444
  const c = child;
446
445
  originX += c.x;
447
446
  originY += c.y;
@@ -503,49 +502,35 @@ function drawBottomDwellers(object, originX, originY, absoluteLowerRightHandSide
503
502
  *
504
503
  * Use originX,originY as origin to displace the object's own x,y coordinates
505
504
  */
506
- addObjectToPDF = function (originX, originY, doc, object, printBuffer, paperSize, layout, options, rootPrintBuffer, containerChain, context) {
507
- var _a, _b, _c, _d, _f;
508
- if (context.applyRootStartBumpToThisCall) {
509
- context.remainingRootStartBumpTenths = (_a = context.pendingRootStartBumpTenths) !== null && _a !== void 0 ? _a : 0;
510
- context.applyRootStartBumpToThisCall = false;
511
- context.pendingRootStartBumpTenths = 0;
512
- }
505
+ addObjectToPDF = function (originX, originY, doc, object, printBuffer, paperSize, layout, options, rootPrintBuffer, containerChain, context, printOnlyAtStartContainersOffset) {
506
+ var _a;
513
507
  const layoutXtenths = originX + object.x;
514
508
  const layoutYtenths = originY + object.y;
515
- /** printOnlyAtStart offset only for outermost recursion and only until first positioning or addPage clears it */
516
- const bumpForDrawNonContainer = containerChain.length === 0 ? (_b = context.remainingRootStartBumpTenths) !== null && _b !== void 0 ? _b : 0 : 0;
509
+ const rawPrintOnlyAtStartOffset = printOnlyAtStartContainersOffset !== null && printOnlyAtStartContainersOffset !== void 0 ? printOnlyAtStartContainersOffset : 0;
510
+ const effectivePrintOnlyAtStartContainersOffset = containerChain.length === 0 && rawPrintOnlyAtStartOffset > 0 && getCurrentPageNumber(doc, context) === 1 ? rawPrintOnlyAtStartOffset : 0;
517
511
  const x = layoutXtenths / 10;
518
- const y = (layoutYtenths + bumpForDrawNonContainer) / 10;
512
+ const y = (layoutYtenths + effectivePrintOnlyAtStartContainersOffset) / 10;
519
513
  const width = object.width / 10;
520
514
  const height = object.height / 10;
521
515
  // Keep track of the lower righthandside of all objects in this container
522
516
  // We need to return this object so whatever calls addObjectToPDF knows how big our drawing was
523
517
  let absoluteLowerRightHandSide = { x: x + width, y: y + height };
524
518
  if (!object.active) {
525
- if (bumpForDrawNonContainer !== 0)
526
- context.remainingRootStartBumpTenths = 0;
527
519
  return absoluteLowerRightHandSide;
528
520
  }
529
521
  if (object.type != 'container') {
530
- if (bumpForDrawNonContainer !== 0)
531
- context.remainingRootStartBumpTenths = 0;
532
522
  drawSimpleObject(x, y, doc, object, printBuffer, width, height, options, context);
533
523
  }
534
524
  // Recursively draw all child objects from a container object
535
525
  if (object.type == 'container') {
536
526
  const source = containerLoopSource(object, printBuffer);
537
527
  if (source === undefined) {
538
- (_c = options.onAlert) === null || _c === void 0 ? void 0 : _c.call(options, { header: 'containernotfound', body: object.source, type: 'warning', timeout: 10000 });
539
- if (containerChain.length === 0)
540
- context.remainingRootStartBumpTenths = 0;
528
+ (_a = options.onAlert) === null || _a === void 0 ? void 0 : _a.call(options, { header: 'containernotfound', body: object.source, type: 'warning', timeout: 10000 });
541
529
  return absoluteLowerRightHandSide;
542
530
  }
543
531
  const nrContainers = source.length;
544
- let bumpOnce = containerChain.length === 0 ? (_d = context.remainingRootStartBumpTenths) !== null && _d !== void 0 ? _d : 0 : 0;
545
- if (bumpOnce !== 0 && containerChain.length === 0)
546
- context.remainingRootStartBumpTenths = 0;
547
532
  let originX = layoutXtenths;
548
- let originY = layoutYtenths + bumpOnce;
533
+ let originY = layoutYtenths + effectivePrintOnlyAtStartContainersOffset;
549
534
  for (let containerIndex = 0; containerIndex < nrContainers; containerIndex++) {
550
535
  const newContainerChain = containerChain.filter(function () {
551
536
  return true;
@@ -557,7 +542,7 @@ addObjectToPDF = function (originX, originY, doc, object, printBuffer, paperSize
557
542
  if (useOrphanCheck && (object.pageBreak || originY + minHeightMm > paperSize.height - paperSize.footerHeight)) {
558
543
  // We ran out of paper
559
544
  originX = layoutXtenths;
560
- originY = layoutYtenths + (containerChain.length === 0 ? (_f = context.remainingRootStartBumpTenths) !== null && _f !== void 0 ? _f : 0 : 0);
545
+ originY = layoutYtenths + effectivePrintOnlyAtStartContainersOffset;
561
546
  const newOrigin = addPage(originX, originY, doc, layout, rootPrintBuffer, paperSize, options, newContainerChain, context);
562
547
  originX = newOrigin.originX;
563
548
  originY = newOrigin.originY;
@@ -731,10 +716,12 @@ function genPDF(layout, printBuffer, options = {}) {
731
716
  paperSize.width *= 10;
732
717
  paperSize.height *= 10;
733
718
  paperSize.footerHeight *= 10;
734
- // First pass: Measurement mode - count pages without rendering
719
+ // First pass: Measurement mode page count without visible output; overlaps must match render pass inputs.
735
720
  const measurementContext = {
736
721
  mode: 'measurement',
737
- currentPageCount: 1, // Start with page 1
722
+ currentPageNumber: 1,
723
+ imageAssets,
724
+ defaultFontFamily: layout.defaultFontFamily,
738
725
  };
739
726
  // Create a minimal jsPDF instance for measurement (needed for some calculations)
740
727
  const measurementDoc = new jspdf_1.jsPDF({
@@ -747,37 +734,26 @@ function genPDF(layout, printBuffer, options = {}) {
747
734
  }
748
735
  drawStaticPartOfPage(measurementDoc, layout, printBuffer, paperSize, options, measurementContext);
749
736
  // Draw containers in measurement mode (same order as render)
750
- let measurementTopPrintedStartBottomTenths = 0;
737
+ /** Max lower edge (layout units ×10 = tenths-mm) of print-only-at-start roots; deterministic for fixed layout+buffer once measure uses same context as render. */
738
+ let measuredPrintOnlyAtStartOffset = 0;
751
739
  layout.objects
752
740
  .filter((object) => object.type == 'container' && object.printOnlyAtStart === true)
753
741
  .forEach((object) => {
754
742
  const lh = addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext);
755
- measurementTopPrintedStartBottomTenths = Math.max(measurementTopPrintedStartBottomTenths, lh.y * 10);
743
+ measuredPrintOnlyAtStartOffset = Math.max(measuredPrintOnlyAtStartOffset, lh.y * 10);
756
744
  });
757
745
  layout.objects
758
- .filter((object) => object.type == 'container' &&
759
- object.printOnlyAtEnd !== true &&
760
- object.printOnlyAtStart !== true)
746
+ .filter((object) => object.type == 'container' && object.printOnlyAtEnd !== true && object.printOnlyAtStart !== true)
761
747
  .forEach((object) => {
762
- const bumpTenths = Math.max(0, measurementTopPrintedStartBottomTenths - object.y);
763
- if (bumpTenths > 0) {
764
- measurementContext.applyRootStartBumpToThisCall = true;
765
- measurementContext.pendingRootStartBumpTenths = bumpTenths;
766
- }
767
- addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext);
748
+ addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext, Math.max(0, measuredPrintOnlyAtStartOffset - object.y));
768
749
  });
769
750
  layout.objects
770
751
  .filter((object) => object.type == 'container' && object.printOnlyAtEnd === true)
771
752
  .forEach((object) => {
772
- const bumpTenths = Math.max(0, measurementTopPrintedStartBottomTenths - object.y);
773
- if (bumpTenths > 0) {
774
- measurementContext.applyRootStartBumpToThisCall = true;
775
- measurementContext.pendingRootStartBumpTenths = bumpTenths;
776
- }
777
- addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext);
753
+ addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext, Math.max(0, measuredPrintOnlyAtStartOffset - object.y));
778
754
  });
779
755
  // Get total page count from measurement pass
780
- const totalPageCount = measurementContext.currentPageCount || 1;
756
+ const totalPageCount = measurementContext.currentPageNumber || 1;
781
757
  // Second pass: Rendering mode - actual PDF generation with pageCount available
782
758
  const doc = new jspdf_1.jsPDF({
783
759
  orientation: paperSizeCopy.width > paperSizeCopy.height ? 'landscape' : 'portrait',
@@ -793,35 +769,21 @@ function genPDF(layout, printBuffer, options = {}) {
793
769
  defaultFontFamily: layout.defaultFontFamily,
794
770
  };
795
771
  drawStaticPartOfPage(doc, layout, printBuffer, paperSize, options, renderContext);
796
- let renderTopPrintedStartBottomTenths = 0;
797
772
  layout.objects
798
773
  .filter((object) => object.type == 'container' && object.printOnlyAtStart === true)
799
774
  .forEach((object) => {
800
- const lh = addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
801
- renderTopPrintedStartBottomTenths = Math.max(renderTopPrintedStartBottomTenths, lh.y * 10);
775
+ addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
802
776
  });
803
777
  // Draw containers in rendering mode (body containers after printOnlyAtStart)
804
778
  layout.objects
805
- .filter((object) => object.type == 'container' &&
806
- object.printOnlyAtEnd !== true &&
807
- object.printOnlyAtStart !== true)
779
+ .filter((object) => object.type == 'container' && object.printOnlyAtEnd !== true && object.printOnlyAtStart !== true)
808
780
  .forEach((object) => {
809
- const bumpTenths = Math.max(0, renderTopPrintedStartBottomTenths - object.y);
810
- if (bumpTenths > 0) {
811
- renderContext.applyRootStartBumpToThisCall = true;
812
- renderContext.pendingRootStartBumpTenths = bumpTenths;
813
- }
814
- addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
781
+ addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext, Math.max(0, measuredPrintOnlyAtStartOffset - object.y));
815
782
  });
816
783
  // Draw printOnlyAtEnd containers once at the end (at their layout coordinates on current page)
817
784
  const printOnlyAtEndContainers = layout.objects.filter((object) => object.type == 'container' && object.printOnlyAtEnd === true);
818
785
  printOnlyAtEndContainers.forEach((object) => {
819
- const bumpTenths = Math.max(0, renderTopPrintedStartBottomTenths - object.y);
820
- if (bumpTenths > 0) {
821
- renderContext.applyRootStartBumpToThisCall = true;
822
- renderContext.pendingRootStartBumpTenths = bumpTenths;
823
- }
824
- addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
786
+ addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext, Math.max(0, measuredPrintOnlyAtStartOffset - object.y));
825
787
  });
826
788
  return doc;
827
789
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwear/latestcollectioncore",
3
- "version": "1.0.184",
3
+ "version": "1.0.186",
4
4
  "description": "Core functions for LatestCollections applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/pdf.ts CHANGED
@@ -61,7 +61,10 @@ export interface ContainerLayoutObject extends BaseLayoutObject {
61
61
  minHeightBeforeBreak?: number
62
62
  /** When true, container and children are drawn only once at the end of the report, not on every page */
63
63
  printOnlyAtEnd?: boolean
64
- /** Only effective on top-level `layout.objects` (PDF engine ignores this flag on nested containers). */
64
+ /**
65
+ * Only on top-level `layout.objects`. Same intent as onlyRenderOnFirstPage: drawn once on the first printable page,
66
+ * not repeated after overflow page breaks.
67
+ */
65
68
  printOnlyAtStart?: boolean
66
69
  }
67
70
 
package/src/reports.ts CHANGED
@@ -40,20 +40,22 @@ export interface GenPdfOptions {
40
40
 
41
41
  interface RenderContext {
42
42
  mode: RenderMode
43
+ /** Total pages from measurement pass; used for field sources like `pageCount` / `page`. */
43
44
  pageCount?: number
44
- currentPageCount?: number // For measurement mode
45
+ /** During measurement, logical printable page (starts at 1); incremented when {@link addPage} runs. Rendering: see {@link getCurrentPageNumber} (1-based, matches pdf page fields). */
46
+ currentPageNumber?: number
45
47
  measureOnly?: boolean // When true, skip drawing to measure bounds (for fillContainer rectangles)
46
48
  imageAssets?: Map<string, PdfImageAsset>
47
49
  /** Layout default font used when object.fontFamily is undefined */
48
50
  defaultFontFamily?: string
49
- /** Set with {@link pendingRootStartBumpTenths} only for top-level genPDF container draws; moved to remaining on entry. */
50
- applyRootStartBumpToThisCall?: boolean
51
- /** Vertical pad (tenths-mm) so body roots sit below rendered printOnlyAtStart siblings; use with applyRootStartBumpToThisCall only. */
52
- pendingRootStartBumpTenths?: number
53
- /**
54
- * First-fragment bump for top-level draws from genPDF — applied with layout coords, then cleared after first positioning or addPage().
55
- */
56
- remainingRootStartBumpTenths?: number
51
+ }
52
+
53
+ /** Measurement uses context `currentPageNumber` (1-based). Rendering normalizes jsPDF’s internal 0-based page index to 1-based so overlap rules match `pageNumber` fields. */
54
+ function getCurrentPageNumber(doc: jsPDF, context: RenderContext): number {
55
+ if (context.mode === 'measurement') {
56
+ return context.currentPageNumber ?? 1
57
+ }
58
+ return doc.getCurrentPageInfo().pageNumber + 1
57
59
  }
58
60
 
59
61
  // We have to declare some functions that will be called by other functions but also have to call those other functions
@@ -76,7 +78,8 @@ let addObjectToPDF = function (
76
78
  _options: GenPdfOptions,
77
79
  _rootPrintBuffer: any,
78
80
  _containerChain: { object: ContainerLayoutObject; printBuffer: any }[],
79
- _context: RenderContext
81
+ _context: RenderContext,
82
+ _printOnlyAtStartContainersOffset?: number
80
83
  ): { x: number; y: number } {
81
84
  // to be assigned later
82
85
  return { x: 0, y: 0 }
@@ -123,17 +126,8 @@ function containerLoopSource(container: ContainerLayoutObject, printBuffer: any)
123
126
  * They are not on the nested {@link containerChain}; redraw them whenever we append a continuation page.
124
127
  * Omit the overflowing root when it is chain[0] — redrawing it would recurse (e.g. wrapper around lines).
125
128
  */
126
- function redrawRootDummyBodyContainersOnNewPage(
127
- doc: jsPDF,
128
- layout: Layout,
129
- rootPrintBuffer: any,
130
- paperSize: PaperSize,
131
- options: GenPdfOptions,
132
- context: RenderContext,
133
- containerChain: { object: ContainerLayoutObject; printBuffer: any }[]
134
- ): void {
135
- const overflowingRootContainer =
136
- containerChain.length > 0 ? (containerChain[0].object as ContainerLayoutObject) : undefined
129
+ function redrawRootDummyBodyContainersOnNewPage(doc: jsPDF, layout: Layout, rootPrintBuffer: any, paperSize: PaperSize, options: GenPdfOptions, context: RenderContext, containerChain: { object: ContainerLayoutObject; printBuffer: any }[]): void {
130
+ const overflowingRootContainer = containerChain.length > 0 ? (containerChain[0].object as ContainerLayoutObject) : undefined
137
131
 
138
132
  layout.objects.forEach((object) => {
139
133
  if (object.type !== 'container') return
@@ -333,17 +327,7 @@ function truncateTextToFitWidth(doc: jsPDF, text: string, maxWidth: number): str
333
327
  return truncatedText + ELLIPSIS
334
328
  }
335
329
 
336
- function drawSimpleObject(
337
- x: number,
338
- y: number,
339
- doc: jsPDF,
340
- object: LayoutObject,
341
- printBuffer: any,
342
- width: number,
343
- height: number,
344
- options: GenPdfOptions,
345
- context: RenderContext
346
- ): void {
330
+ function drawSimpleObject(x: number, y: number, doc: jsPDF, object: LayoutObject, printBuffer: any, width: number, height: number, options: GenPdfOptions, context: RenderContext): void {
347
331
  let text = '' // Will hold text to display for either 'text' or 'field'
348
332
 
349
333
  // Get text from object for text objects
@@ -484,15 +468,8 @@ function addPage(
484
468
  const continuationOxTenths = originX
485
469
  const continuationOyTenths = originY
486
470
 
487
- context.pendingRootStartBumpTenths = 0
488
- context.applyRootStartBumpToThisCall = false
489
- context.remainingRootStartBumpTenths = 0
490
471
  if (context.mode === 'measurement') {
491
- // Track page count in measurement mode
492
- if (context.currentPageCount === undefined) {
493
- context.currentPageCount = 1
494
- }
495
- context.currentPageCount++
472
+ context.currentPageNumber = (context.currentPageNumber ?? 1) + 1
496
473
  } else if (!context.measureOnly) {
497
474
  // Actually add page in rendering mode (skip when measuring for fillContainer)
498
475
  doc.addPage()
@@ -517,12 +494,7 @@ function addPage(
517
494
  link.object.children.forEach((child) => {
518
495
  if (child.snapToBottom) return
519
496
  // Skip inner repeat container on chain: advancing origin by x,y is enough; the live loop draws rows.
520
- if (
521
- child.type === 'container' &&
522
- deeperLinkChild &&
523
- child === deeperLinkChild &&
524
- !containerRedrawsAfterContinuationPage(child)
525
- ) {
497
+ if (child.type === 'container' && deeperLinkChild && child === deeperLinkChild && !containerRedrawsAfterContinuationPage(child)) {
526
498
  const c = child as ContainerLayoutObject
527
499
  originX += c.x
528
500
  originY += c.y
@@ -607,21 +579,17 @@ addObjectToPDF = function (
607
579
  options: GenPdfOptions,
608
580
  rootPrintBuffer: any,
609
581
  containerChain: { object: ContainerLayoutObject; printBuffer: any }[],
610
- context: RenderContext
582
+ context: RenderContext,
583
+ printOnlyAtStartContainersOffset?: number
611
584
  ): { x: number; y: number } {
612
- if (context.applyRootStartBumpToThisCall) {
613
- context.remainingRootStartBumpTenths = context.pendingRootStartBumpTenths ?? 0
614
- context.applyRootStartBumpToThisCall = false
615
- context.pendingRootStartBumpTenths = 0
616
- }
617
585
  const layoutXtenths = originX + object.x
618
586
  const layoutYtenths = originY + object.y
619
- /** printOnlyAtStart offset only for outermost recursion and only until first positioning or addPage clears it */
620
- const bumpForDrawNonContainer =
621
- containerChain.length === 0 ? context.remainingRootStartBumpTenths ?? 0 : 0
587
+ const rawPrintOnlyAtStartOffset = printOnlyAtStartContainersOffset ?? 0
588
+ const effectivePrintOnlyAtStartContainersOffset =
589
+ containerChain.length === 0 && rawPrintOnlyAtStartOffset > 0 && getCurrentPageNumber(doc, context) === 1 ? rawPrintOnlyAtStartOffset : 0
622
590
 
623
591
  const x = layoutXtenths / 10
624
- const y = (layoutYtenths + bumpForDrawNonContainer) / 10
592
+ const y = (layoutYtenths + effectivePrintOnlyAtStartContainersOffset) / 10
625
593
  const width = object.width / 10
626
594
  const height = object.height / 10
627
595
  // Keep track of the lower righthandside of all objects in this container
@@ -629,11 +597,9 @@ addObjectToPDF = function (
629
597
  let absoluteLowerRightHandSide = { x: x + width, y: y + height }
630
598
 
631
599
  if (!object.active) {
632
- if (bumpForDrawNonContainer !== 0) context.remainingRootStartBumpTenths = 0
633
600
  return absoluteLowerRightHandSide
634
601
  }
635
602
  if (object.type != 'container') {
636
- if (bumpForDrawNonContainer !== 0) context.remainingRootStartBumpTenths = 0
637
603
  drawSimpleObject(x, y, doc, object, printBuffer, width, height, options, context)
638
604
  }
639
605
 
@@ -642,15 +608,12 @@ addObjectToPDF = function (
642
608
  const source = containerLoopSource(object as ContainerLayoutObject, printBuffer)
643
609
  if (source === undefined) {
644
610
  options.onAlert?.({ header: 'containernotfound', body: object.source, type: 'warning', timeout: 10000 })
645
- if (containerChain.length === 0) context.remainingRootStartBumpTenths = 0
646
611
  return absoluteLowerRightHandSide
647
612
  }
648
613
  const nrContainers = source.length
649
614
 
650
- let bumpOnce = containerChain.length === 0 ? context.remainingRootStartBumpTenths ?? 0 : 0
651
- if (bumpOnce !== 0 && containerChain.length === 0) context.remainingRootStartBumpTenths = 0
652
615
  let originX = layoutXtenths
653
- let originY = layoutYtenths + bumpOnce
616
+ let originY = layoutYtenths + effectivePrintOnlyAtStartContainersOffset
654
617
 
655
618
  for (let containerIndex = 0; containerIndex < nrContainers; containerIndex++) {
656
619
  const newContainerChain = containerChain.filter(function () {
@@ -663,7 +626,7 @@ addObjectToPDF = function (
663
626
  if (useOrphanCheck && (object.pageBreak || originY + minHeightMm > paperSize.height - paperSize.footerHeight)) {
664
627
  // We ran out of paper
665
628
  originX = layoutXtenths
666
- originY = layoutYtenths + (containerChain.length === 0 ? context.remainingRootStartBumpTenths ?? 0 : 0)
629
+ originY = layoutYtenths + effectivePrintOnlyAtStartContainersOffset
667
630
  const newOrigin = addPage(originX, originY, doc, layout, rootPrintBuffer, paperSize, options, newContainerChain, context)
668
631
  originX = newOrigin.originX
669
632
  originY = newOrigin.originY
@@ -694,21 +657,7 @@ addObjectToPDF = function (
694
657
  measureBounds.y = Math.max(measureBounds.y, r.y)
695
658
  }
696
659
  })
697
- const measureBottom = drawBottomDwellers(
698
- object as ContainerLayoutObject,
699
- measureOriginX,
700
- measureOriginY,
701
- { x: measureBounds.x, y: measureBounds.y },
702
- measureBottomChildY,
703
- paperSize,
704
- doc,
705
- layout,
706
- rootPrintBuffer,
707
- options,
708
- measureChain,
709
- container,
710
- measureContext
711
- )
660
+ const measureBottom = drawBottomDwellers(object as ContainerLayoutObject, measureOriginX, measureOriginY, { x: measureBounds.x, y: measureBounds.y }, measureBottomChildY, paperSize, doc, layout, rootPrintBuffer, options, measureChain, container, measureContext)
712
661
  measureBounds.y = Math.max(measureBounds.y, measureBottom.y)
713
662
  // Draw fillContainer rectangles as background
714
663
  fillContainerRects.forEach(function (rect: any) {
@@ -776,42 +725,14 @@ addObjectToPDF = function (
776
725
  originX = newOrigin.originX
777
726
  originY = newOrigin.originY
778
727
  absoluteLowerRightHandSide = { x: newOrigin.x, y: newOrigin.y }
779
- const newestOrigin = drawBottomDwellers(
780
- object as ContainerLayoutObject,
781
- originX,
782
- originY,
783
- absoluteLowerRightHandSide,
784
- bottomChildY,
785
- paperSize,
786
- doc,
787
- layout,
788
- rootPrintBuffer,
789
- options,
790
- newContainerChain,
791
- container,
792
- context
793
- )
728
+ const newestOrigin = drawBottomDwellers(object as ContainerLayoutObject, originX, originY, absoluteLowerRightHandSide, bottomChildY, paperSize, doc, layout, rootPrintBuffer, options, newContainerChain, container, context)
794
729
  originX = newestOrigin.originX
795
730
  originY = newestOrigin.originY
796
731
  absoluteLowerRightHandSide = { x: newestOrigin.x, y: newestOrigin.y }
797
732
  continue
798
733
  }
799
734
  }
800
- const newestOrigin = drawBottomDwellers(
801
- object as ContainerLayoutObject,
802
- originX,
803
- originY,
804
- absoluteLowerRightHandSide,
805
- bottomChildY,
806
- paperSize,
807
- doc,
808
- layout,
809
- rootPrintBuffer,
810
- options,
811
- newContainerChain,
812
- container,
813
- context
814
- )
735
+ const newestOrigin = drawBottomDwellers(object as ContainerLayoutObject, originX, originY, absoluteLowerRightHandSide, bottomChildY, paperSize, doc, layout, rootPrintBuffer, options, newContainerChain, container, context)
815
736
  originX = newestOrigin.originX
816
737
  originY = newestOrigin.originY
817
738
  absoluteLowerRightHandSide = { x: newestOrigin.x, y: newestOrigin.y }
@@ -870,10 +791,12 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
870
791
  paperSize.height *= 10
871
792
  paperSize.footerHeight *= 10
872
793
 
873
- // First pass: Measurement mode - count pages without rendering
794
+ // First pass: Measurement mode page count without visible output; overlaps must match render pass inputs.
874
795
  const measurementContext: RenderContext = {
875
796
  mode: 'measurement',
876
- currentPageCount: 1, // Start with page 1
797
+ currentPageNumber: 1,
798
+ imageAssets,
799
+ defaultFontFamily: layout.defaultFontFamily,
877
800
  }
878
801
  // Create a minimal jsPDF instance for measurement (needed for some calculations)
879
802
  const measurementDoc = new jsPDF({
@@ -887,41 +810,27 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
887
810
  drawStaticPartOfPage(measurementDoc, layout, printBuffer, paperSize, options, measurementContext)
888
811
 
889
812
  // Draw containers in measurement mode (same order as render)
890
- let measurementTopPrintedStartBottomTenths = 0
813
+ /** Max lower edge (layout units ×10 = tenths-mm) of print-only-at-start roots; deterministic for fixed layout+buffer once measure uses same context as render. */
814
+ let measuredPrintOnlyAtStartOffset = 0
891
815
  layout.objects
892
816
  .filter((object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtStart === true)
893
817
  .forEach((object) => {
894
818
  const lh = addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext)
895
- measurementTopPrintedStartBottomTenths = Math.max(measurementTopPrintedStartBottomTenths, lh.y * 10)
819
+ measuredPrintOnlyAtStartOffset = Math.max(measuredPrintOnlyAtStartOffset, lh.y * 10)
896
820
  })
897
821
  layout.objects
898
- .filter(
899
- (object) =>
900
- object.type == 'container' &&
901
- (object as ContainerLayoutObject).printOnlyAtEnd !== true &&
902
- (object as ContainerLayoutObject).printOnlyAtStart !== true
903
- )
822
+ .filter((object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtEnd !== true && (object as ContainerLayoutObject).printOnlyAtStart !== true)
904
823
  .forEach((object) => {
905
- const bumpTenths = Math.max(0, measurementTopPrintedStartBottomTenths - object.y)
906
- if (bumpTenths > 0) {
907
- measurementContext.applyRootStartBumpToThisCall = true
908
- measurementContext.pendingRootStartBumpTenths = bumpTenths
909
- }
910
- addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext)
824
+ addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext, Math.max(0, measuredPrintOnlyAtStartOffset - object.y))
911
825
  })
912
826
  layout.objects
913
827
  .filter((object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtEnd === true)
914
828
  .forEach((object) => {
915
- const bumpTenths = Math.max(0, measurementTopPrintedStartBottomTenths - object.y)
916
- if (bumpTenths > 0) {
917
- measurementContext.applyRootStartBumpToThisCall = true
918
- measurementContext.pendingRootStartBumpTenths = bumpTenths
919
- }
920
- addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext)
829
+ addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext, Math.max(0, measuredPrintOnlyAtStartOffset - object.y))
921
830
  })
922
831
 
923
832
  // Get total page count from measurement pass
924
- const totalPageCount = measurementContext.currentPageCount || 1
833
+ const totalPageCount = measurementContext.currentPageNumber || 1
925
834
 
926
835
  // Second pass: Rendering mode - actual PDF generation with pageCount available
927
836
  const doc = new jsPDF({
@@ -939,42 +848,23 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
939
848
  }
940
849
  drawStaticPartOfPage(doc, layout, printBuffer, paperSize, options, renderContext)
941
850
 
942
- let renderTopPrintedStartBottomTenths = 0
943
851
  layout.objects
944
852
  .filter((object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtStart === true)
945
853
  .forEach((object) => {
946
- const lh = addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
947
- renderTopPrintedStartBottomTenths = Math.max(renderTopPrintedStartBottomTenths, lh.y * 10)
854
+ addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
948
855
  })
949
856
 
950
857
  // Draw containers in rendering mode (body containers after printOnlyAtStart)
951
858
  layout.objects
952
- .filter(
953
- (object) =>
954
- object.type == 'container' &&
955
- (object as ContainerLayoutObject).printOnlyAtEnd !== true &&
956
- (object as ContainerLayoutObject).printOnlyAtStart !== true
957
- )
859
+ .filter((object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtEnd !== true && (object as ContainerLayoutObject).printOnlyAtStart !== true)
958
860
  .forEach((object) => {
959
- const bumpTenths = Math.max(0, renderTopPrintedStartBottomTenths - object.y)
960
- if (bumpTenths > 0) {
961
- renderContext.applyRootStartBumpToThisCall = true
962
- renderContext.pendingRootStartBumpTenths = bumpTenths
963
- }
964
- addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
861
+ addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext, Math.max(0, measuredPrintOnlyAtStartOffset - object.y))
965
862
  })
966
863
 
967
864
  // Draw printOnlyAtEnd containers once at the end (at their layout coordinates on current page)
968
- const printOnlyAtEndContainers = layout.objects.filter(
969
- (object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtEnd === true
970
- )
865
+ const printOnlyAtEndContainers = layout.objects.filter((object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtEnd === true)
971
866
  printOnlyAtEndContainers.forEach((object) => {
972
- const bumpTenths = Math.max(0, renderTopPrintedStartBottomTenths - object.y)
973
- if (bumpTenths > 0) {
974
- renderContext.applyRootStartBumpToThisCall = true
975
- renderContext.pendingRootStartBumpTenths = bumpTenths
976
- }
977
- addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
867
+ addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext, Math.max(0, measuredPrintOnlyAtStartOffset - object.y))
978
868
  })
979
869
  return doc
980
870
  }