pixeli 0.1.8 → 1.0.3

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 (204) hide show
  1. package/README.md +341 -88
  2. package/dist/cli/commands/collage/index.d.ts +2 -0
  3. package/dist/cli/commands/collage/index.js +125 -0
  4. package/dist/cli/commands/grid/index.d.ts +2 -0
  5. package/dist/cli/commands/grid/index.js +127 -0
  6. package/dist/cli/commands/masonry/index.d.ts +2 -0
  7. package/dist/cli/commands/masonry/index.js +129 -0
  8. package/dist/cli/commands/template/index.d.ts +2 -0
  9. package/dist/cli/commands/template/index.js +123 -0
  10. package/dist/cli/commands/template/presets/artGallery.d.ts +15 -0
  11. package/dist/cli/commands/template/presets/artGallery.js +15 -0
  12. package/dist/cli/commands/template/presets/dashboardShot.d.ts +15 -0
  13. package/dist/cli/commands/template/presets/dashboardShot.js +16 -0
  14. package/dist/cli/commands/template/presets/horizontalBookSpread.d.ts +15 -0
  15. package/dist/cli/commands/template/presets/horizontalBookSpread.js +13 -0
  16. package/dist/cli/commands/template/presets/instagramGrid.d.ts +15 -0
  17. package/dist/cli/commands/template/presets/instagramGrid.js +16 -0
  18. package/dist/cli/commands/template/presets/verticalBookSpread.d.ts +15 -0
  19. package/dist/cli/commands/template/presets/verticalBookSpread.js +13 -0
  20. package/dist/cli/commands/template/presets.d.ts +73 -0
  21. package/{lib/merges/collage-merge → dist/cli/commands/template}/presets.js +6 -8
  22. package/dist/cli/index.d.ts +2 -0
  23. package/dist/cli/index.js +24 -0
  24. package/dist/cli/modules/loadImages.d.ts +15 -0
  25. package/dist/cli/modules/loadImages.js +74 -0
  26. package/dist/cli/modules/progressBar.d.ts +10 -0
  27. package/dist/cli/modules/progressBar.js +34 -0
  28. package/dist/cli/schemas/collage.d.ts +29 -0
  29. package/dist/cli/schemas/collage.js +38 -0
  30. package/dist/cli/schemas/grid.d.ts +34 -0
  31. package/dist/cli/schemas/grid.js +38 -0
  32. package/dist/cli/schemas/masonry.d.ts +62 -0
  33. package/dist/cli/schemas/masonry.js +62 -0
  34. package/dist/cli/schemas/template.d.ts +31 -0
  35. package/dist/cli/schemas/template.js +49 -0
  36. package/dist/cli/utils/buildCommandFromSchema.d.ts +8 -0
  37. package/dist/cli/utils/buildCommandFromSchema.js +55 -0
  38. package/dist/cli/utils/configureCommandErrors.d.ts +2 -0
  39. package/dist/cli/utils/configureCommandErrors.js +22 -0
  40. package/dist/cli/utils/stringFormatter.d.ts +1 -0
  41. package/dist/cli/utils/stringFormatter.js +3 -0
  42. package/dist/cli/utils/toErrorMessage.d.ts +4 -0
  43. package/dist/cli/utils/toErrorMessage.js +22 -0
  44. package/dist/core/helpers.d.ts +10 -0
  45. package/dist/core/helpers.js +42 -0
  46. package/dist/core/index.d.ts +1 -0
  47. package/dist/core/index.js +1 -0
  48. package/dist/core/mergeError.d.ts +9 -0
  49. package/dist/core/mergeError.js +10 -0
  50. package/dist/core/merges/collage/index.d.ts +9 -0
  51. package/dist/core/merges/collage/index.js +32 -0
  52. package/dist/core/merges/collage/steps/calculateImageDimensions.d.ts +12 -0
  53. package/dist/core/merges/collage/steps/calculateImageDimensions.js +18 -0
  54. package/dist/core/merges/collage/steps/createComposites.d.ts +8 -0
  55. package/dist/core/merges/collage/steps/createComposites.js +58 -0
  56. package/dist/core/merges/collage/steps/resizeAndBorderImages.d.ts +12 -0
  57. package/dist/core/merges/collage/steps/resizeAndBorderImages.js +26 -0
  58. package/dist/core/merges/collage/steps/rotateImages.d.ts +7 -0
  59. package/dist/core/merges/collage/steps/rotateImages.js +9 -0
  60. package/dist/core/merges/grid/index.d.ts +12 -0
  61. package/dist/core/merges/grid/index.js +36 -0
  62. package/dist/core/merges/grid/steps/calculateCanvasDimensions.d.ts +8 -0
  63. package/dist/core/merges/grid/steps/calculateCanvasDimensions.js +18 -0
  64. package/dist/core/merges/grid/steps/calculateFontSize.d.ts +7 -0
  65. package/dist/core/merges/grid/steps/calculateFontSize.js +19 -0
  66. package/dist/core/merges/grid/steps/calculateImageDimensions.d.ts +8 -0
  67. package/dist/core/merges/grid/steps/calculateImageDimensions.js +18 -0
  68. package/dist/core/merges/grid/steps/createComposites.d.ts +10 -0
  69. package/dist/core/merges/grid/steps/createComposites.js +63 -0
  70. package/dist/core/merges/grid/steps/prepareImages.d.ts +10 -0
  71. package/dist/core/merges/grid/steps/prepareImages.js +29 -0
  72. package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.d.ts +7 -0
  73. package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.js +17 -0
  74. package/dist/core/merges/index.d.ts +3 -0
  75. package/dist/core/merges/index.js +3 -0
  76. package/dist/core/merges/masonry/index.d.ts +10 -0
  77. package/dist/core/merges/masonry/index.js +32 -0
  78. package/dist/core/merges/masonry/steps/calculateCanvasDimensions.d.ts +15 -0
  79. package/dist/core/merges/masonry/steps/calculateCanvasDimensions.js +17 -0
  80. package/dist/core/merges/masonry/steps/calculateLaneSize.d.ts +9 -0
  81. package/dist/core/merges/masonry/steps/calculateLaneSize.js +27 -0
  82. package/dist/core/merges/masonry/steps/createComposites.d.ts +25 -0
  83. package/dist/core/merges/masonry/steps/createComposites.js +108 -0
  84. package/dist/core/merges/masonry/steps/resizeImages.d.ts +7 -0
  85. package/dist/core/merges/masonry/steps/resizeImages.js +14 -0
  86. package/dist/core/merges/masonry/steps/splitIntoLanes.d.ts +17 -0
  87. package/dist/core/merges/masonry/steps/splitIntoLanes.js +78 -0
  88. package/dist/core/merges/shared-steps/applyComposites.d.ts +2 -0
  89. package/dist/core/merges/shared-steps/applyComposites.js +16 -0
  90. package/dist/core/merges/shared-steps/createCanvas.d.ts +11 -0
  91. package/dist/core/merges/shared-steps/createCanvas.js +17 -0
  92. package/dist/core/merges/shared-steps/exportCanvas.d.ts +7 -0
  93. package/dist/core/merges/shared-steps/exportCanvas.js +25 -0
  94. package/dist/core/merges/shared-steps/finalizeImagePipelines.d.ts +2 -0
  95. package/dist/core/merges/shared-steps/finalizeImagePipelines.js +9 -0
  96. package/dist/core/merges/shared-steps/loadImages.d.ts +2 -0
  97. package/dist/core/merges/shared-steps/loadImages.js +26 -0
  98. package/dist/core/merges/shared-steps/validateCaptions.d.ts +10 -0
  99. package/dist/core/merges/shared-steps/validateCaptions.js +17 -0
  100. package/dist/core/merges/template/index.d.ts +10 -0
  101. package/dist/core/merges/template/index.js +28 -0
  102. package/dist/core/merges/template/steps/calculateSlotDimensions.d.ts +9 -0
  103. package/dist/core/merges/template/steps/calculateSlotDimensions.js +12 -0
  104. package/dist/core/merges/template/steps/createComposites.d.ts +10 -0
  105. package/dist/core/merges/template/steps/createComposites.js +25 -0
  106. package/dist/core/merges/template/steps/getBlocks.d.ts +13 -0
  107. package/dist/core/merges/template/steps/getBlocks.js +28 -0
  108. package/dist/core/merges/template/types.d.ts +21 -0
  109. package/dist/core/merges/template/types.js +1 -0
  110. package/dist/core/merges/types.d.ts +102 -0
  111. package/dist/core/merges/types.js +1 -0
  112. package/dist/core/modules/messages.d.ts +32 -0
  113. package/dist/core/modules/messages.js +54 -0
  114. package/dist/core/pipeline/guards.d.ts +4 -0
  115. package/dist/core/pipeline/guards.js +23 -0
  116. package/dist/core/pipeline/mergePipeline.d.ts +60 -0
  117. package/dist/core/pipeline/mergePipeline.js +122 -0
  118. package/dist/core/schemas/collage.d.ts +26 -0
  119. package/dist/core/schemas/collage.js +17 -0
  120. package/dist/core/schemas/grid.d.ts +32 -0
  121. package/dist/core/schemas/grid.js +29 -0
  122. package/dist/core/schemas/masonry.d.ts +56 -0
  123. package/dist/core/schemas/masonry.js +43 -0
  124. package/dist/core/schemas/template.d.ts +34 -0
  125. package/dist/core/schemas/template.js +88 -0
  126. package/dist/core/utils/colors/hexToRgba.d.ts +2 -0
  127. package/dist/core/utils/colors/hexToRgba.js +25 -0
  128. package/dist/core/utils/colors/rgbaToHex.d.ts +2 -0
  129. package/dist/core/utils/colors/rgbaToHex.js +15 -0
  130. package/dist/core/utils/colors/types.d.ts +7 -0
  131. package/dist/core/utils/colors/types.js +1 -0
  132. package/dist/core/utils/fonts/getFontSize.d.ts +9 -0
  133. package/dist/core/utils/fonts/getFontSize.js +40 -0
  134. package/dist/core/utils/images/addImageBorder.d.ts +13 -0
  135. package/dist/core/utils/images/addImageBorder.js +33 -0
  136. package/dist/core/utils/images/getImageHeights.d.ts +2 -0
  137. package/dist/core/utils/images/getImageHeights.js +9 -0
  138. package/dist/core/utils/images/getImageWidths.d.ts +2 -0
  139. package/dist/core/utils/images/getImageWidths.js +9 -0
  140. package/dist/core/utils/images/getSmallestImageDimensions.d.ts +5 -0
  141. package/dist/core/utils/images/getSmallestImageDimensions.js +9 -0
  142. package/dist/core/utils/images/handleImageEdges.d.ts +13 -0
  143. package/dist/core/utils/images/handleImageEdges.js +39 -0
  144. package/dist/core/utils/images/isActualImage.d.ts +5 -0
  145. package/dist/core/utils/images/isActualImage.js +26 -0
  146. package/dist/core/utils/images/parseAspectRatio.d.ts +1 -0
  147. package/dist/core/utils/images/parseAspectRatio.js +22 -0
  148. package/dist/core/utils/images/roundImage.d.ts +9 -0
  149. package/dist/core/utils/images/roundImage.js +27 -0
  150. package/dist/core/utils/images/roundImages.d.ts +8 -0
  151. package/dist/core/utils/images/roundImages.js +19 -0
  152. package/dist/core/utils/images/scaleImage.d.ts +8 -0
  153. package/dist/core/utils/images/scaleImage.js +36 -0
  154. package/dist/core/utils/images/scaleImages.d.ts +8 -0
  155. package/dist/core/utils/images/scaleImages.js +38 -0
  156. package/dist/core/utils/math/median.d.ts +1 -0
  157. package/dist/core/utils/math/median.js +12 -0
  158. package/dist/core/utils/math/randint.d.ts +6 -0
  159. package/dist/core/utils/math/randint.js +11 -0
  160. package/dist/core/utils/math/trimmedMedian.d.ts +1 -0
  161. package/dist/core/utils/math/trimmedMedian.js +12 -0
  162. package/dist/core/utils/svg/createSvgTextBuffer.d.ts +9 -0
  163. package/dist/core/utils/svg/createSvgTextBuffer.js +21 -0
  164. package/dist/validators/aspectRatio.d.ts +2 -0
  165. package/dist/validators/aspectRatio.js +15 -0
  166. package/dist/validators/coercion.d.ts +2 -0
  167. package/dist/validators/coercion.js +12 -0
  168. package/dist/validators/format.d.ts +2 -0
  169. package/dist/validators/format.js +15 -0
  170. package/dist/validators/hexColor.d.ts +7 -0
  171. package/dist/validators/hexColor.js +27 -0
  172. package/dist/validators/index.d.ts +95 -0
  173. package/dist/validators/index.js +64 -0
  174. package/dist/validators/outputFile.d.ts +2 -0
  175. package/dist/validators/outputFile.js +7 -0
  176. package/dist/validators/path.d.ts +3 -0
  177. package/dist/validators/path.js +18 -0
  178. package/dist/validators/sharpImageInput.d.ts +3 -0
  179. package/dist/validators/sharpImageInput.js +6 -0
  180. package/dist/validators/template.d.ts +15 -0
  181. package/dist/validators/template.js +41 -0
  182. package/package.json +26 -9
  183. package/bin/pixeli.js +0 -26
  184. package/commands/merge/collage.js +0 -83
  185. package/commands/merge/grid.js +0 -71
  186. package/commands/merge/helpers/utils.js +0 -11
  187. package/commands/merge/helpers/validations.js +0 -269
  188. package/commands/merge/index.js +0 -12
  189. package/commands/merge/masonry.js +0 -72
  190. package/lib/helpers/loadImages.js +0 -94
  191. package/lib/helpers/progressBar.js +0 -20
  192. package/lib/helpers/templateValidator.js +0 -139
  193. package/lib/helpers/utils.js +0 -208
  194. package/lib/merges/collage-merge/index.js +0 -110
  195. package/lib/merges/collage-merge/presets/artGallery.js +0 -17
  196. package/lib/merges/collage-merge/presets/dashboardShot.js +0 -18
  197. package/lib/merges/collage-merge/presets/horizontalBookSpread.js +0 -15
  198. package/lib/merges/collage-merge/presets/instagramGrid.js +0 -18
  199. package/lib/merges/collage-merge/presets/verticalBookSpread.js +0 -15
  200. package/lib/merges/grid-merge/index.js +0 -152
  201. package/lib/merges/masonry-merge/horizontal.js +0 -157
  202. package/lib/merges/masonry-merge/index.js +0 -57
  203. package/lib/merges/masonry-merge/vertical.js +0 -152
  204. package/lib/merges/merge-utils.js +0 -176
@@ -0,0 +1,56 @@
1
+ import z from 'zod';
2
+ export declare const masonrySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
3
+ flow: z.ZodLiteral<"horizontal">;
4
+ rowHeight: z.ZodOptional<z.ZodNumber>;
5
+ canvasWidth: z.ZodNumber;
6
+ hAlign: z.ZodDefault<z.ZodEnum<{
7
+ left: "left";
8
+ center: "center";
9
+ right: "right";
10
+ justified: "justified";
11
+ }>>;
12
+ shuffle: z.ZodDefault<z.ZodBoolean>;
13
+ cornerRadius: z.ZodDefault<z.ZodNumber>;
14
+ gap: z.ZodDefault<z.ZodNumber>;
15
+ canvasColor: z.ZodPrefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<import("../utils/colors/types.js").RGBA, string>>, z.ZodObject<{
16
+ r: z.ZodNumber;
17
+ g: z.ZodNumber;
18
+ b: z.ZodNumber;
19
+ alpha: z.ZodNumber;
20
+ }, z.z.core.$strip>]>>;
21
+ format: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<"webp" | "gif" | "jpeg" | "jpg" | "png" | "tiff" | "avif", string>>>;
22
+ borderWidth: z.ZodDefault<z.ZodNumber>;
23
+ borderColor: z.ZodPrefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<import("../utils/colors/types.js").RGBA, string>>, z.ZodObject<{
24
+ r: z.ZodNumber;
25
+ g: z.ZodNumber;
26
+ b: z.ZodNumber;
27
+ alpha: z.ZodNumber;
28
+ }, z.z.core.$strip>]>>;
29
+ }, z.z.core.$strip>, z.ZodObject<{
30
+ flow: z.ZodLiteral<"vertical">;
31
+ columnWidth: z.ZodOptional<z.ZodNumber>;
32
+ canvasHeight: z.ZodNumber;
33
+ vAlign: z.ZodDefault<z.ZodEnum<{
34
+ justified: "justified";
35
+ top: "top";
36
+ middle: "middle";
37
+ bottom: "bottom";
38
+ }>>;
39
+ shuffle: z.ZodDefault<z.ZodBoolean>;
40
+ cornerRadius: z.ZodDefault<z.ZodNumber>;
41
+ gap: z.ZodDefault<z.ZodNumber>;
42
+ canvasColor: z.ZodPrefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<import("../utils/colors/types.js").RGBA, string>>, z.ZodObject<{
43
+ r: z.ZodNumber;
44
+ g: z.ZodNumber;
45
+ b: z.ZodNumber;
46
+ alpha: z.ZodNumber;
47
+ }, z.z.core.$strip>]>>;
48
+ format: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<"webp" | "gif" | "jpeg" | "jpg" | "png" | "tiff" | "avif", string>>>;
49
+ borderWidth: z.ZodDefault<z.ZodNumber>;
50
+ borderColor: z.ZodPrefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<import("../utils/colors/types.js").RGBA, string>>, z.ZodObject<{
51
+ r: z.ZodNumber;
52
+ g: z.ZodNumber;
53
+ b: z.ZodNumber;
54
+ alpha: z.ZodNumber;
55
+ }, z.z.core.$strip>]>>;
56
+ }, z.z.core.$strip>], "flow">;
@@ -0,0 +1,43 @@
1
+ import z from 'zod';
2
+ import { VALIDATORS } from '../../validators/index.js';
3
+ const baseMasonrySchema = z.object({
4
+ shuffle: VALIDATORS.shuffle.default(false),
5
+ cornerRadius: VALIDATORS.cornerRadius.default(0),
6
+ gap: VALIDATORS.gap.default(50),
7
+ canvasColor: VALIDATORS.canvasColor.prefault('#fff'),
8
+ format: VALIDATORS.format.default('png'),
9
+ borderWidth: VALIDATORS.borderWidth.default(0),
10
+ borderColor: VALIDATORS.borderColor.prefault('#000'),
11
+ });
12
+ const horizontalMasonrySchema = z.object({
13
+ ...baseMasonrySchema.shape,
14
+ flow: z.literal('horizontal'),
15
+ rowHeight: VALIDATORS.rowHeight.optional(),
16
+ canvasWidth: VALIDATORS.canvasWidth,
17
+ hAlign: VALIDATORS.hAlign.default('justified'),
18
+ });
19
+ const verticalMasonrySchema = z.object({
20
+ ...baseMasonrySchema.shape,
21
+ flow: z.literal('vertical'),
22
+ columnWidth: VALIDATORS.rowHeight.optional(),
23
+ canvasHeight: VALIDATORS.canvasHeight,
24
+ vAlign: VALIDATORS.vAlign.default('justified'),
25
+ });
26
+ export const masonrySchema = z
27
+ .discriminatedUnion('flow', [horizontalMasonrySchema, verticalMasonrySchema])
28
+ .superRefine((opts, ctx) => {
29
+ if (opts.flow === 'horizontal' && opts.canvasWidth <= opts.gap * 2) {
30
+ ctx.addIssue({
31
+ code: 'custom',
32
+ message: "Canvas is too small to place images in. Increase 'canvasWidth'.",
33
+ path: ['canvasWidth'],
34
+ });
35
+ }
36
+ if (opts.flow === 'vertical' && opts.canvasHeight <= opts.gap * 2) {
37
+ ctx.addIssue({
38
+ code: 'custom',
39
+ message: "Canvas is too small to place images in. Increase 'canvasHeight'.",
40
+ path: ['canvasHeight'],
41
+ });
42
+ }
43
+ });
@@ -0,0 +1,34 @@
1
+ import z from 'zod';
2
+ export declare const templateSchema: z.ZodObject<{
3
+ shuffle: z.ZodDefault<z.ZodBoolean>;
4
+ cornerRadius: z.ZodDefault<z.ZodNumber>;
5
+ gap: z.ZodDefault<z.ZodNumber>;
6
+ canvasColor: z.ZodPrefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<import("../utils/colors/types.js").RGBA, string>>, z.ZodObject<{
7
+ r: z.ZodNumber;
8
+ g: z.ZodNumber;
9
+ b: z.ZodNumber;
10
+ alpha: z.ZodNumber;
11
+ }, z.z.core.$strip>]>>;
12
+ format: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<"webp" | "gif" | "jpeg" | "jpg" | "png" | "tiff" | "avif", string>>>;
13
+ template: z.ZodObject<{
14
+ canvas: z.ZodObject<{
15
+ width: z.ZodNumber;
16
+ height: z.ZodNumber;
17
+ columns: z.ZodNumber;
18
+ rows: z.ZodNumber;
19
+ }, z.z.core.$strip>;
20
+ slots: z.ZodArray<z.ZodObject<{
21
+ col: z.ZodNumber;
22
+ row: z.ZodNumber;
23
+ colSpan: z.ZodNumber;
24
+ rowSpan: z.ZodNumber;
25
+ }, z.z.core.$strip>>;
26
+ }, z.z.core.$strip>;
27
+ borderWidth: z.ZodDefault<z.ZodNumber>;
28
+ borderColor: z.ZodPrefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<import("../utils/colors/types.js").RGBA, string>>, z.ZodObject<{
29
+ r: z.ZodNumber;
30
+ g: z.ZodNumber;
31
+ b: z.ZodNumber;
32
+ alpha: z.ZodNumber;
33
+ }, z.z.core.$strip>]>>;
34
+ }, z.z.core.$strict>;
@@ -0,0 +1,88 @@
1
+ import z from 'zod';
2
+ import { VALIDATORS } from '../../validators/index.js';
3
+ export const templateSchema = z
4
+ .strictObject({
5
+ shuffle: VALIDATORS.shuffle.default(false),
6
+ cornerRadius: VALIDATORS.cornerRadius.default(0),
7
+ gap: VALIDATORS.gap.default(50),
8
+ canvasColor: VALIDATORS.canvasColor.prefault('#fff'),
9
+ format: VALIDATORS.format.default('png'),
10
+ template: VALIDATORS.template,
11
+ borderWidth: VALIDATORS.borderWidth.default(0),
12
+ borderColor: VALIDATORS.borderColor.prefault('#000'),
13
+ })
14
+ .superRefine((opts, ctx) => {
15
+ // Ensure canvas is wide enough for at least a single 1px column
16
+ if (opts.template.canvas.width <= opts.gap * 2) {
17
+ ctx.addIssue({
18
+ code: 'custom',
19
+ message: `Canvas width must be greater than ${opts.gap * 2}.`,
20
+ path: ['template', 'canvas', 'width'],
21
+ });
22
+ }
23
+ // Ensure canvas is long enough for at least a single 1px row
24
+ if (opts.template.canvas.height <= opts.gap * 2) {
25
+ ctx.addIssue({
26
+ code: 'custom',
27
+ message: `Canvas height must be greater than ${opts.gap * 2}.`,
28
+ path: ['template', 'canvas', 'height'],
29
+ });
30
+ }
31
+ // Calculate column width and row height
32
+ const workableCanvasWidth = opts.template.canvas.width - opts.gap * (opts.template.canvas.columns + 1);
33
+ const workableCanvasHeight = opts.template.canvas.height - opts.gap * (opts.template.canvas.rows + 1);
34
+ const columnWidth = Math.floor(workableCanvasWidth / opts.template.canvas.columns);
35
+ const rowHeight = Math.floor(workableCanvasHeight / opts.template.canvas.rows);
36
+ // Ensure columns are thick enough
37
+ if (columnWidth <= 0) {
38
+ ctx.addIssue({
39
+ code: 'custom',
40
+ message: `Columns are too thin. Increase canvas width, reduce gap, or reduce number of columns.`,
41
+ path: ['template', 'canvas', 'columns'],
42
+ });
43
+ }
44
+ // Ensure rows are thick enough
45
+ if (rowHeight <= 0) {
46
+ ctx.addIssue({
47
+ code: 'custom',
48
+ message: `Rows are too thin. Increase canvas height or reduce number of rows.`,
49
+ path: ['template', 'canvas', 'rows'],
50
+ });
51
+ }
52
+ // For each slot...
53
+ for (let i = 0; i < opts.template.slots.length; i++) {
54
+ const slot = opts.template.slots[i];
55
+ // Ensure slot is placed inside given canvas columns
56
+ if (slot.col > opts.template.canvas.columns) {
57
+ ctx.addIssue({
58
+ code: 'custom',
59
+ message: `"col" must be between 1 and ${opts.template.canvas.columns}.`,
60
+ path: ['template', 'slots', i, 'col'],
61
+ });
62
+ }
63
+ // Ensure slot is placed inside given canvas rows
64
+ if (slot.row > opts.template.canvas.rows) {
65
+ ctx.addIssue({
66
+ code: 'custom',
67
+ message: `"row" must be between 1 and ${opts.template.canvas.rows}.`,
68
+ path: ['template', 'slots', i, 'row'],
69
+ });
70
+ }
71
+ // Ensure slot spans within given canvas columns
72
+ if (slot.col + slot.colSpan - 1 > opts.template.canvas.columns) {
73
+ ctx.addIssue({
74
+ code: 'custom',
75
+ message: `slot spans past the right edge of the grid (col + colSpan exceeds columns).`,
76
+ path: ['template', 'slots', i],
77
+ });
78
+ }
79
+ // Ensure slot spans within given canvas rows
80
+ if (slot.row + slot.rowSpan - 1 > opts.template.canvas.rows) {
81
+ ctx.addIssue({
82
+ code: 'custom',
83
+ message: `slot spans past the bottom edge of the grid (row + rowSpan exceeds rows).`,
84
+ path: ['template', 'slots', i],
85
+ });
86
+ }
87
+ }
88
+ });
@@ -0,0 +1,2 @@
1
+ import type { RGBA } from './types.js';
2
+ export declare const hexToRgba: (color: string) => RGBA;
@@ -0,0 +1,25 @@
1
+ export const hexToRgba = (color) => {
2
+ // 3. Normalize hex → RGBA
3
+ let r, g, b, alpha;
4
+ const hexValue = color.slice(1);
5
+ // #rgb
6
+ if (hexValue.length === 3) {
7
+ r = parseInt(hexValue.charAt(0) + hexValue.charAt(0), 16);
8
+ g = parseInt(hexValue.charAt(1) + hexValue.charAt(1), 16);
9
+ b = parseInt(hexValue.charAt(2) + hexValue.charAt(2), 16);
10
+ alpha = 1;
11
+ } // #rrggbb
12
+ else if (hexValue.length === 6) {
13
+ r = parseInt(hexValue.slice(0, 2), 16);
14
+ g = parseInt(hexValue.slice(2, 4), 16);
15
+ b = parseInt(hexValue.slice(4, 6), 16);
16
+ alpha = 1;
17
+ } // #rrggbbaa
18
+ else {
19
+ r = parseInt(hexValue.slice(0, 2), 16);
20
+ g = parseInt(hexValue.slice(2, 4), 16);
21
+ b = parseInt(hexValue.slice(4, 6), 16);
22
+ alpha = Number((parseInt(hexValue.slice(6, 8), 16) / 255).toFixed(2));
23
+ }
24
+ return { r, g, b, alpha };
25
+ };
@@ -0,0 +1,2 @@
1
+ import type { RGBA } from './types.js';
2
+ export declare const rgbaToHex: (rgba: RGBA) => string;
@@ -0,0 +1,15 @@
1
+ export const rgbaToHex = (rgba) => {
2
+ // Function to convert a number to a 2-digit hex string
3
+ const componentToHex = (c) => {
4
+ const hex = c.toString(16);
5
+ return hex.length === 1 ? '0' + hex : hex;
6
+ };
7
+ // Convert r, g, b (0-255)
8
+ const rHex = componentToHex(rgba.r);
9
+ const gHex = componentToHex(rgba.g);
10
+ const bHex = componentToHex(rgba.b);
11
+ // Convert alpha (0-1) to 0-255 range, then to 2-digit hex
12
+ const aVal = Math.round(rgba.alpha * 255);
13
+ const aHex = componentToHex(aVal);
14
+ return `#${rHex}${gHex}${bHex}${aHex}`;
15
+ };
@@ -0,0 +1,7 @@
1
+ export type RGBA = {
2
+ r: number;
3
+ g: number;
4
+ b: number;
5
+ alpha: number;
6
+ };
7
+ export type Color = string | RGBA;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ interface GetFontSizeOptions {
2
+ text: string;
3
+ maxWidth: number;
4
+ maxHeight: number;
5
+ initialFontSize?: number;
6
+ minFontSize?: number;
7
+ }
8
+ export declare const getFontSize: ({ text, maxWidth, maxHeight, initialFontSize, minFontSize }: GetFontSizeOptions) => Promise<number>;
9
+ export {};
@@ -0,0 +1,40 @@
1
+ import sharp from 'sharp';
2
+ import { escapeXML } from '../../helpers.js';
3
+ export const getFontSize = async ({ text, maxWidth, maxHeight, initialFontSize = 100, minFontSize = 2 }) => {
4
+ const FONT_FAMILY = 'sans-serif';
5
+ const THRESHOLD = 200;
6
+ const SMALL_CHANGE = 2;
7
+ const LARGE_CHANGE = 5;
8
+ let fontSize = initialFontSize;
9
+ while (fontSize >= minFontSize) {
10
+ // No width or viewport given so that the actual size can be determined after rasterization
11
+ const svg = `
12
+ <svg xmlns="http://www.w3.org/2000/svg">
13
+ <text
14
+ x="${maxWidth / 2}"
15
+ y="10"
16
+ font-size="${fontSize}"
17
+ font-family="${FONT_FAMILY}"
18
+ fill="#000000"
19
+ text-anchor="middle"
20
+ dominant-baseline="middle">
21
+ ${escapeXML(text)}
22
+ </text>
23
+ </svg>
24
+ `;
25
+ // Rasterize SVG: measure actual rendered size
26
+ const raster = await sharp(Buffer.from(svg)).png().toBuffer();
27
+ const meta = await sharp(raster).metadata();
28
+ if (meta.width <= maxWidth && meta.height <= maxHeight) {
29
+ return fontSize;
30
+ }
31
+ // If the difference is greater than the threshold, use large change
32
+ if (meta.width - maxWidth > THRESHOLD || meta.height - maxHeight > THRESHOLD) {
33
+ fontSize -= LARGE_CHANGE;
34
+ }
35
+ else {
36
+ fontSize -= SMALL_CHANGE;
37
+ }
38
+ }
39
+ return minFontSize;
40
+ };
@@ -0,0 +1,13 @@
1
+ import sharp from 'sharp';
2
+ import type { RGBA } from '../colors/types.js';
3
+ interface Options {
4
+ borderWidth: number;
5
+ borderHeight: number;
6
+ borderColor: RGBA;
7
+ cornerRadius: number;
8
+ imageWidth: number;
9
+ imageHeight: number;
10
+ finalizePipeline?: boolean;
11
+ }
12
+ export declare const addImageBorder: (image: sharp.Sharp, { borderWidth, borderHeight, borderColor, imageWidth, imageHeight, cornerRadius, finalizePipeline }: Options) => Promise<sharp.Sharp>;
13
+ export {};
@@ -0,0 +1,33 @@
1
+ import sharp from 'sharp';
2
+ export const addImageBorder = async (image, { borderWidth, borderHeight, borderColor, imageWidth, imageHeight, cornerRadius = 0, finalizePipeline = false }) => {
3
+ // Only add borders to an image if needed
4
+ if (borderWidth <= 0)
5
+ return image;
6
+ // Create background to act as border
7
+ const background = sharp({
8
+ create: {
9
+ width: imageWidth,
10
+ height: imageHeight,
11
+ channels: 4,
12
+ background: borderColor,
13
+ },
14
+ }).toFormat('png');
15
+ // Crop image
16
+ const croppedImage = image.resize({
17
+ width: imageWidth - borderWidth * 2,
18
+ height: imageHeight - borderHeight * 2,
19
+ });
20
+ // Put cropped image on the background
21
+ background.composite([
22
+ {
23
+ input: await croppedImage.toBuffer(),
24
+ top: borderWidth,
25
+ left: borderHeight,
26
+ },
27
+ ]);
28
+ // Finalize image if needed
29
+ if (finalizePipeline) {
30
+ return sharp(await background.toBuffer());
31
+ }
32
+ return background;
33
+ };
@@ -0,0 +1,2 @@
1
+ import sharp from 'sharp';
2
+ export declare const getImageHeights: (images: sharp.Sharp[]) => Promise<number[]>;
@@ -0,0 +1,9 @@
1
+ import sharp from 'sharp';
2
+ export const getImageHeights = async (images) => {
3
+ const heights = [];
4
+ for (const image of images) {
5
+ const meta = await image.metadata();
6
+ heights.push(meta.height);
7
+ }
8
+ return heights;
9
+ };
@@ -0,0 +1,2 @@
1
+ import sharp from 'sharp';
2
+ export declare const getImageWidths: (images: sharp.Sharp[]) => Promise<number[]>;
@@ -0,0 +1,9 @@
1
+ import sharp from 'sharp';
2
+ export const getImageWidths = async (images) => {
3
+ const widths = [];
4
+ for (const image of images) {
5
+ const meta = await image.metadata();
6
+ widths.push(meta.width);
7
+ }
8
+ return widths;
9
+ };
@@ -0,0 +1,5 @@
1
+ import sharp from 'sharp';
2
+ export declare const getSmallestImageDimensions: (images: sharp.Sharp[]) => Promise<{
3
+ smallestWidth: number;
4
+ smallestHeight: number;
5
+ }>;
@@ -0,0 +1,9 @@
1
+ import sharp from 'sharp';
2
+ // Width and height do not necessarily have to be from the same image
3
+ export const getSmallestImageDimensions = async (images) => {
4
+ const metas = await Promise.all(images.map((img) => img.metadata()));
5
+ return metas.reduce((acc, meta) => ({
6
+ smallestWidth: Math.min(acc.smallestWidth, meta.width),
7
+ smallestHeight: Math.min(acc.smallestHeight, meta.height),
8
+ }), { smallestWidth: Infinity, smallestHeight: Infinity });
9
+ };
@@ -0,0 +1,13 @@
1
+ import sharp from 'sharp';
2
+ import type { RGBA } from '../colors/types.js';
3
+ interface Options {
4
+ borderWidth: number;
5
+ borderHeight: number;
6
+ borderColor: RGBA;
7
+ cornerRadius: number;
8
+ imageWidth: number;
9
+ imageHeight: number;
10
+ finalizePipeline?: boolean;
11
+ }
12
+ export declare const handleImageEdges: (image: sharp.Sharp, { borderWidth, borderHeight, borderColor, imageWidth, imageHeight, cornerRadius, finalizePipeline }: Options) => Promise<sharp.Sharp>;
13
+ export {};
@@ -0,0 +1,39 @@
1
+ import sharp from 'sharp';
2
+ import { rgbaToHex } from '../colors/rgbaToHex.js';
3
+ export const handleImageEdges = async (image, { borderWidth, borderHeight, borderColor, imageWidth, imageHeight, cornerRadius = 0, finalizePipeline = false }) => {
4
+ // Only change the image's edges if needed
5
+ if (borderWidth <= 0 && borderHeight <= 0 && cornerRadius <= 0)
6
+ return image;
7
+ const maxBorderX = imageWidth / 2;
8
+ const maxBorderY = imageHeight / 2;
9
+ const effectiveBorderWidth = Math.min(borderWidth, maxBorderX);
10
+ const effectiveBorderHeight = Math.min(borderHeight, maxBorderY);
11
+ // Step 1 — Round corners mask
12
+ const mask = `
13
+ <svg width="${imageWidth}" height="${imageHeight}">
14
+ <rect x="0" y="0" width="${imageWidth}" height="${imageHeight}" rx="${cornerRadius}" ry="${cornerRadius}" fill="white"/>
15
+ </svg>
16
+ `;
17
+ // Step 2 — Optional border layer (draw inside the image)
18
+ const border = `
19
+ <svg width="${imageWidth}" height="${imageHeight}">
20
+ <rect x="${effectiveBorderWidth / 2}" y="${effectiveBorderHeight / 2}" width="${imageWidth - effectiveBorderWidth}" height="${imageHeight - effectiveBorderHeight}" rx="${cornerRadius}" ry="${cornerRadius}"
21
+ fill="none" stroke="${rgbaToHex(borderColor)}" stroke-width="${Math.max(effectiveBorderWidth, effectiveBorderHeight)}"/>
22
+ </svg>
23
+ `;
24
+ // Only pick needed composites
25
+ const composites = [];
26
+ if (cornerRadius > 0) {
27
+ composites.push({ input: Buffer.from(mask), blend: 'dest-in', top: 0, left: 0 });
28
+ }
29
+ if (effectiveBorderWidth > 0 || effectiveBorderHeight > 0) {
30
+ composites.push({ input: Buffer.from(border), blend: 'over', top: 0, left: 0 });
31
+ }
32
+ // Step 3 — Composite mask and border
33
+ const processed = composites.length > 0 ? image.toFormat('png').composite(composites) : image;
34
+ // Finalize image if needed
35
+ if (finalizePipeline) {
36
+ return sharp(await processed.toBuffer());
37
+ }
38
+ return processed;
39
+ };
@@ -0,0 +1,5 @@
1
+ import sharp from 'sharp';
2
+ export declare const isActualImage: (input: sharp.SharpInput) => Promise<{
3
+ isImage: boolean;
4
+ reason: string;
5
+ }>;
@@ -0,0 +1,26 @@
1
+ import sharp from 'sharp';
2
+ export const isActualImage = async (input) => {
3
+ try {
4
+ // Try to get metadata
5
+ const metadata = await sharp(input).metadata();
6
+ // Try to get width and height
7
+ /* v8 ignore start */
8
+ if (!metadata.width || !metadata.height) {
9
+ return {
10
+ isImage: false,
11
+ reason: 'Image metadata missing width or height',
12
+ };
13
+ }
14
+ /* v8 ignore stop */
15
+ }
16
+ catch (err) {
17
+ return {
18
+ isImage: false,
19
+ reason: err.message,
20
+ };
21
+ }
22
+ return {
23
+ isImage: true,
24
+ reason: '',
25
+ };
26
+ };
@@ -0,0 +1 @@
1
+ export declare const parseAspectRatio: (aspectRatio: string) => number | false;
@@ -0,0 +1,22 @@
1
+ export const parseAspectRatio = (aspectRatio) => {
2
+ // return ratio straight away if its just a number
3
+ const ratio = Number(aspectRatio);
4
+ if (ratio) {
5
+ return ratio;
6
+ }
7
+ const ratioRegex = /^\s*(\d+)\s*(\/|:|x)\s*(\d+)\s*$/i;
8
+ const match = aspectRatio.match(ratioRegex);
9
+ // Ensure match exists
10
+ if (!match)
11
+ return false;
12
+ // Ensures absolute type safety
13
+ const [, wStr, , hStr] = match;
14
+ if (!wStr || !hStr)
15
+ return false;
16
+ // Return aspect ratio
17
+ const width = parseInt(wStr, 10);
18
+ const height = parseInt(hStr, 10);
19
+ if (width === 0 || height === 0)
20
+ return false;
21
+ return width / height;
22
+ };
@@ -0,0 +1,9 @@
1
+ import sharp from 'sharp';
2
+ interface RoundImageOptions {
3
+ width: number;
4
+ height: number;
5
+ cornerRadius: number;
6
+ finalizePipeline?: boolean;
7
+ }
8
+ export declare const roundImage: (image: sharp.Sharp, { width, height, cornerRadius, finalizePipeline }: RoundImageOptions) => Promise<sharp.Sharp>;
9
+ export {};
@@ -0,0 +1,27 @@
1
+ import sharp from 'sharp';
2
+ export const roundImage = async (image, { width, height, cornerRadius, finalizePipeline = false }) => {
3
+ // Skip if the cornerRadius = zero
4
+ if (!cornerRadius)
5
+ return image;
6
+ // Create rounded svg mask
7
+ const mask = Buffer.from(`
8
+ <svg width="${width}" height="${height}">
9
+ <rect x="0" y="0" width="${width}" height="${height}" rx="${cornerRadius}" ry="${cornerRadius}" />
10
+ </svg>
11
+ `);
12
+ // Apply mask
13
+ const roundedImage = image
14
+ .composite([
15
+ {
16
+ input: mask,
17
+ blend: 'dest-in',
18
+ },
19
+ ])
20
+ .toFormat('png');
21
+ // Finalize pipeline if needed
22
+ if (finalizePipeline) {
23
+ const buffer = await roundedImage.toBuffer();
24
+ return sharp(buffer);
25
+ }
26
+ return roundedImage;
27
+ };
@@ -0,0 +1,8 @@
1
+ import sharp from 'sharp';
2
+ interface RoundImagesOptions {
3
+ width: number;
4
+ height: number;
5
+ cornerRadius: number;
6
+ }
7
+ export declare const roundImages: (images: sharp.Sharp[], { width, height, cornerRadius }: RoundImagesOptions) => Promise<sharp.Sharp[]>;
8
+ export {};
@@ -0,0 +1,19 @@
1
+ import sharp from 'sharp';
2
+ export const roundImages = async (images, { width, height, cornerRadius }) => {
3
+ // Skip if the cornerRadius = zero
4
+ if (!cornerRadius)
5
+ return images;
6
+ // Round images respectively
7
+ return await Promise.all(images.map(async (image) => {
8
+ const mask = Buffer.from(`
9
+ <svg width="${width}" height="${height}">
10
+ <rect x="0" y="0" width="${width}" height="${height}" rx="${cornerRadius}" ry="${cornerRadius}" />
11
+ </svg>
12
+ `);
13
+ const buff = await image
14
+ .composite([{ input: mask, blend: 'dest-in' }])
15
+ .png()
16
+ .toBuffer();
17
+ return sharp(buff);
18
+ }));
19
+ };
@@ -0,0 +1,8 @@
1
+ import sharp from 'sharp';
2
+ interface ScaleImageOptions {
3
+ width?: number;
4
+ height?: number;
5
+ finalizePipeline?: boolean;
6
+ }
7
+ export declare const scaleImage: (image: sharp.Sharp, { width, height, finalizePipeline }: ScaleImageOptions) => Promise<sharp.Sharp>;
8
+ export {};