@pdfme/common 4.3.2 → 4.4.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.
package/src/helper.ts CHANGED
@@ -19,6 +19,8 @@ import {
19
19
  DEFAULT_FONT_VALUE,
20
20
  } from './constants.js';
21
21
 
22
+ export const cloneDeep = <T>(value: T): T => JSON.parse(JSON.stringify(value));
23
+
22
24
  const uniq = <T>(array: Array<T>) => Array.from(new Set(array));
23
25
 
24
26
  export const getFallbackFontName = (font: Font) => {
@@ -228,112 +230,267 @@ interface ModifyTemplateForDynamicTableArg {
228
230
  input: Record<string, string>;
229
231
  _cache: Map<any, any>;
230
232
  options: CommonOptions;
231
- modifyTemplate: (arg: {
232
- template: Template;
233
- input: Record<string, string>;
234
- _cache: Map<any, any>;
235
- options: CommonOptions;
236
- }) => Promise<Template>;
237
- getDynamicHeight: (
233
+ getDynamicHeights: (
238
234
  value: string,
239
235
  args: { schema: Schema; basePdf: BasePdf; options: CommonOptions; _cache: Map<any, any> }
240
- ) => Promise<number>;
236
+ ) => Promise<number[]>;
241
237
  }
242
238
 
243
- export const getDynamicTemplate = async (
244
- arg: ModifyTemplateForDynamicTableArg
245
- ): Promise<Template> => {
246
- const { template, modifyTemplate } = arg;
247
- if (!isBlankPdf(template.basePdf)) {
248
- return template;
239
+ class Node {
240
+ index = 0;
241
+
242
+ key?: string;
243
+ schema?: Schema;
244
+
245
+ children: Node[] = [];
246
+
247
+ width = 0;
248
+ height = 0;
249
+ padding: [number, number, number, number] = [0, 0, 0, 0];
250
+ position: { x: number; y: number } = { x: 0, y: 0 };
251
+
252
+ constructor({ width = 0, height = 0 } = {}) {
253
+ this.width = width;
254
+ this.height = height;
249
255
  }
250
256
 
251
- const modifiedTemplate = await modifyTemplate(arg);
257
+ setIndex(index: number): void {
258
+ this.index = index;
259
+ }
252
260
 
253
- const diffMap = await calculateDiffMap({ ...arg, template: modifiedTemplate });
261
+ setKeyAndSchema(key: string, schema: Schema): void {
262
+ this.key = key;
263
+ this.schema = schema;
264
+ }
254
265
 
255
- return normalizePositionsAndPageBreak(modifiedTemplate, diffMap);
256
- };
266
+ setWidth(width: number): void {
267
+ this.width = width;
268
+ }
257
269
 
258
- export const calculateDiffMap = async (arg: ModifyTemplateForDynamicTableArg) => {
259
- const { template, input, _cache, options, getDynamicHeight } = arg;
260
- const basePdf = template.basePdf;
261
- const tmpDiffMap = new Map<number, number>();
262
- if (!isBlankPdf(basePdf)) {
263
- return tmpDiffMap;
270
+ setHeight(height: number): void {
271
+ this.height = height;
264
272
  }
265
- const pageHeight = basePdf.height;
266
- let pageIndex = 0;
267
- for (const schemaObj of template.schemas) {
268
- for (const [key, schema] of Object.entries(schemaObj)) {
269
- const dynamicHeight = await getDynamicHeight(input?.[key] || '', {
270
- schema,
271
- basePdf,
272
- options,
273
- _cache,
274
- });
275
- if (schema.height !== dynamicHeight) {
276
- tmpDiffMap.set(
277
- schema.position.y + schema.height + pageHeight * pageIndex,
278
- dynamicHeight - schema.height
279
- );
280
- }
281
- }
282
- pageIndex++;
273
+
274
+ setPadding(padding: [number, number, number, number]): void {
275
+ this.padding = padding;
283
276
  }
284
277
 
285
- const diffMap = new Map<number, number>();
286
- const keys = Array.from(tmpDiffMap.keys()).sort((a, b) => a - b);
287
- let additionalHeight = 0;
278
+ setPosition(position: { x: number; y: number }): void {
279
+ this.position = position;
280
+ }
288
281
 
289
- for (const key of keys) {
290
- const value = tmpDiffMap.get(key) as number;
291
- const newValue = value + additionalHeight;
292
- diffMap.set(key + additionalHeight, newValue);
293
- additionalHeight += newValue;
282
+ insertChild(child: Node): void {
283
+ const index = this.getChildCount();
284
+ child.setIndex(index);
285
+ this.children.splice(index, 0, child);
294
286
  }
295
287
 
296
- return diffMap;
297
- };
288
+ getChildCount(): number {
289
+ return this.children.length;
290
+ }
298
291
 
299
- export const normalizePositionsAndPageBreak = (
300
- template: Template,
301
- diffMap: Map<number, number>
302
- ): Template => {
303
- if (!isBlankPdf(template.basePdf) || diffMap.size === 0) {
304
- return template;
292
+ getChild(index: number): Node {
293
+ return this.children[index];
305
294
  }
295
+ }
306
296
 
307
- const returnTemplate: Template = { schemas: [{}], basePdf: template.basePdf };
308
- const pages = returnTemplate.schemas;
309
- const pageHeight = template.basePdf.height;
310
- const paddingTop = template.basePdf.padding[0];
311
- const paddingBottom = template.basePdf.padding[2];
297
+ function createPage(basePdf: BlankPdf) {
298
+ const page = new Node({ ...basePdf });
299
+ page.setPadding(basePdf.padding);
300
+ return page;
301
+ }
312
302
 
313
- for (let i = 0; i < template.schemas.length; i += 1) {
314
- const schemaObj = template.schemas[i];
315
- if (!pages[i]) pages[i] = {};
303
+ function createNode(arg: {
304
+ key: string;
305
+ schema: Schema;
306
+ position: { x: number; y: number };
307
+ width: number;
308
+ height: number;
309
+ }) {
310
+ const { position, width, height, key, schema } = arg;
311
+ const node = new Node({ width, height });
312
+ node.setPosition(position);
313
+ node.setKeyAndSchema(key, schema);
314
+ return node;
315
+ }
316
316
 
317
- for (const [key, schema] of Object.entries(schemaObj)) {
318
- const { position, height } = schema;
319
- let newY = position.y;
320
- let pageCursor = i;
317
+ function resortChildren(page: Node, orderMap: Map<string, number>): void {
318
+ page.children = page.children
319
+ .sort((a, b) => {
320
+ const orderA = orderMap.get(a.key!);
321
+ const orderB = orderMap.get(b.key!);
322
+ if (orderA === undefined || orderB === undefined) {
323
+ throw new Error('[@pdfme/common] order is not defined');
324
+ }
325
+ return orderA - orderB;
326
+ })
327
+ .map((child, index) => {
328
+ child.setIndex(index);
329
+ return child;
330
+ });
331
+ }
321
332
 
322
- for (const [diffKey, diffValue] of diffMap) {
323
- if (newY > diffKey) {
324
- newY += diffValue;
333
+ async function createOnePage(
334
+ arg: {
335
+ basePdf: BlankPdf;
336
+ schemaObj: Record<string, Schema>;
337
+ orderMap: Map<string, number>;
338
+ } & Omit<ModifyTemplateForDynamicTableArg, 'template'>
339
+ ): Promise<Node> {
340
+ const { basePdf, schemaObj, orderMap, input, options, _cache, getDynamicHeights } = arg;
341
+ const page = createPage(basePdf);
342
+
343
+ const schemaPositions: number[] = [];
344
+ const sortedSchemaEntries = Object.entries(schemaObj).sort(
345
+ (a, b) => a[1].position.y - b[1].position.y
346
+ );
347
+ const diffMap = new Map();
348
+ for (const [key, schema] of sortedSchemaEntries) {
349
+ const { position, width } = schema;
350
+
351
+ const opt = { schema, basePdf, options, _cache };
352
+ const heights = await getDynamicHeights(input?.[key] || '', opt);
353
+
354
+ const heightsSum = heights.reduce((acc, cur) => acc + cur, 0);
355
+ const originalHeight = schema.height;
356
+ if (heightsSum !== originalHeight) {
357
+ diffMap.set(position.y + originalHeight, heightsSum - originalHeight);
358
+ }
359
+ heights.forEach((height, index) => {
360
+ let y = schema.position.y + heights.reduce((acc, cur, i) => (i < index ? acc + cur : acc), 0);
361
+ for (const [diffY, diff] of diffMap.entries()) {
362
+ if (diffY <= schema.position.y) {
363
+ y += diff;
325
364
  }
326
365
  }
366
+ const node = createNode({ key, schema, position: { ...position, y }, width, height });
367
+
368
+ schemaPositions.push(y + height + basePdf.padding[2]);
369
+ page.insertChild(node);
370
+ });
371
+ }
372
+
373
+ const pageHeight = Math.max(...schemaPositions, basePdf.height - basePdf.padding[2]);
374
+ page.setHeight(pageHeight);
375
+
376
+ resortChildren(page, orderMap);
327
377
 
328
- while (newY + height >= pageHeight - paddingBottom) {
329
- newY = newY + paddingTop - (pageHeight - paddingBottom) + paddingTop;
330
- pageCursor++;
378
+ return page;
379
+ }
380
+
381
+ function breakIntoPages(arg: {
382
+ longPage: Node;
383
+ orderMap: Map<string, number>;
384
+ basePdf: BlankPdf;
385
+ }): Node[] {
386
+ const { longPage, orderMap, basePdf } = arg;
387
+ const pages: Node[] = [createPage(basePdf)];
388
+ const [paddingTop, , paddingBottom] = basePdf.padding;
389
+ const yAdjustments: { page: number; value: number }[] = [];
390
+
391
+ const getPageHeight = (pageIndex: number) =>
392
+ basePdf.height - paddingBottom - (pageIndex > 0 ? paddingTop : 0);
393
+
394
+ const calculateNewY = (y: number, pageIndex: number) => {
395
+ const newY = y - pageIndex * (basePdf.height - paddingTop - paddingBottom);
396
+
397
+ while (pages.length <= pageIndex) {
398
+ if (!pages[pageIndex]) {
399
+ pages.push(createPage(basePdf));
400
+ yAdjustments.push({ page: pageIndex, value: (newY - paddingTop) * -1 });
331
401
  }
402
+ }
403
+ return newY + (yAdjustments.find((adj) => adj.page === pageIndex)?.value || 0);
404
+ };
405
+
406
+ const children = longPage.children.sort((a, b) => a.position.y - b.position.y);
407
+ for (let i = 0; i < children.length; i++) {
408
+ const { key, schema, position, height, width } = children[i];
409
+ const { y, x } = position;
410
+
411
+ let targetPageIndex = Math.floor(y / getPageHeight(pages.length - 1));
412
+ let newY = calculateNewY(y, targetPageIndex);
332
413
 
333
- if (!pages[pageCursor]) pages[pageCursor] = {};
334
- pages[pageCursor][key] = { ...schema, position: { ...position, y: newY } };
414
+ if (newY + height > basePdf.height - paddingBottom) {
415
+ targetPageIndex++;
416
+ newY = calculateNewY(y, targetPageIndex);
335
417
  }
418
+
419
+ if (!key || !schema) throw new Error('[@pdfme/common] key or schema is undefined');
420
+
421
+ const clonedElement = createNode({ key, schema, position: { x, y: newY }, width, height });
422
+ pages[targetPageIndex].insertChild(clonedElement);
423
+ }
424
+
425
+ pages.forEach((page) => resortChildren(page, orderMap));
426
+
427
+ return pages;
428
+ }
429
+
430
+ function createNewTemplate(pages: Node[], basePdf: BlankPdf): Template {
431
+ const newTemplate: Template = {
432
+ schemas: Array.from({ length: pages.length }, () => ({} as Record<string, Schema>)),
433
+ basePdf: basePdf,
434
+ };
435
+
436
+ const keyToSchemas = new Map<string, Node[]>();
437
+
438
+ cloneDeep(pages).forEach((page, pageIndex) => {
439
+ page.children.forEach((child) => {
440
+ const { key, schema } = child;
441
+ if (!key || !schema) throw new Error('[@pdfme/common] key or schema is undefined');
442
+
443
+ if (!keyToSchemas.has(key)) {
444
+ keyToSchemas.set(key, []);
445
+ }
446
+ keyToSchemas.get(key)!.push(child);
447
+
448
+ const sameKeySchemas = page.children.filter((c) => c.key === key);
449
+ const start = keyToSchemas.get(key)!.length - sameKeySchemas.length;
450
+
451
+ if (sameKeySchemas.length > 0) {
452
+ if (!sameKeySchemas[0].schema) {
453
+ throw new Error('[@pdfme/common] schema is undefined');
454
+ }
455
+
456
+ // Use the first schema to get the schema and position
457
+ const schema = sameKeySchemas[0].schema;
458
+ const height = sameKeySchemas.reduce((acc, cur) => acc + cur.height, 0);
459
+ const position = sameKeySchemas[0].position;
460
+
461
+ // Currently, __bodyRange exists for table schemas, but if we make it more abstract,
462
+ // it could be used for other schemas as well to render schemas that have been split by page breaks, starting from the middle.
463
+ schema.__bodyRange = {
464
+ start: Math.max(start - 1, 0),
465
+ end: start + sameKeySchemas.length - 1,
466
+ };
467
+
468
+ newTemplate.schemas[pageIndex][key] = Object.assign({}, schema, { position, height });
469
+ }
470
+ });
471
+ });
472
+
473
+ return newTemplate;
474
+ }
475
+
476
+ export const getDynamicTemplate = async (
477
+ arg: ModifyTemplateForDynamicTableArg
478
+ ): Promise<Template> => {
479
+ const { template } = arg;
480
+ if (!isBlankPdf(template.basePdf)) {
481
+ return template;
482
+ }
483
+
484
+ const basePdf = template.basePdf as BlankPdf;
485
+ const pages: Node[] = [];
486
+
487
+ for (const schemaObj of template.schemas) {
488
+ const orderMap = new Map(Object.keys(schemaObj).map((key, index) => [key, index]));
489
+
490
+ const longPage = await createOnePage({ basePdf, schemaObj, orderMap, ...arg });
491
+ const brokenPages = breakIntoPages({ longPage, basePdf, orderMap });
492
+ pages.push(...brokenPages);
336
493
  }
337
494
 
338
- return returnTemplate;
495
+ return createNewTemplate(pages, template.basePdf);
339
496
  };
package/src/index.ts CHANGED
@@ -37,6 +37,7 @@ import type {
37
37
  DesignerProps,
38
38
  } from './types.js';
39
39
  import {
40
+ cloneDeep,
40
41
  getFallbackFontName,
41
42
  getDefaultFont,
42
43
  getB64BasePdf,
@@ -67,6 +68,7 @@ export {
67
68
  BLANK_PDF,
68
69
  ZOOM,
69
70
  DEFAULT_FONT_NAME,
71
+ cloneDeep,
70
72
  getFallbackFontName,
71
73
  getDefaultFont,
72
74
  getB64BasePdf,