@softwear/latestcollectioncore 1.0.182 → 1.0.184

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/reports.js CHANGED
@@ -61,6 +61,26 @@ function containerLoopSource(container, printBuffer) {
61
61
  return [printBuffer];
62
62
  return getProperty(container.source, printBuffer);
63
63
  }
64
+ /**
65
+ * Top-level empty-source containers (invoice “billTo” bands, wrappers) sit beside overflowing repeats.
66
+ * They are not on the nested {@link containerChain}; redraw them whenever we append a continuation page.
67
+ * Omit the overflowing root when it is chain[0] — redrawing it would recurse (e.g. wrapper around lines).
68
+ */
69
+ function redrawRootDummyBodyContainersOnNewPage(doc, layout, rootPrintBuffer, paperSize, options, context, containerChain) {
70
+ const overflowingRootContainer = containerChain.length > 0 ? containerChain[0].object : undefined;
71
+ layout.objects.forEach((object) => {
72
+ if (object.type !== 'container')
73
+ return;
74
+ if (overflowingRootContainer !== undefined && object === overflowingRootContainer)
75
+ return;
76
+ const c = object;
77
+ if (c.printOnlyAtStart === true || c.printOnlyAtEnd === true)
78
+ return;
79
+ if (!dummyLayoutSource(c.source))
80
+ return;
81
+ addObjectToPDF(0, 0, doc, object, rootPrintBuffer, paperSize, layout, options, rootPrintBuffer, [], context);
82
+ });
83
+ }
64
84
  /** jsPDF font style strings. fontStyle bitmask: 0=normal, 1=bold, 2=italic, 3=bold+italic */
65
85
  const FONT_STYLES = ['normal', 'bold', 'italic', 'bolditalic'];
66
86
  /** Recursively collect used custom fonts from layout objects (text/field with fontFamily) and layout default. */
@@ -380,6 +400,9 @@ function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options
380
400
  }
381
401
  }
382
402
  function addPage(originX, originY, doc, layout, rootPrintBuffer, paperSize, options, containerChain, context) {
403
+ /** Absolute tenths where the overflowing repeat resumes; callers already computed this correctly. */
404
+ const continuationOxTenths = originX;
405
+ const continuationOyTenths = originY;
383
406
  context.pendingRootStartBumpTenths = 0;
384
407
  context.applyRootStartBumpToThisCall = false;
385
408
  context.remainingRootStartBumpTenths = 0;
@@ -396,45 +419,55 @@ function addPage(originX, originY, doc, layout, rootPrintBuffer, paperSize, opti
396
419
  }
397
420
  if (!context.measureOnly) {
398
421
  drawStaticPartOfPage(doc, layout, rootPrintBuffer, paperSize, options, context);
422
+ redrawRootDummyBodyContainersOnNewPage(doc, layout, rootPrintBuffer, paperSize, options, context, containerChain);
399
423
  }
400
- const absoluteLowerRightHandSide = { x: originX / 10, y: originY / 10 };
401
- // Now draw all the parent containers of the current container
424
+ // Bounding box tracked for snap-to-bottom; continuation cursor is ALWAYS the passed-in coords.
425
+ const absoluteLowerRightHandSide = { x: continuationOxTenths / 10, y: continuationOyTenths / 10 };
402
426
  if (containerChain.length > 0) {
403
- // Let origin start at (x,y) of the outermost container
427
+ // Re-draw ancestor frames on the new page (local coordinates). Do NOT treat this pass as another
428
+ // repeat iteration: skip repeatContainer advancement on the innermost link—the real loop resumes
429
+ // after addPage returns with continuationOxTenths / continuationOyTenths.
404
430
  originX = containerChain[0].object.x;
405
431
  originY = containerChain[0].object.y;
406
- absoluteLowerRightHandSide.x = originX / 10;
407
- absoluteLowerRightHandSide.y = originY / 10;
432
+ absoluteLowerRightHandSide.x = Math.max(absoluteLowerRightHandSide.x, originX / 10);
433
+ absoluteLowerRightHandSide.y = Math.max(absoluteLowerRightHandSide.y, originY / 10);
408
434
  containerChain.forEach((link, linkIdx) => {
409
435
  var _a;
410
- const chainChild = (_a = containerChain[linkIdx + 1]) === null || _a === void 0 ? void 0 : _a.object;
436
+ const deeperLinkChild = (_a = containerChain[linkIdx + 1]) === null || _a === void 0 ? void 0 : _a.object;
411
437
  link.object.children.forEach((child) => {
412
438
  if (child.snapToBottom)
413
439
  return;
414
- // Advance past bound repeat containers on the overflow path (they are not redrawn here; the
415
- // innermost link draws their leaves). Without this, nested x/y offsets are lost on page 2+.
440
+ // Skip inner repeat container on chain: advancing origin by x,y is enough; the live loop draws rows.
416
441
  if (child.type === 'container' &&
417
- chainChild &&
418
- child === chainChild &&
442
+ deeperLinkChild &&
443
+ child === deeperLinkChild &&
419
444
  !containerRedrawsAfterContinuationPage(child)) {
420
445
  const c = child;
421
446
  originX += c.x;
422
447
  originY += c.y;
423
448
  return;
424
449
  }
425
- if (child.type != 'container' || containerRedrawsAfterContinuationPage(child)) {
450
+ if (child.type !== 'container' || containerRedrawsAfterContinuationPage(child)) {
426
451
  const childLowerRightHandSide = addObjectToPDF(originX, originY, doc, child, link.printBuffer, paperSize, layout, options, rootPrintBuffer, [], context);
427
452
  absoluteLowerRightHandSide.x = Math.max(absoluteLowerRightHandSide.x, childLowerRightHandSide.x);
428
453
  absoluteLowerRightHandSide.y = Math.max(absoluteLowerRightHandSide.y, childLowerRightHandSide.y);
429
454
  }
430
455
  });
431
- if (link.object.repeatContainer == 'horizontal')
432
- originX = absoluteLowerRightHandSide.x * 10;
433
- if (link.object.repeatContainer == 'vertical')
434
- originY = absoluteLowerRightHandSide.y * 10;
456
+ const isLastLinkOnChain = linkIdx >= containerChain.length - 1;
457
+ if (!isLastLinkOnChain) {
458
+ if (link.object.repeatContainer === 'horizontal')
459
+ originX = absoluteLowerRightHandSide.x * 10;
460
+ if (link.object.repeatContainer === 'vertical')
461
+ originY = absoluteLowerRightHandSide.y * 10;
462
+ }
435
463
  });
436
464
  }
437
- return { originX: originX, originY: originY, x: absoluteLowerRightHandSide.x, y: absoluteLowerRightHandSide.y };
465
+ return {
466
+ originX: continuationOxTenths,
467
+ originY: continuationOyTenths,
468
+ x: absoluteLowerRightHandSide.x,
469
+ y: absoluteLowerRightHandSide.y,
470
+ };
438
471
  }
439
472
  function drawBottomDwellers(object, originX, originY, absoluteLowerRightHandSide, bottomChildY, paperSize, doc, layout, rootPrintBuffer, options, newContainerChain, container, context) {
440
473
  let childRelativeToBottomDrawn = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwear/latestcollectioncore",
3
- "version": "1.0.182",
3
+ "version": "1.0.184",
4
4
  "description": "Core functions for LatestCollections applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/reports.ts CHANGED
@@ -118,6 +118,33 @@ function containerLoopSource(container: ContainerLayoutObject, printBuffer: any)
118
118
  return getProperty(container.source as string, printBuffer)
119
119
  }
120
120
 
121
+ /**
122
+ * Top-level empty-source containers (invoice “billTo” bands, wrappers) sit beside overflowing repeats.
123
+ * They are not on the nested {@link containerChain}; redraw them whenever we append a continuation page.
124
+ * Omit the overflowing root when it is chain[0] — redrawing it would recurse (e.g. wrapper around lines).
125
+ */
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
137
+
138
+ layout.objects.forEach((object) => {
139
+ if (object.type !== 'container') return
140
+ if (overflowingRootContainer !== undefined && object === overflowingRootContainer) return
141
+ const c = object as ContainerLayoutObject
142
+ if (c.printOnlyAtStart === true || c.printOnlyAtEnd === true) return
143
+ if (!dummyLayoutSource(c.source)) return
144
+ addObjectToPDF(0, 0, doc, object, rootPrintBuffer, paperSize, layout, options, rootPrintBuffer, [], context)
145
+ })
146
+ }
147
+
121
148
  /** jsPDF font style strings. fontStyle bitmask: 0=normal, 1=bold, 2=italic, 3=bold+italic */
122
149
  const FONT_STYLES = ['normal', 'bold', 'italic', 'bolditalic'] as const
123
150
 
@@ -453,6 +480,10 @@ function addPage(
453
480
  containerChain: { object: ContainerLayoutObject; printBuffer: any }[],
454
481
  context: RenderContext
455
482
  ): { originX: number; originY: number; x: number; y: number } {
483
+ /** Absolute tenths where the overflowing repeat resumes; callers already computed this correctly. */
484
+ const continuationOxTenths = originX
485
+ const continuationOyTenths = originY
486
+
456
487
  context.pendingRootStartBumpTenths = 0
457
488
  context.applyRootStartBumpToThisCall = false
458
489
  context.remainingRootStartBumpTenths = 0
@@ -468,25 +499,28 @@ function addPage(
468
499
  }
469
500
  if (!context.measureOnly) {
470
501
  drawStaticPartOfPage(doc, layout, rootPrintBuffer, paperSize, options, context)
502
+ redrawRootDummyBodyContainersOnNewPage(doc, layout, rootPrintBuffer, paperSize, options, context, containerChain)
471
503
  }
472
- const absoluteLowerRightHandSide = { x: originX / 10, y: originY / 10 }
473
- // Now draw all the parent containers of the current container
504
+ // Bounding box tracked for snap-to-bottom; continuation cursor is ALWAYS the passed-in coords.
505
+ const absoluteLowerRightHandSide = { x: continuationOxTenths / 10, y: continuationOyTenths / 10 }
474
506
  if (containerChain.length > 0) {
475
- // Let origin start at (x,y) of the outermost container
507
+ // Re-draw ancestor frames on the new page (local coordinates). Do NOT treat this pass as another
508
+ // repeat iteration: skip repeatContainer advancement on the innermost link—the real loop resumes
509
+ // after addPage returns with continuationOxTenths / continuationOyTenths.
476
510
  originX = containerChain[0].object.x
477
511
  originY = containerChain[0].object.y
478
- absoluteLowerRightHandSide.x = originX / 10
479
- absoluteLowerRightHandSide.y = originY / 10
512
+ absoluteLowerRightHandSide.x = Math.max(absoluteLowerRightHandSide.x, originX / 10)
513
+ absoluteLowerRightHandSide.y = Math.max(absoluteLowerRightHandSide.y, originY / 10)
514
+
480
515
  containerChain.forEach((link, linkIdx) => {
481
- const chainChild = containerChain[linkIdx + 1]?.object
516
+ const deeperLinkChild = containerChain[linkIdx + 1]?.object
482
517
  link.object.children.forEach((child) => {
483
518
  if (child.snapToBottom) return
484
- // Advance past bound repeat containers on the overflow path (they are not redrawn here; the
485
- // innermost link draws their leaves). Without this, nested x/y offsets are lost on page 2+.
519
+ // Skip inner repeat container on chain: advancing origin by x,y is enough; the live loop draws rows.
486
520
  if (
487
521
  child.type === 'container' &&
488
- chainChild &&
489
- child === chainChild &&
522
+ deeperLinkChild &&
523
+ child === deeperLinkChild &&
490
524
  !containerRedrawsAfterContinuationPage(child)
491
525
  ) {
492
526
  const c = child as ContainerLayoutObject
@@ -494,17 +528,25 @@ function addPage(
494
528
  originY += c.y
495
529
  return
496
530
  }
497
- if (child.type != 'container' || containerRedrawsAfterContinuationPage(child)) {
531
+ if (child.type !== 'container' || containerRedrawsAfterContinuationPage(child)) {
498
532
  const childLowerRightHandSide = addObjectToPDF(originX, originY, doc, child, link.printBuffer, paperSize, layout, options, rootPrintBuffer, [], context)
499
533
  absoluteLowerRightHandSide.x = Math.max(absoluteLowerRightHandSide.x, childLowerRightHandSide.x)
500
534
  absoluteLowerRightHandSide.y = Math.max(absoluteLowerRightHandSide.y, childLowerRightHandSide.y)
501
535
  }
502
536
  })
503
- if (link.object.repeatContainer == 'horizontal') originX = absoluteLowerRightHandSide.x * 10
504
- if (link.object.repeatContainer == 'vertical') originY = absoluteLowerRightHandSide.y * 10
537
+ const isLastLinkOnChain = linkIdx >= containerChain.length - 1
538
+ if (!isLastLinkOnChain) {
539
+ if (link.object.repeatContainer === 'horizontal') originX = absoluteLowerRightHandSide.x * 10
540
+ if (link.object.repeatContainer === 'vertical') originY = absoluteLowerRightHandSide.y * 10
541
+ }
505
542
  })
506
543
  }
507
- return { originX: originX, originY: originY, x: absoluteLowerRightHandSide.x, y: absoluteLowerRightHandSide.y }
544
+ return {
545
+ originX: continuationOxTenths,
546
+ originY: continuationOyTenths,
547
+ x: absoluteLowerRightHandSide.x,
548
+ y: absoluteLowerRightHandSide.y,
549
+ }
508
550
  }
509
551
 
510
552
  function drawBottomDwellers(
@@ -263,4 +263,72 @@ describe('genPDF', () => {
263
263
  expect(pdfText).toContain('ROW_0_MARK')
264
264
  expect(pdfText).toContain('ROW_35_MARK')
265
265
  })
266
+
267
+ it('redraws top-level empty-source body containers on every continuation page beside an overflowing sibling', async () => {
268
+ const chromeText = {
269
+ type: 'text' as const,
270
+ name: 'chrome-label',
271
+ x: 0,
272
+ y: 0,
273
+ width: 300,
274
+ height: 48,
275
+ active: true,
276
+ text: 'CHROME_EACH_PAGE',
277
+ textAlign: 1 as const,
278
+ fontFamily: 'Helvetica',
279
+ fontSize: 28,
280
+ fontStyle: 0,
281
+ }
282
+ const lineField = {
283
+ type: 'field' as const,
284
+ name: 'ln',
285
+ x: 0,
286
+ y: 0,
287
+ width: 800,
288
+ height: 96,
289
+ active: true,
290
+ source: 'line',
291
+ textAlign: 1 as const,
292
+ fontFamily: 'Helvetica',
293
+ fontSize: 36,
294
+ fontStyle: 0,
295
+ }
296
+ const ITEMS = Array.from({ length: 48 }, (_, i) => ({ line: `SKU_${i}` }))
297
+ const layout: Layout = {
298
+ name: 'sibling-chrome',
299
+ paperSize: { width: 210, height: 297, footerHeight: 20 },
300
+ objects: [
301
+ {
302
+ type: 'container',
303
+ name: 'addressBand',
304
+ x: 60,
305
+ y: 440,
306
+ width: 400,
307
+ height: 280,
308
+ active: true,
309
+ source: '',
310
+ children: [chromeText],
311
+ },
312
+ {
313
+ type: 'container',
314
+ name: 'lines',
315
+ x: 500,
316
+ y: 440,
317
+ width: 1500,
318
+ height: 200,
319
+ active: true,
320
+ source: 'ITEMS',
321
+ repeatContainer: 'vertical' as const,
322
+ children: [lineField],
323
+ },
324
+ ],
325
+ }
326
+
327
+ const doc = await genPDF(layout, { ITEMS })
328
+ expect(doc.getNumberOfPages()).toBeGreaterThanOrEqual(2)
329
+ const pdfText = Buffer.from(doc.output('arraybuffer')).toString('latin1')
330
+ const chromeMatches = pdfText.match(/\(CHROME_EACH_PAGE\)/g)
331
+ expect(chromeMatches?.length ?? 0).toBeGreaterThanOrEqual(doc.getNumberOfPages())
332
+ expect(pdfText).toContain('SKU_20')
333
+ })
266
334
  })