@softwear/latestcollectioncore 1.0.177 → 1.0.179

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
@@ -43,9 +43,21 @@ function getProperty(propertyName, object) {
43
43
  }
44
44
  return property;
45
45
  }
46
- /** Repeat once over the current buffer when the container is only a visual group (no `source` binding). */
46
+ /** Repeat once when the container has no data binding (`source` empty / whitespace-only). */
47
+ function dummyLayoutSource(source) {
48
+ return source === undefined || String(source).trim() === '';
49
+ }
50
+ /** Like repeatOnOverflow: redraw after a page break so nested overflowing content can proceed. */
51
+ function containerRedrawsAfterContinuationPage(child) {
52
+ if (child.type !== 'container')
53
+ return false;
54
+ const c = child;
55
+ if (c.repeatOnOverflow)
56
+ return true;
57
+ return dummyLayoutSource(c.source);
58
+ }
47
59
  function containerLoopSource(container, printBuffer) {
48
- if (!container.source || container.source === '')
60
+ if (dummyLayoutSource(container.source))
49
61
  return [printBuffer];
50
62
  return getProperty(container.source, printBuffer);
51
63
  }
@@ -368,6 +380,7 @@ function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options
368
380
  }
369
381
  }
370
382
  function addPage(originX, originY, doc, layout, rootPrintBuffer, paperSize, options, containerChain, context) {
383
+ context.pendingRootStartBumpTenths = 0;
371
384
  if (context.mode === 'measurement') {
372
385
  // Track page count in measurement mode
373
386
  if (context.currentPageCount === undefined) {
@@ -392,7 +405,7 @@ function addPage(originX, originY, doc, layout, rootPrintBuffer, paperSize, opti
392
405
  absoluteLowerRightHandSide.y = originY / 10;
393
406
  containerChain.forEach((link) => {
394
407
  link.object.children.forEach((child) => {
395
- if (!child.snapToBottom && (child.type != 'container' || child.repeatOnOverflow)) {
408
+ if (!child.snapToBottom && (child.type != 'container' || containerRedrawsAfterContinuationPage(child))) {
396
409
  const childLowerRightHandSide = addObjectToPDF(originX, originY, doc, child, link.printBuffer, paperSize, layout, options, rootPrintBuffer, [], context);
397
410
  absoluteLowerRightHandSide.x = Math.max(absoluteLowerRightHandSide.x, childLowerRightHandSide.x);
398
411
  absoluteLowerRightHandSide.y = Math.max(absoluteLowerRightHandSide.y, childLowerRightHandSide.y);
@@ -441,9 +454,10 @@ function drawBottomDwellers(object, originX, originY, absoluteLowerRightHandSide
441
454
  * Use originX,originY as origin to displace the object's own x,y coordinates
442
455
  */
443
456
  addObjectToPDF = function (originX, originY, doc, object, printBuffer, paperSize, layout, options, rootPrintBuffer, containerChain, context) {
444
- var _a;
457
+ var _a, _b;
458
+ const rootBump = containerChain.length === 0 ? (_a = context.pendingRootStartBumpTenths) !== null && _a !== void 0 ? _a : 0 : 0;
445
459
  const x = (originX + object.x) / 10;
446
- const y = (originY + object.y) / 10;
460
+ const y = (originY + object.y + rootBump) / 10;
447
461
  const width = object.width / 10;
448
462
  const height = object.height / 10;
449
463
  // Keep track of the lower righthandside of all objects in this container
@@ -458,7 +472,7 @@ addObjectToPDF = function (originX, originY, doc, object, printBuffer, paperSize
458
472
  let originX = x * 10, originY = y * 10;
459
473
  const source = containerLoopSource(object, printBuffer);
460
474
  if (source === undefined) {
461
- (_a = options.onAlert) === null || _a === void 0 ? void 0 : _a.call(options, { header: 'containernotfound', body: object.source, type: 'warning', timeout: 10000 });
475
+ (_b = options.onAlert) === null || _b === void 0 ? void 0 : _b.call(options, { header: 'containernotfound', body: object.source, type: 'warning', timeout: 10000 });
462
476
  return absoluteLowerRightHandSide;
463
477
  }
464
478
  const nrContainers = source.length;
@@ -663,21 +677,25 @@ function genPDF(layout, printBuffer, options = {}) {
663
677
  }
664
678
  drawStaticPartOfPage(measurementDoc, layout, printBuffer, paperSize, options, measurementContext);
665
679
  // Draw containers in measurement mode (same order as render)
680
+ let measurementTopPrintedStartBottomTenths = 0;
666
681
  layout.objects
667
682
  .filter((object) => object.type == 'container' && object.printOnlyAtStart)
668
683
  .forEach((object) => {
669
- addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext);
684
+ const lh = addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext);
685
+ measurementTopPrintedStartBottomTenths = Math.max(measurementTopPrintedStartBottomTenths, lh.y * 10);
670
686
  });
671
687
  layout.objects
672
688
  .filter((object) => object.type == 'container' &&
673
689
  !object.printOnlyAtEnd &&
674
690
  !object.printOnlyAtStart)
675
691
  .forEach((object) => {
692
+ measurementContext.pendingRootStartBumpTenths = Math.max(0, measurementTopPrintedStartBottomTenths - object.y);
676
693
  addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext);
677
694
  });
678
695
  layout.objects
679
696
  .filter((object) => object.type == 'container' && object.printOnlyAtEnd)
680
697
  .forEach((object) => {
698
+ measurementContext.pendingRootStartBumpTenths = Math.max(0, measurementTopPrintedStartBottomTenths - object.y);
681
699
  addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext);
682
700
  });
683
701
  // Get total page count from measurement pass
@@ -697,10 +715,12 @@ function genPDF(layout, printBuffer, options = {}) {
697
715
  defaultFontFamily: layout.defaultFontFamily,
698
716
  };
699
717
  drawStaticPartOfPage(doc, layout, printBuffer, paperSize, options, renderContext);
718
+ let renderTopPrintedStartBottomTenths = 0;
700
719
  layout.objects
701
720
  .filter((object) => object.type == 'container' && object.printOnlyAtStart)
702
721
  .forEach((object) => {
703
- addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
722
+ const lh = addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
723
+ renderTopPrintedStartBottomTenths = Math.max(renderTopPrintedStartBottomTenths, lh.y * 10);
704
724
  });
705
725
  // Draw containers in rendering mode (body containers after printOnlyAtStart)
706
726
  layout.objects
@@ -708,11 +728,13 @@ function genPDF(layout, printBuffer, options = {}) {
708
728
  !object.printOnlyAtEnd &&
709
729
  !object.printOnlyAtStart)
710
730
  .forEach((object) => {
731
+ renderContext.pendingRootStartBumpTenths = Math.max(0, renderTopPrintedStartBottomTenths - object.y);
711
732
  addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
712
733
  });
713
734
  // Draw printOnlyAtEnd containers once at the end (at their layout coordinates on current page)
714
735
  const printOnlyAtEndContainers = layout.objects.filter((object) => object.type == 'container' && object.printOnlyAtEnd);
715
736
  printOnlyAtEndContainers.forEach((object) => {
737
+ renderContext.pendingRootStartBumpTenths = Math.max(0, renderTopPrintedStartBottomTenths - object.y);
716
738
  addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext);
717
739
  });
718
740
  return doc;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwear/latestcollectioncore",
3
- "version": "1.0.177",
3
+ "version": "1.0.179",
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
@@ -46,6 +46,8 @@ interface RenderContext {
46
46
  imageAssets?: Map<string, PdfImageAsset>
47
47
  /** Layout default font used when object.fontFamily is undefined */
48
48
  defaultFontFamily?: string
49
+ /** Vertical pad for root draws after printOnlyAtStart; set per root container; addPage resets to 0 after each page break. */
50
+ pendingRootStartBumpTenths?: number
49
51
  }
50
52
 
51
53
  // We have to declare some functions that will be called by other functions but also have to call those other functions
@@ -92,10 +94,22 @@ function getProperty(propertyName: string, object: any): any {
92
94
  return property
93
95
  }
94
96
 
95
- /** Repeat once over the current buffer when the container is only a visual group (no `source` binding). */
97
+ /** Repeat once when the container has no data binding (`source` empty / whitespace-only). */
98
+ function dummyLayoutSource(source?: string): boolean {
99
+ return source === undefined || String(source).trim() === ''
100
+ }
101
+
102
+ /** Like repeatOnOverflow: redraw after a page break so nested overflowing content can proceed. */
103
+ function containerRedrawsAfterContinuationPage(child: LayoutObject): boolean {
104
+ if (child.type !== 'container') return false
105
+ const c = child as ContainerLayoutObject
106
+ if (c.repeatOnOverflow) return true
107
+ return dummyLayoutSource(c.source)
108
+ }
109
+
96
110
  function containerLoopSource(container: ContainerLayoutObject, printBuffer: any): any {
97
- if (!container.source || container.source === '') return [printBuffer]
98
- return getProperty(container.source, printBuffer)
111
+ if (dummyLayoutSource(container.source)) return [printBuffer]
112
+ return getProperty(container.source as string, printBuffer)
99
113
  }
100
114
 
101
115
  /** jsPDF font style strings. fontStyle bitmask: 0=normal, 1=bold, 2=italic, 3=bold+italic */
@@ -433,6 +447,7 @@ function addPage(
433
447
  containerChain: { object: ContainerLayoutObject; printBuffer: any }[],
434
448
  context: RenderContext
435
449
  ): { originX: number; originY: number; x: number; y: number } {
450
+ context.pendingRootStartBumpTenths = 0
436
451
  if (context.mode === 'measurement') {
437
452
  // Track page count in measurement mode
438
453
  if (context.currentPageCount === undefined) {
@@ -456,7 +471,7 @@ function addPage(
456
471
  absoluteLowerRightHandSide.y = originY / 10
457
472
  containerChain.forEach((link) => {
458
473
  link.object.children.forEach((child) => {
459
- if (!child.snapToBottom && (child.type != 'container' || child.repeatOnOverflow)) {
474
+ if (!child.snapToBottom && (child.type != 'container' || containerRedrawsAfterContinuationPage(child))) {
460
475
  const childLowerRightHandSide = addObjectToPDF(originX, originY, doc, child, link.printBuffer, paperSize, layout, options, rootPrintBuffer, [], context)
461
476
  absoluteLowerRightHandSide.x = Math.max(absoluteLowerRightHandSide.x, childLowerRightHandSide.x)
462
477
  absoluteLowerRightHandSide.y = Math.max(absoluteLowerRightHandSide.y, childLowerRightHandSide.y)
@@ -529,8 +544,9 @@ addObjectToPDF = function (
529
544
  containerChain: { object: ContainerLayoutObject; printBuffer: any }[],
530
545
  context: RenderContext
531
546
  ): { x: number; y: number } {
547
+ const rootBump = containerChain.length === 0 ? context.pendingRootStartBumpTenths ?? 0 : 0
532
548
  const x = (originX + object.x) / 10
533
- const y = (originY + object.y) / 10
549
+ const y = (originY + object.y + rootBump) / 10
534
550
  const width = object.width / 10
535
551
  const height = object.height / 10
536
552
  // Keep track of the lower righthandside of all objects in this container
@@ -786,10 +802,12 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
786
802
  drawStaticPartOfPage(measurementDoc, layout, printBuffer, paperSize, options, measurementContext)
787
803
 
788
804
  // Draw containers in measurement mode (same order as render)
805
+ let measurementTopPrintedStartBottomTenths = 0
789
806
  layout.objects
790
807
  .filter((object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtStart)
791
808
  .forEach((object) => {
792
- addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext)
809
+ const lh = addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext)
810
+ measurementTopPrintedStartBottomTenths = Math.max(measurementTopPrintedStartBottomTenths, lh.y * 10)
793
811
  })
794
812
  layout.objects
795
813
  .filter(
@@ -799,11 +817,13 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
799
817
  !(object as ContainerLayoutObject).printOnlyAtStart
800
818
  )
801
819
  .forEach((object) => {
820
+ measurementContext.pendingRootStartBumpTenths = Math.max(0, measurementTopPrintedStartBottomTenths - object.y)
802
821
  addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext)
803
822
  })
804
823
  layout.objects
805
824
  .filter((object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtEnd)
806
825
  .forEach((object) => {
826
+ measurementContext.pendingRootStartBumpTenths = Math.max(0, measurementTopPrintedStartBottomTenths - object.y)
807
827
  addObjectToPDF(0, 0, measurementDoc, object, printBuffer, paperSize, layout, options, printBuffer, [], measurementContext)
808
828
  })
809
829
 
@@ -826,10 +846,12 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
826
846
  }
827
847
  drawStaticPartOfPage(doc, layout, printBuffer, paperSize, options, renderContext)
828
848
 
849
+ let renderTopPrintedStartBottomTenths = 0
829
850
  layout.objects
830
851
  .filter((object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtStart)
831
852
  .forEach((object) => {
832
- addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
853
+ const lh = addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
854
+ renderTopPrintedStartBottomTenths = Math.max(renderTopPrintedStartBottomTenths, lh.y * 10)
833
855
  })
834
856
 
835
857
  // Draw containers in rendering mode (body containers after printOnlyAtStart)
@@ -841,6 +863,7 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
841
863
  !(object as ContainerLayoutObject).printOnlyAtStart
842
864
  )
843
865
  .forEach((object) => {
866
+ renderContext.pendingRootStartBumpTenths = Math.max(0, renderTopPrintedStartBottomTenths - object.y)
844
867
  addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
845
868
  })
846
869
 
@@ -849,6 +872,7 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
849
872
  (object) => object.type == 'container' && (object as ContainerLayoutObject).printOnlyAtEnd
850
873
  )
851
874
  printOnlyAtEndContainers.forEach((object) => {
875
+ renderContext.pendingRootStartBumpTenths = Math.max(0, renderTopPrintedStartBottomTenths - object.y)
852
876
  addObjectToPDF(0, 0, doc, object, printBuffer, paperSize, layout, options, printBuffer, [], renderContext)
853
877
  })
854
878
  return doc
@@ -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,115 @@ 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
+ })
212
+
213
+ it('redraws empty-source wrapper so inner vertical repeat spans multiple pages', async () => {
214
+ const rowField = {
215
+ type: 'field' as const,
216
+ name: 'ln',
217
+ x: 0,
218
+ y: 0,
219
+ width: 1600,
220
+ height: 80,
221
+ active: true,
222
+ source: 'line',
223
+ textAlign: 1 as const,
224
+ fontFamily: 'Helvetica',
225
+ fontSize: 36,
226
+ fontStyle: 0,
227
+ }
228
+ const ROWS = Array.from({ length: 40 }, (_, i) => ({ line: `ROW_${i}_MARK` }))
229
+ const layout: Layout = {
230
+ name: 'wrapper-overflow',
231
+ paperSize: { width: 210, height: 297, footerHeight: 20 },
232
+ objects: [
233
+ {
234
+ type: 'container',
235
+ name: 'wrap',
236
+ x: 50,
237
+ y: 100,
238
+ width: 1900,
239
+ height: 2800,
240
+ active: true,
241
+ source: '',
242
+ children: [
243
+ {
244
+ type: 'container',
245
+ name: 'rows',
246
+ x: 0,
247
+ y: 0,
248
+ width: 1800,
249
+ height: 120,
250
+ active: true,
251
+ source: 'ROWS',
252
+ repeatContainer: 'vertical' as const,
253
+ children: [rowField],
254
+ },
255
+ ],
256
+ },
257
+ ],
258
+ }
259
+
260
+ const doc = await genPDF(layout, { ROWS })
261
+ expect(doc.getNumberOfPages()).toBeGreaterThanOrEqual(2)
262
+ const pdfText = Buffer.from(doc.output('arraybuffer')).toString('latin1')
263
+ expect(pdfText).toContain('ROW_0_MARK')
264
+ expect(pdfText).toContain('ROW_35_MARK')
265
+ })
141
266
  })