@hellkite/pipkin 0.6.2 → 0.7.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.
Files changed (49) hide show
  1. package/build/src/lib/template.d.ts +9 -10
  2. package/build/src/lib/template.js +75 -40
  3. package/build/src/lib/template.js.map +1 -1
  4. package/build/src/lib/types/containers.d.ts +14 -2
  5. package/build/src/lib/types/containers.js.map +1 -1
  6. package/build/src/lib/types/hypernode.d.ts +2 -0
  7. package/build/src/lib/types/hypernode.js +3 -0
  8. package/build/src/lib/types/hypernode.js.map +1 -0
  9. package/build/src/lib/types/image.d.ts +5 -0
  10. package/build/src/lib/types/image.js.map +1 -1
  11. package/build/src/lib/types/index.d.ts +1 -0
  12. package/build/src/lib/types/index.js +1 -0
  13. package/build/src/lib/types/index.js.map +1 -1
  14. package/build/src/lib/types/text.d.ts +7 -2
  15. package/build/src/lib/types/text.js +1 -1
  16. package/build/src/lib/types/text.js.map +1 -1
  17. package/build/src/lib/utils/container.js +10 -45
  18. package/build/src/lib/utils/container.js.map +1 -1
  19. package/build/src/lib/utils/htmlToImage.d.ts +2 -3
  20. package/build/src/lib/utils/htmlToImage.js.map +1 -1
  21. package/build/src/lib/utils/index.d.ts +2 -2
  22. package/build/src/lib/utils/index.js +2 -2
  23. package/build/src/lib/utils/index.js.map +1 -1
  24. package/build/src/lib/utils/placeBoundingBox.d.ts +2 -0
  25. package/build/src/lib/utils/placeBoundingBox.js +21 -0
  26. package/build/src/lib/utils/placeBoundingBox.js.map +1 -0
  27. package/build/src/lib/utils/placeImage.d.ts +7 -6
  28. package/build/src/lib/utils/placeImage.js +20 -20
  29. package/build/src/lib/utils/placeImage.js.map +1 -1
  30. package/build/src/lib/utils/placeText.d.ts +14 -0
  31. package/build/src/lib/utils/placeText.js +69 -0
  32. package/build/src/lib/utils/placeText.js.map +1 -0
  33. package/build/src/test.js +28 -10
  34. package/build/src/test.js.map +1 -1
  35. package/package.json +3 -1
  36. package/roadmap.md +2 -1
  37. package/src/lib/template.ts +100 -70
  38. package/src/lib/types/containers.ts +22 -4
  39. package/src/lib/types/hypernode.ts +4 -0
  40. package/src/lib/types/image.ts +6 -0
  41. package/src/lib/types/index.ts +1 -0
  42. package/src/lib/types/text.ts +10 -5
  43. package/src/lib/utils/container.ts +13 -70
  44. package/src/lib/utils/htmlToImage.ts +3 -3
  45. package/src/lib/utils/index.ts +2 -2
  46. package/src/lib/utils/{drawBoundingBox.ts → placeBoundingBox.ts} +3 -9
  47. package/src/lib/utils/placeImage.ts +34 -27
  48. package/src/lib/utils/placeText.ts +110 -0
  49. package/src/lib/utils/renderText.ts +0 -107
@@ -5,6 +5,10 @@ import { RequiredDeep } from 'type-fest';
5
5
 
6
6
  export type ImageType = JimpInstance;
7
7
 
8
+ /**
9
+ * Static images -> `buffer`, `path`, `absolutePath`
10
+ * Dynamic images -> `key`, `pathFn`
11
+ */
8
12
  export type ImageRef<EntryType extends Record<string, string>> =
9
13
  | { buffer: Buffer }
10
14
  | { path: string }
@@ -36,6 +40,8 @@ export const DEFAULT_IMAGE_LAYER_OPTIONS: RequiredDeep<ImageLayerOptions<Record<
36
40
  assetsPath: ''
37
41
  };
38
42
 
43
+ export type ImageLayerSpecificOptions<EntryType extends Record<string, string>> = Omit<ImageLayerOptions<EntryType>, keyof LayerOptions<EntryType>>;
44
+
39
45
  export type Alignment = 'start' | 'center' | 'end';
40
46
 
41
47
  export const DEFAULT_IMAGE_ALIGNMENT: Alignment = 'center';
@@ -8,3 +8,4 @@ export * from './text';
8
8
  export * from './boundingBox';
9
9
  export * from './css';
10
10
  export * from './layer';
11
+ export * from './hypernode';
@@ -2,9 +2,13 @@ import type { RequiredDeep } from 'type-fest';
2
2
  import { LayerOptions } from './layer';
3
3
  import { ReplacementMap } from './replacement';
4
4
 
5
+ /**
6
+ * Static text -> `text`
7
+ * Dynamic text -> `key`, `textFn`
8
+ */
5
9
  export type TextRef<EntryType> =
6
- | { key: string }
7
10
  | { text: string }
11
+ | { key: string }
8
12
  | { textFn: (entry: EntryType) => string };
9
13
 
10
14
  export type FontOptions = {
@@ -42,15 +46,16 @@ export type TextLayerOptions<EntryType extends Record<string, string>> =
42
46
  replacement?: ReplacementMap;
43
47
  };
44
48
 
45
- export const DEFAULT_FONT: Required<
46
- FontOptions
47
- > = {
49
+ export const DEFAULT_FONT: Required<FontOptions> = {
48
50
  size: 28,
49
51
  family: 'Arial',
50
52
  bold: false,
51
53
  italic: false,
52
54
  };
53
55
 
56
+ export type TextLayerSpecificOptions<EntryType extends Record<string, string>> =
57
+ Omit<TextLayerOptions<EntryType>, keyof LayerOptions<EntryType>>;
58
+
54
59
  export const DEFAULT_TEXT_LAYER_OPTIONS: RequiredDeep<
55
60
  TextLayerOptions<Record<string, string>>
56
61
  > = {
@@ -63,5 +68,5 @@ export const DEFAULT_TEXT_LAYER_OPTIONS: RequiredDeep<
63
68
  border: {
64
69
  width: 0,
65
70
  color: 'black',
66
- }
71
+ },
67
72
  };
@@ -2,28 +2,25 @@ import { h } from 'virtual-dom';
2
2
  import {
3
3
  DEFAULT_DIRECTION_CONTAINER_OPTIONS,
4
4
  DirectionContainerOptions,
5
- ImageType,
6
5
  PackingFn,
7
6
  BoundingBox,
8
7
  SCALE_MODE_TO_OBJECT_FIT,
9
- Size,
10
8
  GridContainerOptions,
11
9
  DEFAULT_GRID_CONTAINER_OPTIONS,
10
+ HyperNode,
12
11
  } from '../types';
13
12
  import { boundingBoxToPx, toPx } from './toPx';
14
13
  import merge from 'lodash.merge';
15
- import { htmlToImage } from './htmlToImage';
16
14
  import chunk from 'lodash.chunk';
17
15
 
18
16
  export const vboxPackingFn =
19
17
  <EntryType extends Record<string, string>>(
20
18
  options?: DirectionContainerOptions<EntryType>,
21
19
  ): PackingFn =>
22
- (box: BoundingBox, background: ImageType, images: Array<ImageType>) =>
20
+ (box: BoundingBox, elements: Array<HyperNode>) =>
23
21
  directionalPackingFn({
24
22
  isVertical: true,
25
- backgroundSize: background,
26
- images,
23
+ elements,
27
24
  box,
28
25
  options,
29
26
  });
@@ -32,11 +29,10 @@ export const hboxPackingFn =
32
29
  <EntryType extends Record<string, string>>(
33
30
  options?: DirectionContainerOptions<EntryType>,
34
31
  ): PackingFn =>
35
- (box: BoundingBox, background: ImageType, images: Array<ImageType>) =>
32
+ (box: BoundingBox, elements: Array<HyperNode>) =>
36
33
  directionalPackingFn({
37
34
  isVertical: false,
38
- backgroundSize: background,
39
- images,
35
+ elements,
40
36
  box,
41
37
  options,
42
38
  });
@@ -45,41 +41,15 @@ export const gridPackingFn =
45
41
  <EntryType extends Record<string, string>>(
46
42
  options?: GridContainerOptions<EntryType>,
47
43
  ): PackingFn =>
48
- async (
49
- box: BoundingBox,
50
- background: ImageType,
51
- images: Array<ImageType>,
52
- ) => {
44
+ async (box: BoundingBox, elements: Array<HyperNode>) => {
53
45
  const mergedOptions = merge(
54
46
  {},
55
47
  DEFAULT_GRID_CONTAINER_OPTIONS,
56
48
  options,
57
49
  );
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
50
 
81
51
  const items = [];
82
- for (const subset of chunk(children)) {
52
+ for (const subset of chunk(elements)) {
83
53
  items.push(
84
54
  h(
85
55
  'div',
@@ -98,7 +68,7 @@ export const gridPackingFn =
98
68
  ),
99
69
  );
100
70
  }
101
- const document = h(
71
+ return h(
102
72
  'div',
103
73
  {
104
74
  style: {
@@ -112,51 +82,26 @@ export const gridPackingFn =
112
82
  },
113
83
  items,
114
84
  );
115
-
116
- return htmlToImage(document, background);
117
85
  };
118
86
 
119
87
  const directionalPackingFn = async <EntryType extends Record<string, string>>({
120
88
  isVertical,
121
- backgroundSize: background,
122
- images,
89
+ elements,
123
90
  box,
124
91
  options,
125
92
  }: {
126
93
  isVertical: boolean;
127
- backgroundSize: Size;
128
- images: Array<ImageType>;
94
+ elements: Array<HyperNode>;
129
95
  box: BoundingBox;
130
96
  options?: DirectionContainerOptions<EntryType>;
131
- }): Promise<ImageType> => {
97
+ }): Promise<HyperNode> => {
132
98
  const mergedOptions = merge(
133
99
  {},
134
100
  DEFAULT_DIRECTION_CONTAINER_OPTIONS,
135
101
  options,
136
102
  );
137
- const objectFit = SCALE_MODE_TO_OBJECT_FIT[mergedOptions.scale];
138
103
 
139
- const children = await Promise.all(
140
- images.map(async image => {
141
- const imageBase64 = await image.getBase64('image/png');
142
- return h(
143
- 'img',
144
- {
145
- style: {
146
- objectFit,
147
- flex: '1 1 auto',
148
- minWidth: 0,
149
- minHeight: 0,
150
- maxWidth: '100%',
151
- maxHeight: '100%',
152
- },
153
- src: imageBase64,
154
- },
155
- [],
156
- );
157
- }),
158
- );
159
- const document = h(
104
+ return h(
160
105
  'div',
161
106
  {
162
107
  style: {
@@ -172,8 +117,6 @@ const directionalPackingFn = async <EntryType extends Record<string, string>>({
172
117
  ...boundingBoxToPx(box),
173
118
  },
174
119
  },
175
- children,
120
+ elements,
176
121
  );
177
-
178
- return htmlToImage(document, background);
179
122
  };
@@ -1,10 +1,10 @@
1
- import { create as createElement, VNode } from 'virtual-dom';
1
+ import { create as createElement } from 'virtual-dom';
2
2
  import nodeHtmlToImage from 'node-html-to-image';
3
3
  import { Jimp } from 'jimp';
4
- import { ImageType, Size } from '../types';
4
+ import { HyperNode, ImageType, Size } from '../types';
5
5
 
6
6
  export const htmlToImage = async (
7
- document: VNode,
7
+ document: HyperNode,
8
8
  backgroundSize: Size,
9
9
  ): Promise<ImageType> => {
10
10
  const html = createElement(document).toString();
@@ -1,6 +1,6 @@
1
1
  export * from './container';
2
- export * from './drawBoundingBox';
2
+ export * from './placeBoundingBox';
3
3
  export * from './placeImage';
4
- export * from './renderText';
4
+ export * from './placeText';
5
5
  export * from './htmlToImage';
6
6
  export * from './toPx';
@@ -1,13 +1,9 @@
1
1
  import { h } from 'virtual-dom';
2
- import { htmlToImage } from './htmlToImage';
3
2
  import { boundingBoxToPx } from './toPx';
4
- import { ImageType, Size, BoundingBox } from '../types';
3
+ import { BoundingBox, HyperNode } from '../types';
5
4
 
6
- export const drawBoundingBox = async (
7
- box: BoundingBox,
8
- imageSize: Size,
9
- ): Promise<ImageType> => {
10
- const document = h(
5
+ export const placeBoundingBox = async (box: BoundingBox): Promise<HyperNode> => {
6
+ return h(
11
7
  'div',
12
8
  {
13
9
  style: {
@@ -20,6 +16,4 @@ export const drawBoundingBox = async (
20
16
  },
21
17
  [],
22
18
  );
23
-
24
- return htmlToImage(document, imageSize);
25
19
  };
@@ -5,27 +5,23 @@ import {
5
5
  ImageType,
6
6
  BoundingBox,
7
7
  SCALE_MODE_TO_OBJECT_FIT,
8
+ HyperNode,
9
+ ImageLayerSpecificOptions,
8
10
  } from '../types';
9
- import { htmlToImage } from './htmlToImage';
10
11
  import { RequiredDeep } from 'type-fest';
11
12
 
12
13
  type PlaceImageProps<EntryType extends Record<string, string>> = {
13
14
  image: ImageType;
14
15
  box: BoundingBox;
15
- backgroundSize: { width: number; height: number };
16
16
  options: RequiredDeep<ImageLayerOptions<EntryType>>;
17
17
  };
18
18
 
19
19
  export const placeImage = async <EntryType extends Record<string, string>>({
20
20
  image,
21
21
  box,
22
- backgroundSize,
23
22
  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(
23
+ }: PlaceImageProps<EntryType>): Promise<HyperNode> => {
24
+ return h(
29
25
  'div',
30
26
  {
31
27
  style: {
@@ -39,24 +35,35 @@ export const placeImage = async <EntryType extends Record<string, string>>({
39
35
  ...boundingBoxToPx(box),
40
36
  },
41
37
  },
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
- ],
38
+ [await prepareImage({ image, options })],
59
39
  );
40
+ };
41
+
42
+ type PrepareImageProps<EntryType extends Record<string, string>> = {
43
+ image: ImageType;
44
+ options: RequiredDeep<ImageLayerSpecificOptions<EntryType>>;
45
+ };
46
+
47
+ export const prepareImage = async <EntryType extends Record<string, string>>({
48
+ image,
49
+ options,
50
+ }: PrepareImageProps<EntryType>): Promise<HyperNode> => {
51
+ const imageBase64 = await image.getBase64('image/png');
52
+ const objectFit = SCALE_MODE_TO_OBJECT_FIT[options.scale];
60
53
 
61
- return htmlToImage(document, backgroundSize);
62
- }
54
+ return h(
55
+ 'img',
56
+ {
57
+ style: {
58
+ objectFit,
59
+ flex: '1 1 auto',
60
+ minWidth: 0,
61
+ minHeight: 0,
62
+ maxWidth: '100%',
63
+ maxHeight: '100%',
64
+ },
65
+ src: imageBase64,
66
+ },
67
+ [],
68
+ );
69
+ };
@@ -0,0 +1,110 @@
1
+ import { h } from 'virtual-dom';
2
+ import {
3
+ BoundingBox,
4
+ TextLayerOptions,
5
+ HyperNode,
6
+ TextLayerSpecificOptions,
7
+ } from '../types';
8
+ import { boundingBoxToPx, toPx } from './toPx';
9
+ import { RequiredDeep } from 'type-fest';
10
+
11
+ type PlaceTextProps<EntryType extends Record<string, string>> = {
12
+ text: string;
13
+ box: BoundingBox;
14
+ options: RequiredDeep<TextLayerOptions<EntryType>>;
15
+ };
16
+
17
+ export const placeText = async <EntryType extends Record<string, string>>({
18
+ text,
19
+ box,
20
+ options,
21
+ }: PlaceTextProps<EntryType>): Promise<HyperNode> => {
22
+ return h(
23
+ 'div',
24
+ {
25
+ style: {
26
+ display: 'flex',
27
+ overflow: 'visible',
28
+ position: 'absolute',
29
+
30
+ justifyContent: options.justifyContent,
31
+ alignItems: options.alignItems,
32
+
33
+ ...boundingBoxToPx(box),
34
+ },
35
+ },
36
+ [
37
+ await prepareText({
38
+ text,
39
+ options,
40
+ }),
41
+ ],
42
+ );
43
+ };
44
+
45
+ type PrepareTextProps<EntryType extends Record<string, string>> = {
46
+ text: string;
47
+ options: RequiredDeep<TextLayerSpecificOptions<EntryType>>;
48
+ };
49
+
50
+ export const prepareText = async <EntryType extends Record<string, string>>({
51
+ text,
52
+ options,
53
+ }: PrepareTextProps<EntryType>): Promise<HyperNode> => {
54
+ let textChildren: Array<string | HyperNode> = [text];
55
+ for (const [word, image] of Object.entries(options.replacement)) {
56
+ const regex = new RegExp(word, 'gi');
57
+ const imageBase64 = await image.getBase64('image/png');
58
+
59
+ let tmpChildren: Array<string | HyperNode> = [];
60
+ for (const textSegment of textChildren) {
61
+ if (typeof textSegment !== 'string') {
62
+ continue;
63
+ }
64
+
65
+ const parts = (textSegment as string).split(regex);
66
+ for (let index = 0; index < parts.length; index++) {
67
+ if (index > 0) {
68
+ tmpChildren.push(
69
+ h(
70
+ 'img',
71
+ {
72
+ style: {
73
+ display: 'inline',
74
+ verticalAlign: 'middle',
75
+ height: toPx(options.font.size),
76
+ width: 'auto',
77
+ },
78
+ src: imageBase64,
79
+ },
80
+ [],
81
+ ),
82
+ );
83
+ }
84
+ tmpChildren.push(parts[index]);
85
+ }
86
+ }
87
+
88
+ textChildren = tmpChildren;
89
+ }
90
+
91
+ return h(
92
+ 'div',
93
+ {
94
+ style: {
95
+ overflow: 'visible',
96
+ overflowWrap: 'word-wrap',
97
+ whiteSpace: 'normal',
98
+
99
+ color: options.color,
100
+ fontFamily: options.font.family,
101
+ fontSize: options.font.size,
102
+ fontStyle: options.font.italic ? 'italic' : undefined,
103
+ fontWeight: options.font.bold ? 'bold' : undefined,
104
+
105
+ '-webkit-text-stroke': `${options.border.width}px ${options.border.color}`,
106
+ },
107
+ },
108
+ textChildren,
109
+ );
110
+ };
@@ -1,107 +0,0 @@
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';
6
-
7
- export const renderText = async <EntryType extends Record<string, string>>(
8
- text: string,
9
- box: BoundingBox,
10
- backgroundSize: Size,
11
- options: RequiredDeep<TextLayerOptions<EntryType>>,
12
- fonts: Record<string, string>,
13
- ): Promise<ImageType> => {
14
- let textChildren: Array<string | VNode> = [text];
15
- for (const [word, image] of Object.entries(options.replacement)) {
16
- const regex = new RegExp(word, 'gi');
17
- const imageBase64 = await image.getBase64('image/png');
18
-
19
- let tmpChildren: Array<string | VNode> = [];
20
- for (const textSegment of textChildren) {
21
- if (typeof textSegment !== 'string') {
22
- continue;
23
- }
24
-
25
- const parts = (textSegment as string).split(regex);
26
- for (let index = 0; index < parts.length; index++) {
27
- if (index > 0) {
28
- tmpChildren.push(
29
- h(
30
- 'img',
31
- {
32
- style: {
33
- display: 'inline',
34
- verticalAlign: 'middle',
35
- height: toPx(options.font.size),
36
- width: 'auto',
37
- },
38
- src: imageBase64,
39
- },
40
- [],
41
- ),
42
- );
43
- }
44
- tmpChildren.push(parts[index]);
45
- }
46
- }
47
-
48
- textChildren = tmpChildren;
49
- }
50
-
51
- const content = h(
52
- 'div',
53
- {
54
- style: {
55
- display: 'flex',
56
- overflow: 'visible',
57
- position: 'absolute',
58
-
59
- justifyContent: options.justifyContent,
60
- alignItems: options.alignItems,
61
-
62
- ...boundingBoxToPx(box),
63
- },
64
- },
65
- [
66
- h(
67
- 'div',
68
- {
69
- style: {
70
- overflow: 'visible',
71
- overflowWrap: 'word-wrap',
72
- whiteSpace: 'normal',
73
-
74
- color: options.color,
75
- fontFamily: options.font.family,
76
- fontSize: options.font.size,
77
- fontStyle: options.font.italic ? 'italic' : undefined,
78
- fontWeight: options.font.bold ? 'bold' : undefined,
79
-
80
- '-webkit-text-stroke': `${options.border.width}px ${options.border.color}`
81
- },
82
- },
83
- textChildren,
84
- ),
85
- ],
86
- );
87
-
88
- const document = h('html', [
89
- h('head', [
90
- h(
91
- 'style',
92
- Object.entries(fonts)
93
- .map(
94
- ([name, data]) =>
95
- `@font-face {
96
- font-family: '${name}';
97
- src: url(data:font/ttf;base64,${data}) format('truetype');
98
- }`,
99
- )
100
- .join('\n'),
101
- ),
102
- ]),
103
- h('body', [content]),
104
- ]);
105
-
106
- return htmlToImage(document, backgroundSize);
107
- };