@hellkite/pipkin 0.7.0 → 0.8.2

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.
Files changed (72) hide show
  1. package/build/src/lib/bundler.js.map +1 -1
  2. package/build/src/lib/index.d.ts +0 -1
  3. package/build/src/lib/index.js +0 -1
  4. package/build/src/lib/index.js.map +1 -1
  5. package/build/src/lib/replacement.d.ts +5 -3
  6. package/build/src/lib/replacement.js +6 -3
  7. package/build/src/lib/replacement.js.map +1 -1
  8. package/build/src/lib/template.d.ts +25 -20
  9. package/build/src/lib/template.js +176 -45
  10. package/build/src/lib/template.js.map +1 -1
  11. package/build/src/lib/types/containers.js +2 -7
  12. package/build/src/lib/types/containers.js.map +1 -1
  13. package/build/src/lib/types/hypernode.d.ts +1 -1
  14. package/build/src/lib/types/image.d.ts +6 -5
  15. package/build/src/lib/types/image.js +2 -7
  16. package/build/src/lib/types/image.js.map +1 -1
  17. package/build/src/lib/types/index.d.ts +2 -0
  18. package/build/src/lib/types/index.js +2 -0
  19. package/build/src/lib/types/index.js.map +1 -1
  20. package/build/src/lib/types/layer.d.ts +10 -1
  21. package/build/src/lib/types/layer.js +7 -0
  22. package/build/src/lib/types/layer.js.map +1 -1
  23. package/build/src/lib/types/pdf.d.ts +1 -0
  24. package/build/src/lib/types/pdf.js +3 -0
  25. package/build/src/lib/types/pdf.js.map +1 -0
  26. package/build/src/lib/types/replacement.d.ts +2 -2
  27. package/build/src/lib/types/template.d.ts +13 -0
  28. package/build/src/lib/types/template.js +10 -0
  29. package/build/src/lib/types/template.js.map +1 -0
  30. package/build/src/lib/types/text.d.ts +2 -2
  31. package/build/src/lib/types/text.js +3 -10
  32. package/build/src/lib/types/text.js.map +1 -1
  33. package/build/src/lib/utils/container.js.map +1 -1
  34. package/build/src/lib/utils/htmlToImage.js +0 -1
  35. package/build/src/lib/utils/htmlToImage.js.map +1 -1
  36. package/build/src/lib/utils/imagesToPdf.d.ts +2 -0
  37. package/build/src/lib/utils/imagesToPdf.js +31 -0
  38. package/build/src/lib/utils/imagesToPdf.js.map +1 -0
  39. package/build/src/lib/utils/index.d.ts +1 -2
  40. package/build/src/lib/utils/index.js +1 -2
  41. package/build/src/lib/utils/index.js.map +1 -1
  42. package/build/src/lib/utils/placeBoundingBox.js +11 -3
  43. package/build/src/lib/utils/placeBoundingBox.js.map +1 -1
  44. package/build/src/lib/utils/reduceBoundingBox.d.ts +2 -0
  45. package/build/src/lib/utils/reduceBoundingBox.js +16 -0
  46. package/build/src/lib/utils/reduceBoundingBox.js.map +1 -0
  47. package/build/src/test.js +115 -27
  48. package/build/src/test.js.map +1 -1
  49. package/package.json +2 -1
  50. package/roadmap.md +1 -0
  51. package/src/lib/bundler.ts +1 -1
  52. package/src/lib/index.ts +0 -1
  53. package/src/lib/template.ts +327 -84
  54. package/src/lib/types/containers.ts +3 -5
  55. package/src/lib/types/hypernode.ts +1 -2
  56. package/src/lib/types/image.ts +15 -10
  57. package/src/lib/types/index.ts +2 -0
  58. package/src/lib/types/layer.ts +24 -1
  59. package/src/lib/types/pdf.ts +1 -0
  60. package/src/lib/types/render.ts +2 -4
  61. package/src/lib/types/replacement.ts +2 -2
  62. package/src/lib/types/template.ts +22 -0
  63. package/src/lib/types/text.ts +6 -7
  64. package/src/lib/utils/container.ts +0 -1
  65. package/src/lib/utils/htmlToImage.ts +0 -1
  66. package/src/lib/utils/imagesToPdf.ts +24 -0
  67. package/src/lib/utils/index.ts +1 -2
  68. package/src/lib/utils/placeBoundingBox.ts +33 -13
  69. package/src/lib/utils/reduceBoundingBox.ts +21 -0
  70. package/src/lib/replacement.ts +0 -21
  71. package/src/lib/utils/placeImage.ts +0 -69
  72. package/src/lib/utils/placeText.ts +0 -110
@@ -1,6 +1,6 @@
1
1
  import * as fs from 'fs';
2
2
  import { parse as parseCsv } from 'papaparse';
3
- import { Jimp, rgbaToInt } from 'jimp';
3
+ import { Jimp } from 'jimp';
4
4
  import camelCase from 'lodash.camelcase';
5
5
  import concat from 'lodash.concat';
6
6
  import path from 'path';
@@ -9,11 +9,9 @@ import {
9
9
  gridPackingFn,
10
10
  hboxPackingFn,
11
11
  htmlToImage,
12
- placeImage,
13
- placeText,
14
12
  vboxPackingFn,
15
- prepareText,
16
- prepareImage,
13
+ boundingBoxToPx,
14
+ toPx,
17
15
  } from './utils';
18
16
  import {
19
17
  DirectionContainerOptions,
@@ -37,52 +35,41 @@ import {
37
35
  ElementsFn,
38
36
  ElementRef,
39
37
  ImageLayerSpecificOptions,
38
+ TextLayerSpecificOptions,
39
+ SCALE_MODE_TO_OBJECT_FIT,
40
+ StaticImageRef,
41
+ TemplateOptions,
42
+ DEFAULT_TEMPLATE_OPTIONS,
43
+ LayerFn,
44
+ ReplacementMap,
40
45
  } from './types';
41
46
  import merge from 'lodash.merge';
42
47
  import { RequiredDeep } from 'type-fest';
43
48
  import { h } from 'virtual-dom';
44
49
  import flatten from 'lodash.flatten';
45
-
46
- type RequiredTemplateOptions = {
47
- height: number;
48
- width: number;
49
- color: number;
50
- };
51
-
52
- type OptionalTemplateOptions = Partial<{
53
- defaultFontFamily: string;
54
- defaultAssetsPath: string;
55
- }>;
56
-
57
- export type TemplateOptions = RequiredTemplateOptions & OptionalTemplateOptions;
58
-
59
- const DEFAULT_TEMPLATE_OPTIONS: RequiredTemplateOptions = {
60
- height: 1050,
61
- width: 750,
62
- color: rgbaToInt(255, 255, 255, 255),
63
- };
64
-
65
- export type LayerFnContext = {
66
- debugMode: boolean;
67
- };
68
-
69
- export type LayerFn<EntryType> = (
70
- entry: EntryType,
71
- context: LayerFnContext,
72
- ) => Promise<Array<HyperNode>>;
50
+ import { Bundler } from './bundler';
73
51
 
74
52
  export type TemplateLayerFn<EntryType extends Record<string, string>> = (
75
53
  template: Template<EntryType>,
76
54
  ) => Template<EntryType>;
77
55
 
78
56
  export class Template<EntryType extends Record<string, string>> {
79
- private readonly fonts: Record<string, string> = {};
80
- private readonly layers: LayerFn<EntryType>[] = [];
57
+ /**
58
+ * Template data
59
+ */
81
60
  private readonly background: ImageType;
82
- private debugMode: boolean = false;
61
+ private readonly layers: LayerFn<EntryType>[] = [];
62
+ private readonly fonts: Record<string, string> = {};
63
+ private renderBoundingBox?: boolean;
83
64
  private defaultFontFamily?: string;
84
65
  private defaultAssetsPath?: string;
85
66
 
67
+ /**
68
+ * Render data
69
+ */
70
+ private readonly debugPoints: Array<DebugPoint> = [];
71
+ private bundler?: Bundler;
72
+
86
73
  // disallow constructor initialization
87
74
  private constructor(options: TemplateOptions) {
88
75
  this.background = new Jimp({
@@ -127,7 +114,7 @@ export class Template<EntryType extends Record<string, string>> {
127
114
  const template = fn(this.shadowTemplate());
128
115
  const image = await template.render(entry);
129
116
  return [
130
- await placeImage({
117
+ await this.placeImage({
131
118
  image,
132
119
  box: {
133
120
  left: 0,
@@ -145,19 +132,28 @@ export class Template<EntryType extends Record<string, string>> {
145
132
  packingFn: PackingFn,
146
133
  options?: ContainerOptions<EntryType>,
147
134
  ): this =>
148
- this.layer(async (entry: EntryType, { debugMode }) => {
135
+ this.layer(async (entry: EntryType) => {
149
136
  const mergedOptions: RequiredDeep<ContainerOptions<EntryType>> =
150
- merge({}, DEFAULT_CONTAINER_OPTIONS, options);
137
+ merge(
138
+ {},
139
+ DEFAULT_CONTAINER_OPTIONS,
140
+ { renderBoundingBox: this.renderBoundingBox },
141
+ options,
142
+ );
151
143
  if (this.shouldSkipLayerForEntry(entry, mergedOptions)) {
152
144
  return [];
153
145
  }
154
146
 
155
147
  const elementRefs = await elementsFn(entry);
156
- const elements = await Promise.all(elementRefs.map(elementRef => this.elementFromElementRef(entry, elementRef)))
148
+ const elements = await Promise.all(
149
+ elementRefs.map(elementRef =>
150
+ this.elementFromElementRef(entry, elementRef),
151
+ ),
152
+ );
157
153
  const result = await packingFn(box, elements);
158
154
 
159
155
  // debug mode
160
- if (debugMode) {
156
+ if (mergedOptions.renderBoundingBox) {
161
157
  const debugImage = await placeBoundingBox(box);
162
158
  return [result, debugImage];
163
159
  }
@@ -188,12 +184,15 @@ export class Template<EntryType extends Record<string, string>> {
188
184
  box: BoundingBox,
189
185
  options: ImageLayerOptions<EntryType>,
190
186
  ): this =>
191
- this.layer(async (entry, { debugMode }) => {
187
+ this.layer(async entry => {
192
188
  const mergedOptions: RequiredDeep<ImageLayerOptions<EntryType>> =
193
189
  merge(
194
190
  {},
195
191
  DEFAULT_IMAGE_LAYER_OPTIONS,
196
- { assetsPath: this.defaultAssetsPath },
192
+ {
193
+ assetsPath: this.defaultAssetsPath,
194
+ renderBoundingBox: this.renderBoundingBox,
195
+ },
197
196
  options,
198
197
  );
199
198
  if (this.shouldSkipLayerForEntry(entry, mergedOptions)) {
@@ -203,16 +202,16 @@ export class Template<EntryType extends Record<string, string>> {
203
202
  const image = await this.imageFromImageRef(
204
203
  entry,
205
204
  ref,
206
- mergedOptions,
205
+ mergedOptions.assetsPath,
207
206
  );
208
- const result = await placeImage({
207
+ const result = await this.placeImage({
209
208
  image,
210
209
  box,
211
210
  options: mergedOptions,
212
211
  });
213
212
 
214
213
  // debug mode
215
- if (debugMode) {
214
+ if (mergedOptions.renderBoundingBox) {
216
215
  const debugImage = await placeBoundingBox(box);
217
216
  return [result, debugImage];
218
217
  }
@@ -230,7 +229,7 @@ export class Template<EntryType extends Record<string, string>> {
230
229
  box: BoundingBox,
231
230
  options?: TextLayerOptions<EntryType>,
232
231
  ): this =>
233
- this.layer(async (entry, { debugMode }) => {
232
+ this.layer(async entry => {
234
233
  const mergedOptions = merge(
235
234
  {},
236
235
  DEFAULT_TEXT_LAYER_OPTIONS,
@@ -238,6 +237,7 @@ export class Template<EntryType extends Record<string, string>> {
238
237
  font: {
239
238
  family: this.defaultFontFamily,
240
239
  },
240
+ renderBoundingBox: this.renderBoundingBox,
241
241
  } as FontOptions,
242
242
  options,
243
243
  );
@@ -246,14 +246,14 @@ export class Template<EntryType extends Record<string, string>> {
246
246
  }
247
247
 
248
248
  const text = this.textFromTextRef(entry, ref);
249
- const result = await placeText({
249
+ const result = await this.placeText({
250
250
  text,
251
251
  box,
252
252
  options: mergedOptions,
253
253
  });
254
254
 
255
255
  // debug mode
256
- if (debugMode) {
256
+ if (mergedOptions.renderBoundingBox) {
257
257
  const debugImage = await placeBoundingBox(box);
258
258
  return [result, debugImage];
259
259
  }
@@ -267,41 +267,77 @@ export class Template<EntryType extends Record<string, string>> {
267
267
  return this;
268
268
  }
269
269
 
270
- debug(): this {
271
- this.debugMode = true;
270
+ debug = (): this => {
271
+ this.debugPoints.push({
272
+ index: this.layers.length,
273
+ });
272
274
  return this;
273
- }
275
+ };
274
276
 
275
277
  async render(entry: EntryType): Promise<ImageType> {
276
278
  const results = await Promise.all(
277
279
  this.layers.map(layerFn =>
278
280
  layerFn(entry, {
279
- debugMode: this.debugMode,
281
+ renderBoundingBox: this.renderBoundingBox,
280
282
  }),
281
283
  ),
282
284
  );
283
- const document = h('html', [
284
- h('head', [
285
- h(
286
- 'style',
287
- Object.entries(this.fonts)
288
- .map(
289
- ([name, data]) =>
290
- `@font-face {
285
+ const buildDocument = (children: Array<HyperNode>) =>
286
+ h('html', [
287
+ h('head', [
288
+ h(
289
+ 'style',
290
+ // load fonts
291
+ Object.entries(this.fonts)
292
+ .map(
293
+ ([name, data]) =>
294
+ `@font-face {
291
295
  font-family: '${name}';
292
296
  src: url(data:font/ttf;base64,${data}) format('truetype');
293
297
  }`,
294
- )
295
- .join('\n'),
298
+ )
299
+ .join('\n'),
300
+ ),
301
+ ]),
302
+ // fix chromium headless padding issue
303
+ h(
304
+ 'body',
305
+ {
306
+ style: {
307
+ margin: 0,
308
+ },
309
+ },
310
+ [
311
+ h(
312
+ 'div',
313
+ {
314
+ style: {
315
+ marginLeft: '-8px',
316
+ },
317
+ },
318
+ children,
319
+ ),
320
+ ],
296
321
  ),
297
- ]),
298
- h(
299
- 'body',
300
- flatten(results),
301
- ),
302
- ]);
303
- const renderedLayers = await htmlToImage(document, this.backgroundSize);
304
- return this.background.clone().composite(renderedLayers);
322
+ ]);
323
+
324
+ // TODO: move it to a proper place
325
+ for (const debugPoint of this.debugPoints) {
326
+ const debugRender = await htmlToImage(
327
+ buildDocument(flatten(results.slice(0, debugPoint.index))),
328
+ this.backgroundSize,
329
+ );
330
+ const debugImage: ImageType = await this.background
331
+ .clone()
332
+ .composite(debugRender);
333
+ await debugImage.write('assets/debug-1.png');
334
+ }
335
+
336
+ const render = await htmlToImage(
337
+ buildDocument(flatten(results)),
338
+ this.backgroundSize,
339
+ );
340
+ return this.background.clone().composite(render);
305
341
  }
306
342
 
307
343
  async renderAll(
@@ -363,11 +399,31 @@ export class Template<EntryType extends Record<string, string>> {
363
399
  private imageFromImageRef = async (
364
400
  entry: EntryType,
365
401
  ref: ImageRef<EntryType>,
366
- options: RequiredDeep<ImageLayerSpecificOptions<EntryType>>,
402
+ assetsPath: string,
367
403
  ): Promise<ImageType> => {
368
404
  const pathSegments = [];
369
- if (options.assetsPath) {
370
- pathSegments.push(options.assetsPath);
405
+ if (assetsPath.length) {
406
+ pathSegments.push(assetsPath);
407
+ }
408
+
409
+ if ('key' in ref) {
410
+ const fileName = entry[ref.key];
411
+ return this.loadImage(path.join(...pathSegments, fileName));
412
+ } else if ('pathFn' in ref) {
413
+ const fileName = ref.pathFn(entry);
414
+ return this.loadImage(path.join(...pathSegments, fileName));
415
+ } else {
416
+ return this.imageFromStaticImageRef(ref, assetsPath);
417
+ }
418
+ };
419
+
420
+ private imageFromStaticImageRef = async (
421
+ ref: StaticImageRef,
422
+ assetsPath: string,
423
+ ): Promise<ImageType> => {
424
+ const pathSegments = [];
425
+ if (assetsPath.length) {
426
+ pathSegments.push(assetsPath);
371
427
  }
372
428
 
373
429
  if ('buffer' in ref) {
@@ -376,12 +432,6 @@ export class Template<EntryType extends Record<string, string>> {
376
432
  return this.loadImage(path.join(...pathSegments, ref.path));
377
433
  } else if ('absolutePath' in ref) {
378
434
  return this.loadImage(ref.absolutePath);
379
- } else if ('key' in ref) {
380
- const fileName = entry[ref.key];
381
- return this.loadImage(path.join(...pathSegments, fileName));
382
- } else if ('pathFn' in ref) {
383
- const fileName = ref.pathFn(entry);
384
- return this.loadImage(path.join(...pathSegments, fileName));
385
435
  } else {
386
436
  throw new Error('Unknown ImageRef variant');
387
437
  }
@@ -408,18 +458,22 @@ export class Template<EntryType extends Record<string, string>> {
408
458
  ): Promise<HyperNode> => {
409
459
  if ('text' in ref) {
410
460
  const options = merge({}, DEFAULT_TEXT_LAYER_OPTIONS, ref.options);
411
- return prepareText({
461
+ return this.prepareText({
412
462
  text: this.textFromTextRef(entry, ref.text),
413
463
  options,
414
464
  });
415
465
  } else if ('image' in ref) {
416
466
  const options = merge({}, DEFAULT_IMAGE_LAYER_OPTIONS, ref.options);
417
- return prepareImage({
418
- image: await this.imageFromImageRef(entry, ref.image, options),
467
+ return this.prepareImage({
468
+ image: await this.imageFromImageRef(
469
+ entry,
470
+ ref.image,
471
+ options.assetsPath,
472
+ ),
419
473
  options,
420
474
  });
421
475
  } else if ('node' in ref) {
422
- return ref.node
476
+ return ref.node;
423
477
  } else {
424
478
  throw new Error('Unknown TextRef variant');
425
479
  }
@@ -434,4 +488,193 @@ export class Template<EntryType extends Record<string, string>> {
434
488
  }
435
489
  return options.skip ?? false;
436
490
  };
491
+
492
+ private placeText = async ({
493
+ text,
494
+ box,
495
+ options,
496
+ }: {
497
+ text: string;
498
+ box: BoundingBox;
499
+ options: RequiredDeep<TextLayerOptions<EntryType>>;
500
+ }): Promise<HyperNode> => {
501
+ return h(
502
+ 'div',
503
+ {
504
+ style: {
505
+ display: 'flex',
506
+ overflow: 'visible',
507
+ position: 'absolute',
508
+
509
+ justifyContent: options.justifyContent,
510
+ alignItems: options.alignItems,
511
+
512
+ ...boundingBoxToPx(box),
513
+ },
514
+ },
515
+ [
516
+ await this.prepareText({
517
+ text,
518
+ options,
519
+ }),
520
+ ],
521
+ );
522
+ };
523
+
524
+ private prepareText = async ({
525
+ text,
526
+ options,
527
+ }: {
528
+ text: string;
529
+ options: RequiredDeep<TextLayerSpecificOptions<EntryType>>;
530
+ }): Promise<HyperNode> => {
531
+ let textChildren: Array<string | HyperNode> = [text];
532
+ const replacementBuilder = new ReplacementBuilder();
533
+ options.replacementFn(replacementBuilder);
534
+ const replacementMap = replacementBuilder.build();
535
+ for (const [word, imageRef] of Object.entries(replacementMap)) {
536
+ const regex = new RegExp(word, 'gi');
537
+ const textOptions: RequiredDeep<ImageLayerOptions<EntryType>> =
538
+ merge(
539
+ {},
540
+ DEFAULT_IMAGE_LAYER_OPTIONS,
541
+ { assetsPath: this.defaultAssetsPath },
542
+ options,
543
+ );
544
+ const image = await this.imageFromStaticImageRef(
545
+ imageRef,
546
+ textOptions.assetsPath,
547
+ );
548
+ const imageBase64 = await image.getBase64('image/png');
549
+
550
+ const tmpChildren: Array<string | HyperNode> = [];
551
+ for (const textSegment of textChildren) {
552
+ if (typeof textSegment !== 'string') {
553
+ continue;
554
+ }
555
+
556
+ const parts = (textSegment as string).split(regex);
557
+ for (let index = 0; index < parts.length; index++) {
558
+ if (index > 0) {
559
+ tmpChildren.push(
560
+ h(
561
+ 'img',
562
+ {
563
+ style: {
564
+ display: 'inline',
565
+ verticalAlign: 'middle',
566
+ height: toPx(options.font.size),
567
+ width: 'auto',
568
+ },
569
+ src: imageBase64,
570
+ },
571
+ [],
572
+ ),
573
+ );
574
+ }
575
+ tmpChildren.push(parts[index]);
576
+ }
577
+ }
578
+
579
+ textChildren = tmpChildren;
580
+ }
581
+
582
+ return h(
583
+ 'div',
584
+ {
585
+ style: {
586
+ overflow: 'visible',
587
+ overflowWrap: 'word-wrap',
588
+ whiteSpace: 'normal',
589
+
590
+ color: options.color,
591
+ fontFamily: options.font.family,
592
+ fontSize: options.font.size,
593
+ fontStyle: options.font.italic ? 'italic' : undefined,
594
+ fontWeight: options.font.bold ? 'bold' : undefined,
595
+
596
+ '-webkit-text-stroke': `${options.border.width}px ${options.border.color}`,
597
+ },
598
+ },
599
+ textChildren,
600
+ );
601
+ };
602
+
603
+ private placeImage = async ({
604
+ image,
605
+ box,
606
+ options,
607
+ }: {
608
+ image: ImageType;
609
+ box: BoundingBox;
610
+ options: RequiredDeep<ImageLayerOptions<EntryType>>;
611
+ }): Promise<HyperNode> => {
612
+ return h(
613
+ 'div',
614
+ {
615
+ style: {
616
+ display: 'flex',
617
+ position: 'absolute',
618
+ scale: 1,
619
+
620
+ justifyContent: options.justifyContent,
621
+ alignItems: options.alignItems,
622
+
623
+ ...boundingBoxToPx(box),
624
+ },
625
+ },
626
+ [await this.prepareImage({ image, options })],
627
+ );
628
+ };
629
+
630
+ private prepareImage = async ({
631
+ image,
632
+ options,
633
+ }: {
634
+ image: ImageType;
635
+ options: RequiredDeep<ImageLayerSpecificOptions<EntryType>>;
636
+ }): Promise<HyperNode> => {
637
+ const imageBase64 = await image.getBase64('image/png');
638
+ const objectFit = SCALE_MODE_TO_OBJECT_FIT[options.scale];
639
+
640
+ return h(
641
+ 'img',
642
+ {
643
+ style: {
644
+ objectFit,
645
+ flex: '1 1 auto',
646
+ minWidth: 0,
647
+ minHeight: 0,
648
+ maxWidth: '100%',
649
+ maxHeight: '100%',
650
+ },
651
+ src: imageBase64,
652
+ },
653
+ [],
654
+ );
655
+ };
437
656
  }
657
+
658
+ /**
659
+ * Represents a replacement map between sets of words and symbols
660
+ */
661
+ export class Replacement {
662
+ protected readonly replacementMap: ReplacementMap = {};
663
+
664
+ replace(words: Array<string>, symbol: StaticImageRef): this {
665
+ words.forEach(word => {
666
+ this.replacementMap[word] = symbol;
667
+ });
668
+ return this;
669
+ }
670
+ }
671
+
672
+ class ReplacementBuilder extends Replacement {
673
+ build(): ReplacementMap {
674
+ return this.replacementMap;
675
+ }
676
+ }
677
+
678
+ type DebugPoint = {
679
+ index: number;
680
+ };
@@ -1,8 +1,8 @@
1
1
  import { RequiredDeep } from 'type-fest';
2
2
  import { BoundingBox } from './boundingBox';
3
- import { ImageLayerSpecificOptions, ImageRef, ImageType } from './image';
3
+ import { ImageLayerSpecificOptions, ImageRef } from './image';
4
4
  import { ScaleMode } from './scale';
5
- import { LayerOptions } from './layer';
5
+ import { DEFAULT_LAYER_OPTIONS, LayerOptions } from './layer';
6
6
  import { HyperNode } from './hypernode';
7
7
  import { TextLayerSpecificOptions, TextRef } from './text';
8
8
 
@@ -25,11 +25,9 @@ export type ContainerOptions<EntryType extends Record<string, string>> =
25
25
  export const DEFAULT_CONTAINER_OPTIONS: RequiredDeep<
26
26
  ContainerOptions<Record<string, string>>
27
27
  > = {
28
+ ...DEFAULT_LAYER_OPTIONS,
28
29
  gap: 0,
29
- justifyContent: 'normal',
30
- alignItems: 'center',
31
30
  scale: 'none',
32
- skip: false,
33
31
  };
34
32
 
35
33
  export type DirectionContainerOptions<
@@ -1,4 +1,3 @@
1
- import { VNode } from "virtual-dom";
1
+ import { VNode } from 'virtual-dom';
2
2
 
3
3
  export type HyperNode = VNode;
4
-
@@ -1,6 +1,6 @@
1
1
  import { JimpInstance } from 'jimp';
2
2
  import { ScaleMode } from './scale';
3
- import { LayerOptions } from './layer';
3
+ import { DEFAULT_LAYER_OPTIONS, LayerOptions } from './layer';
4
4
  import { RequiredDeep } from 'type-fest';
5
5
 
6
6
  export type ImageType = JimpInstance;
@@ -10,12 +10,15 @@ export type ImageType = JimpInstance;
10
10
  * Dynamic images -> `key`, `pathFn`
11
11
  */
12
12
  export type ImageRef<EntryType extends Record<string, string>> =
13
- | { buffer: Buffer }
14
- | { path: string }
15
- | { absolutePath: string }
13
+ | StaticImageRef
16
14
  | { key: keyof EntryType }
17
15
  | { pathFn: (entry: EntryType) => string };
18
16
 
17
+ export type StaticImageRef =
18
+ | { buffer: Buffer }
19
+ | { path: string }
20
+ | { absolutePath: string };
21
+
19
22
  export type ImageLayerOptions<EntryType extends Record<string, string>> =
20
23
  LayerOptions<EntryType> & {
21
24
  /**
@@ -32,15 +35,17 @@ export type ImageLayerOptions<EntryType extends Record<string, string>> =
32
35
  // processorFn?: (entry: EntryType, image: ImageType) => Promise<ImageType>;
33
36
  };
34
37
 
35
- export const DEFAULT_IMAGE_LAYER_OPTIONS: RequiredDeep<ImageLayerOptions<Record<string, string>>> = {
36
- justifyContent: 'center',
37
- alignItems: 'center',
38
+ export const DEFAULT_IMAGE_LAYER_OPTIONS: RequiredDeep<
39
+ ImageLayerOptions<Record<string, string>>
40
+ > = {
41
+ ...DEFAULT_LAYER_OPTIONS,
38
42
  scale: 'none',
39
- skip: false,
40
- assetsPath: ''
43
+ assetsPath: '',
41
44
  };
42
45
 
43
- export type ImageLayerSpecificOptions<EntryType extends Record<string, string>> = Omit<ImageLayerOptions<EntryType>, keyof LayerOptions<EntryType>>;
46
+ export type ImageLayerSpecificOptions<
47
+ EntryType extends Record<string, string>,
48
+ > = Omit<ImageLayerOptions<EntryType>, keyof LayerOptions<EntryType>>;
44
49
 
45
50
  export type Alignment = 'start' | 'center' | 'end';
46
51
 
@@ -9,3 +9,5 @@ export * from './boundingBox';
9
9
  export * from './css';
10
10
  export * from './layer';
11
11
  export * from './hypernode';
12
+ export * from './template';
13
+ export * from './pdf';
@@ -1,4 +1,6 @@
1
- import { JustifyContent, AlignItems } from "./css";
1
+ import { RequiredDeep } from 'type-fest';
2
+ import { JustifyContent, AlignItems } from './css';
3
+ import { HyperNode } from './hypernode';
2
4
 
3
5
  export type LayerOptions<EntryType extends Record<string, string>> = {
4
6
  /**
@@ -15,4 +17,25 @@ export type LayerOptions<EntryType extends Record<string, string>> = {
15
17
  * Decides if a layer should be rendered or not for a certain entry
16
18
  */
17
19
  skip?: boolean | ((entry: EntryType) => boolean);
20
+
21
+ /**
22
+ * Control if should render bounding box for this layer
23
+ */
24
+ renderBoundingBox?: boolean;
18
25
  };
26
+
27
+ export const DEFAULT_LAYER_OPTIONS: RequiredDeep<
28
+ LayerOptions<Record<string, string>>
29
+ > = {
30
+ justifyContent: 'center',
31
+ alignItems: 'center',
32
+ skip: false,
33
+ renderBoundingBox: false,
34
+ };
35
+
36
+ export type LayerFnContext = {};
37
+
38
+ export type LayerFn<EntryType> = (
39
+ entry: EntryType,
40
+ context: LayerFnContext,
41
+ ) => Promise<Array<HyperNode>>;