@softwear/latestcollectioncore 1.0.177 → 1.0.178

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/dist/pdf.d.ts CHANGED
@@ -54,7 +54,7 @@ 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
- /** When true, container and children are drawn only once before other body containers (first page header block) */
57
+ /** Only effective on top-level `layout.objects` (PDF engine ignores this flag on nested containers). */
58
58
  printOnlyAtStart?: boolean;
59
59
  }
60
60
  export type LayoutObject = TextLayoutObject | FieldLayoutObject | RectangleLayoutObject | ImageLayoutObject | ContainerLayoutObject;
package/dist/reports.js CHANGED
@@ -663,22 +663,26 @@ function genPDF(layout, printBuffer, options = {}) {
663
663
  }
664
664
  drawStaticPartOfPage(measurementDoc, layout, printBuffer, paperSize, options, measurementContext);
665
665
  // Draw containers in measurement mode (same order as render)
666
+ let measurementTopPrintedStartBottomTenths = 0;
666
667
  layout.objects
667
668
  .filter((object) => object.type == 'container' && object.printOnlyAtStart)
668
669
  .forEach((object) => {
669
- addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext);
670
+ const lh = addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext);
671
+ measurementTopPrintedStartBottomTenths = Math.max(measurementTopPrintedStartBottomTenths, lh.y * 10);
670
672
  });
671
673
  layout.objects
672
674
  .filter((object) => object.type == 'container' &&
673
675
  !object.printOnlyAtEnd &&
674
676
  !object.printOnlyAtStart)
675
677
  .forEach((object) => {
676
- addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext);
678
+ const bump = Math.max(0, measurementTopPrintedStartBottomTenths - object.y);
679
+ addObjectToPDF(0, bump, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext);
677
680
  });
678
681
  layout.objects
679
682
  .filter((object) => object.type == 'container' && object.printOnlyAtEnd)
680
683
  .forEach((object) => {
681
- addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext);
684
+ const bump = Math.max(0, measurementTopPrintedStartBottomTenths - object.y);
685
+ addObjectToPDF(0, bump, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext);
682
686
  });
683
687
  // Get total page count from measurement pass
684
688
  const totalPageCount = measurementContext.currentPageCount || 1;
@@ -697,10 +701,12 @@ function genPDF(layout, printBuffer, options = {}) {
697
701
  defaultFontFamily: layout.defaultFontFamily,
698
702
  };
699
703
  drawStaticPartOfPage(doc, layout, printBuffer, paperSize, options, renderContext);
704
+ let renderTopPrintedStartBottomTenths = 0;
700
705
  layout.objects
701
706
  .filter((object) => object.type == 'container' && object.printOnlyAtStart)
702
707
  .forEach((object) => {
703
- addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
708
+ const lh = addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
709
+ renderTopPrintedStartBottomTenths = Math.max(renderTopPrintedStartBottomTenths, lh.y * 10);
704
710
  });
705
711
  // Draw containers in rendering mode (body containers after printOnlyAtStart)
706
712
  layout.objects
@@ -708,12 +714,14 @@ function genPDF(layout, printBuffer, options = {}) {
708
714
  !object.printOnlyAtEnd &&
709
715
  !object.printOnlyAtStart)
710
716
  .forEach((object) => {
711
- addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
717
+ const bump = Math.max(0, renderTopPrintedStartBottomTenths - object.y);
718
+ addObjectToPDF(0, bump, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
712
719
  });
713
720
  // Draw printOnlyAtEnd containers once at the end (at their layout coordinates on current page)
714
721
  const printOnlyAtEndContainers = layout.objects.filter((object) => object.type == 'container' && object.printOnlyAtEnd);
715
722
  printOnlyAtEndContainers.forEach((object) => {
716
- addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
723
+ const bump = Math.max(0, renderTopPrintedStartBottomTenths - object.y);
724
+ addObjectToPDF(0, bump, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
717
725
  });
718
726
  return doc;
719
727
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwear/latestcollectioncore",
3
- "version": "1.0.177",
3
+ "version": "1.0.178",
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,7 @@ 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
- /** When true, container and children are drawn only once before other body containers (first page header block) */
64
+ /** Only effective on top-level `layout.objects` (PDF engine ignores this flag on nested containers). */
65
65
  printOnlyAtStart?: boolean
66
66
  }
67
67
 
package/src/reports.ts CHANGED
@@ -786,10 +786,12 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
786
786
  drawStaticPartOfPage(measurementDoc, layout, printBuffer, paperSize, options, measurementContext)
787
787
 
788
788
  // Draw containers in measurement mode (same order as render)
789
+ let measurementTopPrintedStartBottomTenths = 0
789
790
  layout.objects
790
791
  .filter((object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtStart)
791
792
  .forEach((object) => {
792
- addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext)
793
+ const lh = addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext)
794
+ measurementTopPrintedStartBottomTenths = Math.max(measurementTopPrintedStartBottomTenths, lh.y * 10)
793
795
  })
794
796
  layout.objects
795
797
  .filter(
@@ -799,12 +801,14 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
799
801
  !(object as ContainerLayoutObject).printOnlyAtStart
800
802
  )
801
803
  .forEach((object) => {
802
- addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext)
804
+ const bump = Math.max(0, measurementTopPrintedStartBottomTenths - object.y)
805
+ addObjectToPDF(0, bump, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext)
803
806
  })
804
807
  layout.objects
805
808
  .filter((object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtEnd)
806
809
  .forEach((object) => {
807
- addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext)
810
+ const bump = Math.max(0, measurementTopPrintedStartBottomTenths - object.y)
811
+ addObjectToPDF(0, bump, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext)
808
812
  })
809
813
 
810
814
  // Get total page count from measurement pass
@@ -826,10 +830,12 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
826
830
  }
827
831
  drawStaticPartOfPage(doc, layout, printBuffer, paperSize, options, renderContext)
828
832
 
833
+ let renderTopPrintedStartBottomTenths = 0
829
834
  layout.objects
830
835
  .filter((object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtStart)
831
836
  .forEach((object) => {
832
- addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
837
+ const lh = addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
838
+ renderTopPrintedStartBottomTenths = Math.max(renderTopPrintedStartBottomTenths, lh.y * 10)
833
839
  })
834
840
 
835
841
  // Draw containers in rendering mode (body containers after printOnlyAtStart)
@@ -841,7 +847,8 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
841
847
  !(object as ContainerLayoutObject).printOnlyAtStart
842
848
  )
843
849
  .forEach((object) => {
844
- addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
850
+ const bump = Math.max(0, renderTopPrintedStartBottomTenths - object.y)
851
+ addObjectToPDF(0, bump, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
845
852
  })
846
853
 
847
854
  // Draw printOnlyAtEnd containers once at the end (at their layout coordinates on current page)
@@ -849,7 +856,8 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
849
856
  (object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtEnd
850
857
  )
851
858
  printOnlyAtEndContainers.forEach((object) => {
852
- addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
859
+ const bump = Math.max(0, renderTopPrintedStartBottomTenths - object.y)
860
+ addObjectToPDF(0, bump, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
853
861
  })
854
862
  return doc
855
863
  }
@@ -1,8 +1,15 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import { genPDF } from '../src/reports'
3
3
  import type { Layout } from '../src/pdf'
4
- // import fs from 'fs'
5
4
 
5
+ /** Tiny valid JPEG as data URL — avoids flaky network-dependent image fetch in CI/sandbox. */
6
+ function offlineJpegFixture(): string {
7
+ const b64 =
8
+ '/9j/4AAQSkZJRgABAQIAASABIAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAAKAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAHhAP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAQkCf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8B/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgMBPwF//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQYPAj//xAAVEAEAAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQBPCQ//Z'
9
+ return `data:image/jpeg;base64,${b64}`
10
+ }
11
+
12
+ // URL still used so collectImageUrlsFromLayout resolves the layout object
6
13
  const imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/thumb/7/79/Phillips_PM5538.jpg/500px-Phillips_PM5538.jpg'
7
14
 
8
15
  describe('genPDF', () => {
@@ -59,7 +66,14 @@ describe('genPDF', () => {
59
66
  title: 'Minimal PDF',
60
67
  }
61
68
 
62
- const doc = await genPDF(layout, printBuffer)
69
+ const doc = await genPDF(layout, printBuffer, {
70
+ loadImage: async () => ({
71
+ dataUrl: offlineJpegFixture(),
72
+ format: 'JPEG',
73
+ width: 500,
74
+ height: 375,
75
+ }),
76
+ })
63
77
  const pdf = doc.output('arraybuffer')
64
78
  const pdfText = Buffer.from(pdf).toString('latin1')
65
79
 
@@ -138,4 +152,61 @@ describe('genPDF', () => {
138
152
  expect(iStart).toBeLessThan(iMid)
139
153
  expect(iMid).toBeLessThan(iEnd)
140
154
  })
155
+
156
+ it('stacks top-level body containers below printOnlyAtStart when they share the same layout y', async () => {
157
+ const text = (name: string, label: string, y = 0, fontSize = 80) =>
158
+ ({
159
+ type: 'text' as const,
160
+ name,
161
+ x: 20,
162
+ y,
163
+ width: 900,
164
+ height: 400,
165
+ active: true,
166
+ text: label,
167
+ textAlign: 1 as const,
168
+ fontFamily: 'Helvetica',
169
+ fontSize,
170
+ fontStyle: 0,
171
+ }) as const
172
+
173
+ const sharedY = 400
174
+ const layout: Layout = {
175
+ name: 'same-y-stack',
176
+ paperSize: { width: 210, height: 297, footerHeight: 20 },
177
+ objects: [
178
+ {
179
+ type: 'container',
180
+ x: 0,
181
+ y: sharedY,
182
+ width: 1000,
183
+ height: 900,
184
+ active: true,
185
+ source: '',
186
+ printOnlyAtStart: true,
187
+ children: [text('h', 'PAGE1_HEADER_BLOCK')],
188
+ },
189
+ {
190
+ type: 'container',
191
+ x: 0,
192
+ y: sharedY,
193
+ width: 1000,
194
+ height: 500,
195
+ active: true,
196
+ source: '',
197
+ children: [text('b', 'BODY_AFTER_STACK', 0, 32)],
198
+ },
199
+ ],
200
+ }
201
+
202
+ const doc = await genPDF(layout, {})
203
+ const pdfText = Buffer.from(doc.output('arraybuffer')).toString('latin1')
204
+ expect(pdfText).toContain('(PAGE1_HEADER_BLOCK)')
205
+ expect(pdfText).toContain('(BODY_AFTER_STACK)')
206
+ const iH = pdfText.indexOf('(PAGE1_HEADER_BLOCK)')
207
+ const iB = pdfText.indexOf('(BODY_AFTER_STACK)')
208
+ expect(iH).toBeGreaterThanOrEqual(0)
209
+ expect(iB).toBeGreaterThanOrEqual(0)
210
+ expect(iH).toBeLessThan(iB)
211
+ })
141
212
  })