@hellkite/pipkin 0.5.0 → 0.5.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 (92) hide show
  1. package/build/src/lib/bundler.d.ts +1 -2
  2. package/build/src/lib/bundler.js.map +1 -1
  3. package/build/src/lib/replacement.d.ts +1 -2
  4. package/build/src/lib/replacement.js.map +1 -1
  5. package/build/src/lib/template.d.ts +13 -14
  6. package/build/src/lib/template.js +102 -37
  7. package/build/src/lib/template.js.map +1 -1
  8. package/build/src/lib/types/2d.d.ts +4 -5
  9. package/build/src/lib/types/boundingBox.d.ts +19 -0
  10. package/build/src/lib/types/boundingBox.js +3 -0
  11. package/build/src/lib/types/boundingBox.js.map +1 -0
  12. package/build/src/lib/types/containers.d.ts +13 -27
  13. package/build/src/lib/types/containers.js +3 -2
  14. package/build/src/lib/types/containers.js.map +1 -1
  15. package/build/src/lib/types/css.d.ts +4 -1
  16. package/build/src/lib/types/css.js +6 -3
  17. package/build/src/lib/types/css.js.map +1 -1
  18. package/build/src/lib/types/image.d.ts +17 -38
  19. package/build/src/lib/types/image.js +8 -2
  20. package/build/src/lib/types/image.js.map +1 -1
  21. package/build/src/lib/types/index.d.ts +3 -2
  22. package/build/src/lib/types/index.js +3 -2
  23. package/build/src/lib/types/index.js.map +1 -1
  24. package/build/src/lib/types/layer.d.ts +15 -0
  25. package/build/src/lib/types/layer.js +3 -0
  26. package/build/src/lib/types/layer.js.map +1 -0
  27. package/build/src/lib/types/position.d.ts +21 -0
  28. package/build/src/lib/types/position.js +42 -0
  29. package/build/src/lib/types/position.js.map +1 -0
  30. package/build/src/lib/types/scale.d.ts +9 -1
  31. package/build/src/lib/types/scale.js +2 -0
  32. package/build/src/lib/types/scale.js.map +1 -1
  33. package/build/src/lib/types/size.d.ts +4 -0
  34. package/build/src/lib/types/size.js +3 -0
  35. package/build/src/lib/types/size.js.map +1 -0
  36. package/build/src/lib/types/text.d.ts +29 -33
  37. package/build/src/lib/types/text.js +13 -4
  38. package/build/src/lib/types/text.js.map +1 -1
  39. package/build/src/lib/utils/buildFontString.d.ts +2 -2
  40. package/build/src/lib/utils/buildFontString.js.map +1 -1
  41. package/build/src/lib/utils/canvasToImage.d.ts +1 -1
  42. package/build/src/lib/utils/container.d.ts +4 -4
  43. package/build/src/lib/utils/container.js +52 -46
  44. package/build/src/lib/utils/container.js.map +1 -1
  45. package/build/src/lib/utils/drawBoundingBox.d.ts +1 -2
  46. package/build/src/lib/utils/drawBoundingBox.js +7 -47
  47. package/build/src/lib/utils/drawBoundingBox.js.map +1 -1
  48. package/build/src/lib/utils/htmlToImage.d.ts +3 -0
  49. package/build/src/lib/utils/htmlToImage.js +36 -0
  50. package/build/src/lib/utils/htmlToImage.js.map +1 -0
  51. package/build/src/lib/utils/index.d.ts +2 -1
  52. package/build/src/lib/utils/index.js +2 -1
  53. package/build/src/lib/utils/index.js.map +1 -1
  54. package/build/src/lib/utils/placeImage.d.ts +10 -5
  55. package/build/src/lib/utils/placeImage.js +25 -36
  56. package/build/src/lib/utils/placeImage.js.map +1 -1
  57. package/build/src/lib/utils/renderText.d.ts +3 -2
  58. package/build/src/lib/utils/renderText.js +12 -39
  59. package/build/src/lib/utils/renderText.js.map +1 -1
  60. package/build/src/lib/utils/toPx.d.ts +3 -0
  61. package/build/src/lib/utils/toPx.js +12 -0
  62. package/build/src/lib/utils/toPx.js.map +1 -0
  63. package/build/src/lib/utils/vNodeToImage.d.ts +4 -0
  64. package/build/src/lib/utils/vNodeToImage.js +36 -0
  65. package/build/src/lib/utils/vNodeToImage.js.map +1 -0
  66. package/build/src/test.js +55 -69
  67. package/build/src/test.js.map +1 -1
  68. package/package.json +6 -3
  69. package/roadmap.md +0 -1
  70. package/src/lib/bundler.ts +1 -2
  71. package/src/lib/replacement.ts +1 -2
  72. package/src/lib/template.ts +190 -68
  73. package/src/lib/types/boundingBox.ts +28 -0
  74. package/src/lib/types/containers.ts +55 -60
  75. package/src/lib/types/css.ts +32 -1
  76. package/src/lib/types/image.ts +32 -57
  77. package/src/lib/types/index.ts +3 -2
  78. package/src/lib/types/layer.ts +18 -0
  79. package/src/lib/types/scale.ts +11 -0
  80. package/src/lib/types/size.ts +4 -0
  81. package/src/lib/types/text.ts +47 -56
  82. package/src/lib/utils/buildFontString.ts +2 -2
  83. package/src/lib/utils/container.ts +118 -57
  84. package/src/lib/utils/drawBoundingBox.ts +19 -16
  85. package/src/lib/utils/htmlToImage.ts +24 -0
  86. package/src/lib/utils/index.ts +2 -1
  87. package/src/lib/utils/placeImage.ts +55 -58
  88. package/src/lib/utils/renderText.ts +19 -43
  89. package/src/lib/utils/toPx.ts +11 -0
  90. package/src/lib/types/2d.ts +0 -11
  91. package/src/lib/types/omitArgument.ts +0 -17
  92. package/src/lib/utils/canvasToImage.ts +0 -19
@@ -1,67 +1,58 @@
1
- import { BoundingBox } from './2d';
1
+ import type { RequiredDeep } from 'type-fest';
2
+ import { LayerOptions } from './layer';
2
3
  import { ReplacementMap } from './replacement';
3
4
 
4
- export type TextLayerProps<EntryType> = {
5
- key: keyof EntryType;
6
- position: TextPosition;
7
- options?: TextLayerOptions;
8
- };
9
-
10
- export type TextLayerOptions = {
11
- font?: {
12
- /**
13
- * Size of font is represented in pixels
14
- */
15
- size?: number;
16
- /**
17
- * Either use one supported by canvas
18
- * or load a custom one before rendering
19
- */
20
- family?: string;
21
- /**
22
- * Warning: Not supported by all fonts
23
- */
24
- bold?: boolean;
25
- /**
26
- * Warning: Not supported by all fonts
27
- */
28
- italic?: boolean;
29
- };
30
-
31
- color?: string;
32
-
33
- // TODO: processor fn
34
- replacement?: ReplacementMap;
5
+ export type TextRef<EntryType> =
6
+ | { key: string }
7
+ | { text: string }
8
+ | { textFn: (entry: EntryType) => string };
35
9
 
10
+ export type FontOptions = {
36
11
  /**
37
- * default `center`
12
+ * Size of font is represented in pixels
38
13
  */
39
- xAlign?:
40
- | 'left'
41
- | 'center'
42
- | 'right'
43
- | 'justify'
44
- | 'justify-left'
45
- | 'justify-center'
46
- | 'justify-right';
47
-
14
+ size?: number;
15
+ /**
16
+ * Either use one supported by canvas
17
+ * or load a custom one before rendering
18
+ */
19
+ family?: string;
48
20
  /**
49
- * default `center`
21
+ * Warning: Not supported by all fonts
50
22
  */
51
- yAlign?:
52
- | 'left'
53
- | 'center'
54
- | 'right'
55
- | 'justify'
56
- | 'justify-left'
57
- | 'justify-center'
58
- | 'justify-right';
23
+ bold?: boolean;
24
+ /**
25
+ * Warning: Not supported by all fonts
26
+ */
27
+ italic?: boolean;
59
28
  };
60
29
 
61
- export type TextPosition = BoundingBox;
30
+ export type TextLayerOptions<EntryType extends Record<string, string>> =
31
+ LayerOptions<EntryType> & {
32
+ font?: FontOptions;
33
+
34
+ color?: string;
35
+
36
+ // TODO: processor fn
37
+ replacement?: ReplacementMap;
38
+ };
39
+
40
+ export const DEFAULT_FONT: Required<
41
+ FontOptions
42
+ > = {
43
+ size: 28,
44
+ family: 'Arial',
45
+ bold: false,
46
+ italic: false,
47
+ };
62
48
 
63
- // TODO: all required
64
- export const DEFAULT_TEXT_LAYER_OPTIONS: TextLayerOptions = {
65
- xAlign: 'center',
66
- yAlign: 'center',
49
+ export const DEFAULT_TEXT_LAYER_OPTIONS: RequiredDeep<
50
+ TextLayerOptions<Record<string, string>>
51
+ > = {
52
+ justifyContent: 'center',
53
+ alignItems: 'center',
54
+ font: DEFAULT_FONT,
55
+ color: 'black',
56
+ replacement: {},
57
+ skip: false,
67
58
  };
@@ -1,7 +1,7 @@
1
- import { TextLayerOptions } from "../types/text";
1
+ import { FontOptions } from "../types";
2
2
 
3
3
  export const buildFontString = (
4
- font: TextLayerOptions['font'],
4
+ font: FontOptions,
5
5
  defaultFontFamily?: string,
6
6
  ): string => {
7
7
  const fragments: string[] = [];
@@ -1,52 +1,141 @@
1
- import { BoundingBox, Point } from '../types/2d';
1
+ import { h } from 'virtual-dom';
2
2
  import {
3
- DEFAULT_CONTAINER_OPTIONS,
4
3
  DEFAULT_DIRECTION_CONTAINER_OPTIONS,
5
4
  DirectionContainerOptions,
5
+ ImageType,
6
6
  PackingFn,
7
- } from '../types/containers';
8
- import { ImageType, ScaleMode } from '../types/image';
9
- import { h, create as createElement, VNode } from 'virtual-dom';
10
- import nodeHtmlToImage from 'node-html-to-image';
11
- import { Jimp } from 'jimp';
12
- import { toPx } from '../types';
7
+ BoundingBox,
8
+ SCALE_MODE_TO_OBJECT_FIT,
9
+ Size,
10
+ GridContainerOptions,
11
+ DEFAULT_GRID_CONTAINER_OPTIONS,
12
+ } from '../types';
13
+ import { boundingBoxToPx, toPx } from './toPx';
14
+ import merge from 'lodash.merge';
15
+ import { htmlToImage } from './htmlToImage';
16
+ import chunk from 'lodash.chunk';
13
17
 
14
18
  export const vboxPackingFn =
15
- (position: BoundingBox, options?: DirectionContainerOptions): PackingFn =>
16
- (background: ImageType, images: Array<ImageType>) =>
19
+ <EntryType extends Record<string, string>>(
20
+ options?: DirectionContainerOptions<EntryType>,
21
+ ): PackingFn =>
22
+ (box: BoundingBox, background: ImageType, images: Array<ImageType>) =>
17
23
  directionalPackingFn({
18
24
  isVertical: true,
19
- background,
25
+ backgroundSize: background,
20
26
  images,
21
- position,
27
+ box,
22
28
  options,
23
29
  });
24
30
 
25
31
  export const hboxPackingFn =
26
- (position: BoundingBox, options?: DirectionContainerOptions): PackingFn =>
27
- (background: ImageType, images: Array<ImageType>) =>
32
+ <EntryType extends Record<string, string>>(
33
+ options?: DirectionContainerOptions<EntryType>,
34
+ ): PackingFn =>
35
+ (box: BoundingBox, background: ImageType, images: Array<ImageType>) =>
28
36
  directionalPackingFn({
29
37
  isVertical: false,
30
- background,
38
+ backgroundSize: background,
31
39
  images,
32
- position,
40
+ box,
33
41
  options,
34
42
  });
35
43
 
36
- const directionalPackingFn = async ({
44
+ export const gridPackingFn =
45
+ <EntryType extends Record<string, string>>(
46
+ options?: GridContainerOptions<EntryType>,
47
+ ): PackingFn =>
48
+ async (
49
+ box: BoundingBox,
50
+ background: ImageType,
51
+ images: Array<ImageType>,
52
+ ) => {
53
+ const mergedOptions = merge(
54
+ {},
55
+ DEFAULT_GRID_CONTAINER_OPTIONS,
56
+ options,
57
+ );
58
+ const objectFit = SCALE_MODE_TO_OBJECT_FIT[mergedOptions.scale];
59
+
60
+ const children = await Promise.all(
61
+ images.map(async image => {
62
+ const imageBase64 = await image.getBase64('image/png');
63
+ return h(
64
+ 'img',
65
+ {
66
+ style: {
67
+ objectFit,
68
+ flex: '1 1 auto',
69
+ minWidth: 0,
70
+ minHeight: 0,
71
+ maxWidth: '100%',
72
+ maxHeight: '100%',
73
+ },
74
+ src: imageBase64,
75
+ },
76
+ [],
77
+ );
78
+ }),
79
+ );
80
+
81
+ const items = [];
82
+ for (const subset of chunk(children)) {
83
+ items.push(
84
+ h(
85
+ 'div',
86
+ {
87
+ style: {
88
+ display: 'flex',
89
+ flexDirection: 'row',
90
+ overflow: 'hidden',
91
+
92
+ gap: toPx(mergedOptions.gap),
93
+ justifyContent: mergedOptions.justifyContent,
94
+ alignItems: mergedOptions.alignItems,
95
+ },
96
+ },
97
+ subset,
98
+ ),
99
+ );
100
+ }
101
+ const document = h(
102
+ 'div',
103
+ {
104
+ style: {
105
+ display: 'grid',
106
+ gridTemplateColumns: `repeat(${mergedOptions.size}, 1fr)`,
107
+ position: 'absolute',
108
+
109
+ gap: toPx(mergedOptions.gap),
110
+ ...boundingBoxToPx(box),
111
+ },
112
+ },
113
+ items,
114
+ );
115
+
116
+ return htmlToImage(document, background);
117
+ };
118
+
119
+ const directionalPackingFn = async <EntryType extends Record<string, string>>({
37
120
  isVertical,
38
- background,
121
+ backgroundSize: background,
39
122
  images,
40
- position,
123
+ box,
41
124
  options,
42
125
  }: {
43
126
  isVertical: boolean;
44
- background: ImageType;
127
+ backgroundSize: Size;
45
128
  images: Array<ImageType>;
46
- position: BoundingBox;
47
- options?: DirectionContainerOptions;
129
+ box: BoundingBox;
130
+ options?: DirectionContainerOptions<EntryType>;
48
131
  }): Promise<ImageType> => {
49
- const objectFit = SCALE_MODE_TO_OBJECT_FIT[options?.scale ?? 'none'];
132
+ const mergedOptions = merge(
133
+ {},
134
+ DEFAULT_DIRECTION_CONTAINER_OPTIONS,
135
+ options,
136
+ );
137
+ const objectFit = SCALE_MODE_TO_OBJECT_FIT[mergedOptions.scale];
138
+
50
139
  const children = await Promise.all(
51
140
  images.map(async image => {
52
141
  const imageBase64 = await image.getBase64('image/png');
@@ -75,44 +164,16 @@ const directionalPackingFn = async ({
75
164
  position: 'absolute',
76
165
  scale: 1,
77
166
 
78
- flexDirection: `${isVertical ? 'column' : 'row'}${options?.reversed ? '-reversed' : ''}`,
79
- gap: toPx(options?.gap ?? 0),
80
- // TODO: merge options and defaults
81
- justifyContent:
82
- options?.justifyContent ??
83
- DEFAULT_DIRECTION_CONTAINER_OPTIONS.justifyContent,
84
- alignItems:
85
- options?.alignItems ??
86
- DEFAULT_DIRECTION_CONTAINER_OPTIONS.alignItems,
167
+ flexDirection: `${isVertical ? 'column' : 'row'}${mergedOptions.reversed ? '-reversed' : ''}`,
168
+ gap: toPx(mergedOptions.gap),
169
+ justifyContent: mergedOptions.justifyContent,
170
+ alignItems: mergedOptions.alignItems,
87
171
 
88
- top: toPx(position.y),
89
- left: toPx(position.x),
90
- height: toPx(position.height),
91
- width: toPx(position.width),
172
+ ...boundingBoxToPx(box),
92
173
  },
93
174
  },
94
175
  children,
95
176
  );
96
177
 
97
- const rootNode = createElement(document);
98
- // TODO: extract this in a dif function
99
- const image = await nodeHtmlToImage({
100
- html: rootNode.toString(),
101
- transparent: true,
102
- type: 'png',
103
- puppeteerArgs: {
104
- defaultViewport: {
105
- width: background.width,
106
- height: background.height,
107
- },
108
- },
109
- });
110
- return Jimp.read(image as Buffer) as Promise<ImageType>;
111
- };
112
-
113
-
114
- const SCALE_MODE_TO_OBJECT_FIT: Record<ScaleMode, string> = {
115
- 'keep-ratio': 'contain',
116
- stretch: 'fill',
117
- none: 'none',
178
+ return htmlToImage(document, background);
118
179
  };
@@ -1,22 +1,25 @@
1
- import { canvasToImage } from './canvasToImage';
2
- import { BoundingBox, Size } from '../types/2d';
3
- import * as fabric from 'fabric/node';
4
- import { ImageType } from '../types/image';
1
+ import { h } from 'virtual-dom';
2
+ import { htmlToImage } from './htmlToImage';
3
+ import { boundingBoxToPx } from './toPx';
4
+ import { ImageType, Size, BoundingBox } from '../types';
5
5
 
6
6
  export const drawBoundingBox = async (
7
7
  box: BoundingBox,
8
8
  imageSize: Size,
9
9
  ): Promise<ImageType> => {
10
- const fabricCanvas = new fabric.Canvas(undefined, imageSize);
11
- const boundingBox = new fabric.Rect({
12
- left: box.x,
13
- top: box.y,
14
- width: box.width,
15
- height: box.height,
16
- stroke: 'red',
17
- strokeWidth: 2,
18
- fill: 'transparent',
19
- });
20
- fabricCanvas.add(boundingBox);
21
- return canvasToImage(fabricCanvas);
10
+ const document = h(
11
+ 'div',
12
+ {
13
+ style: {
14
+ position: 'absolute',
15
+ border: '2px solid red',
16
+ background: 'transparent',
17
+ boxSizing: 'border-box',
18
+ ...boundingBoxToPx(box),
19
+ },
20
+ },
21
+ [],
22
+ );
23
+
24
+ return htmlToImage(document, imageSize);
22
25
  };
@@ -0,0 +1,24 @@
1
+ import { create as createElement, VNode } from 'virtual-dom';
2
+ import nodeHtmlToImage from 'node-html-to-image';
3
+ import { Jimp } from 'jimp';
4
+ import { ImageType, Size } from '../types';
5
+
6
+ export const htmlToImage = async (
7
+ document: VNode,
8
+ backgroundSize: Size,
9
+ ): Promise<ImageType> => {
10
+ const html = createElement(document).toString();
11
+ const image = await nodeHtmlToImage({
12
+ html,
13
+ transparent: true,
14
+ type: 'png',
15
+ puppeteerArgs: {
16
+ defaultViewport: {
17
+ width: backgroundSize.width,
18
+ height: backgroundSize.height,
19
+ deviceScaleFactor: 1,
20
+ },
21
+ },
22
+ });
23
+ return Jimp.read(image as Buffer) as Promise<ImageType>;
24
+ };
@@ -1,6 +1,7 @@
1
1
  export * from './buildFontString';
2
- export * from './canvasToImage';
3
2
  export * from './container';
4
3
  export * from './drawBoundingBox';
5
4
  export * from './placeImage';
6
5
  export * from './renderText';
6
+ export * from './htmlToImage';
7
+ export * from './toPx';
@@ -1,65 +1,62 @@
1
- import { Jimp } from 'jimp';
2
- import path from 'path';
1
+ import { h } from 'virtual-dom';
2
+ import { boundingBoxToPx } from './toPx';
3
3
  import {
4
- Alignment,
5
- ImageType,
6
- ImagePosition,
7
4
  ImageLayerOptions,
8
- DEFAULT_IMAGE_ALIGNMENT,
9
- DEFAULT_SCALE_MODE,
10
- } from '../types/image';
11
-
12
- function computeOffsetFromAlignment(
13
- alignment: Alignment,
14
- size: number,
15
- boxSize: number,
16
- ): number {
17
- if (alignment === 'start') {
18
- return 0;
19
- } else if (alignment === 'center') {
20
- return Math.floor((boxSize - size) / 2);
21
- } else {
22
- return -(boxSize - size);
23
- }
24
- }
5
+ ImageType,
6
+ BoundingBox,
7
+ SCALE_MODE_TO_OBJECT_FIT,
8
+ } from '../types';
9
+ import { htmlToImage } from './htmlToImage';
10
+ import { RequiredDeep } from 'type-fest';
25
11
 
26
- type PlaceImageProps = {
27
- background: ImageType;
12
+ type PlaceImageProps<EntryType extends Record<string, string>> = {
28
13
  image: ImageType;
29
- position: ImagePosition;
14
+ box: BoundingBox;
15
+ backgroundSize: { width: number; height: number };
16
+ options: RequiredDeep<ImageLayerOptions<EntryType>>;
30
17
  };
31
18
 
32
- export async function placeImage(
33
- // TODO: pass background size only
34
- { background, image, position }: PlaceImageProps,
35
- ): Promise<ImageType> {
36
- // handle alignment inside the bounding box
37
- const xAlignment =
38
- position.xAlignment ?? position.alignment ?? DEFAULT_IMAGE_ALIGNMENT;
39
- const yAlignment =
40
- position.yAlignment ?? position.alignment ?? DEFAULT_IMAGE_ALIGNMENT;
41
-
42
- const scale = position.scale ?? DEFAULT_SCALE_MODE;
43
-
44
- if (scale === 'keep-ratio') {
45
- // TODO: handle case when box has a size equal to 0
46
- const ratio = Math.min(
47
- position.width / image.width,
48
- position.height / image.height,
49
- );
50
- image.scale(ratio);
51
- }
52
-
53
- if (scale === 'stretch') {
54
- image.scaleToFit({ w: position.width, h: position.height });
55
- }
56
-
57
- const xOffset =
58
- position.x +
59
- computeOffsetFromAlignment(xAlignment, image.width, position.width);
60
- const yOffset =
61
- position.y +
62
- computeOffsetFromAlignment(yAlignment, image.height, position.height);
63
-
64
- return background.composite(image, xOffset, yOffset);
19
+ export const placeImage = async <EntryType extends Record<string, string>>({
20
+ image,
21
+ box,
22
+ backgroundSize,
23
+ options,
24
+ }: PlaceImageProps<EntryType>): Promise<ImageType> => {
25
+ const imageBase64 = await image.getBase64('image/png');
26
+ const objectFit = SCALE_MODE_TO_OBJECT_FIT[options.scale];
27
+
28
+ const document = h(
29
+ 'div',
30
+ {
31
+ style: {
32
+ display: 'flex',
33
+ position: 'absolute',
34
+ scale: 1,
35
+
36
+ justifyContent: options.justifyContent,
37
+ alignItems: options.alignItems,
38
+
39
+ ...boundingBoxToPx(box),
40
+ },
41
+ },
42
+ [
43
+ h(
44
+ 'img',
45
+ {
46
+ style: {
47
+ objectFit,
48
+ flex: '1 1 auto',
49
+ minWidth: 0,
50
+ minHeight: 0,
51
+ maxWidth: '100%',
52
+ maxHeight: '100%',
53
+ },
54
+ src: imageBase64,
55
+ },
56
+ [],
57
+ ),
58
+ ],
59
+ );
60
+
61
+ return htmlToImage(document, backgroundSize);
65
62
  }
@@ -1,24 +1,17 @@
1
- import { h, create as createElement, VNode } from 'virtual-dom';
2
- import {
3
- DEFAULT_TEXT_LAYER_OPTIONS,
4
- ImageType,
5
- Size,
6
- TextLayerOptions,
7
- TextPosition,
8
- toPx,
9
- } from '../types';
10
- import nodeHtmlToImage from 'node-html-to-image';
11
- import { Jimp } from 'jimp';
1
+ import { h, VNode } from 'virtual-dom';
2
+ import { ImageType, BoundingBox, Size, TextLayerOptions } from '../types';
3
+ import { boundingBoxToPx, toPx } from './toPx';
4
+ import { htmlToImage } from './htmlToImage';
5
+ import { RequiredDeep } from 'type-fest';
12
6
 
13
-
14
- export const renderText = async (
7
+ export const renderText = async <EntryType extends Record<string, string>>(
15
8
  text: string,
16
- position: TextPosition,
9
+ box: BoundingBox,
17
10
  backgroundSize: Size,
18
- options?: TextLayerOptions,
11
+ options: RequiredDeep<TextLayerOptions<EntryType>>,
19
12
  ): Promise<ImageType> => {
20
13
  let textChildren: Array<string | VNode> = [text];
21
- for (const [word, image] of Object.entries(options?.replacement ?? {})) {
14
+ for (const [word, image] of Object.entries(options.replacement)) {
22
15
  const regex = new RegExp(word, 'gi');
23
16
  const imageBase64 = await image.getBase64('image/png');
24
17
 
@@ -38,7 +31,7 @@ export const renderText = async (
38
31
  style: {
39
32
  display: 'inline',
40
33
  verticalAlign: 'middle',
41
- height: toPx(options?.font?.size ?? 28), // TODO: use constant
34
+ height: toPx(options.font.size),
42
35
  width: 'auto',
43
36
  },
44
37
  src: imageBase64,
@@ -62,15 +55,10 @@ export const renderText = async (
62
55
  overflow: 'visible',
63
56
  position: 'absolute',
64
57
 
65
- top: toPx(position.y),
66
- left: toPx(position.x),
67
- height: toPx(position.height),
68
- width: toPx(position.width),
58
+ justifyContent: options.justifyContent,
59
+ alignItems: options.alignItems,
69
60
 
70
- justifyContent:
71
- options?.xAlign ?? DEFAULT_TEXT_LAYER_OPTIONS.xAlign,
72
- alignItems:
73
- options?.yAlign ?? DEFAULT_TEXT_LAYER_OPTIONS.yAlign,
61
+ ...boundingBoxToPx(box),
74
62
  },
75
63
  },
76
64
  [
@@ -82,11 +70,11 @@ export const renderText = async (
82
70
  overflowWrap: 'word-wrap',
83
71
  whiteSpace: 'normal',
84
72
 
85
- color: options?.color,
86
- fontFamily: options?.font?.family,
87
- fontSize: options?.font?.size,
88
- fontStyle: options?.font?.italic ? 'italic' : undefined,
89
- fontWeight: options?.font?.bold ? 'bold' : undefined,
73
+ color: options.color,
74
+ fontFamily: options.font.family,
75
+ fontSize: options.font.size,
76
+ fontStyle: options.font.italic ? 'italic' : undefined,
77
+ fontWeight: options.font.bold ? 'bold' : undefined,
90
78
  },
91
79
  },
92
80
  textChildren,
@@ -94,17 +82,5 @@ export const renderText = async (
94
82
  ],
95
83
  );
96
84
 
97
- const rootNode = createElement(document);
98
- const image = await nodeHtmlToImage({
99
- html: rootNode.toString(),
100
- transparent: true,
101
- type: 'png',
102
- puppeteerArgs: {
103
- defaultViewport: {
104
- width: backgroundSize.width,
105
- height: backgroundSize.height,
106
- },
107
- },
108
- });
109
- return Jimp.read(image as Buffer) as Promise<ImageType>;
85
+ return htmlToImage(document, backgroundSize);
110
86
  };
@@ -0,0 +1,11 @@
1
+ import { BoundingBox } from '../types';
2
+
3
+ export const toPx = (value: number): string => `${value}px`;
4
+
5
+ export const boundingBoxToPx = (
6
+ boundingBox: BoundingBox,
7
+ ): Record<string, string> => {
8
+ return Object.entries(boundingBox)
9
+ .map(([key, value]) => [key, toPx(value)])
10
+ .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
11
+ };
@@ -1,11 +0,0 @@
1
- export type Point = {
2
- x: number;
3
- y: number;
4
- };
5
-
6
- export type Size = {
7
- width: number;
8
- height: number;
9
- };
10
-
11
- export type BoundingBox = Point & Size;