pixeli 0.1.9 → 1.0.4

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 (212) hide show
  1. package/README.md +362 -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 +2 -0
  47. package/dist/core/index.js +2 -0
  48. package/dist/core/jobs/batchRunner.d.ts +44 -0
  49. package/dist/core/jobs/batchRunner.js +90 -0
  50. package/dist/core/jobs/types.d.ts +10 -0
  51. package/dist/core/jobs/types.js +1 -0
  52. package/dist/core/mergeError.d.ts +9 -0
  53. package/dist/core/mergeError.js +10 -0
  54. package/dist/core/merges/collage/index.d.ts +9 -0
  55. package/dist/core/merges/collage/index.js +32 -0
  56. package/dist/core/merges/collage/steps/calculateImageDimensions.d.ts +12 -0
  57. package/dist/core/merges/collage/steps/calculateImageDimensions.js +18 -0
  58. package/dist/core/merges/collage/steps/createComposites.d.ts +8 -0
  59. package/dist/core/merges/collage/steps/createComposites.js +58 -0
  60. package/dist/core/merges/collage/steps/resizeAndBorderImages.d.ts +12 -0
  61. package/dist/core/merges/collage/steps/resizeAndBorderImages.js +26 -0
  62. package/dist/core/merges/collage/steps/rotateImages.d.ts +7 -0
  63. package/dist/core/merges/collage/steps/rotateImages.js +9 -0
  64. package/dist/core/merges/grid/index.d.ts +12 -0
  65. package/dist/core/merges/grid/index.js +36 -0
  66. package/dist/core/merges/grid/steps/calculateCanvasDimensions.d.ts +8 -0
  67. package/dist/core/merges/grid/steps/calculateCanvasDimensions.js +18 -0
  68. package/dist/core/merges/grid/steps/calculateFontSize.d.ts +7 -0
  69. package/dist/core/merges/grid/steps/calculateFontSize.js +19 -0
  70. package/dist/core/merges/grid/steps/calculateImageDimensions.d.ts +8 -0
  71. package/dist/core/merges/grid/steps/calculateImageDimensions.js +18 -0
  72. package/dist/core/merges/grid/steps/createComposites.d.ts +10 -0
  73. package/dist/core/merges/grid/steps/createComposites.js +63 -0
  74. package/dist/core/merges/grid/steps/prepareImages.d.ts +10 -0
  75. package/dist/core/merges/grid/steps/prepareImages.js +29 -0
  76. package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.d.ts +7 -0
  77. package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.js +17 -0
  78. package/dist/core/merges/index.d.ts +5 -0
  79. package/dist/core/merges/index.js +4 -0
  80. package/dist/core/merges/masonry/index.d.ts +10 -0
  81. package/dist/core/merges/masonry/index.js +32 -0
  82. package/dist/core/merges/masonry/steps/calculateCanvasDimensions.d.ts +15 -0
  83. package/dist/core/merges/masonry/steps/calculateCanvasDimensions.js +17 -0
  84. package/dist/core/merges/masonry/steps/calculateLaneSize.d.ts +9 -0
  85. package/dist/core/merges/masonry/steps/calculateLaneSize.js +27 -0
  86. package/dist/core/merges/masonry/steps/createComposites.d.ts +25 -0
  87. package/dist/core/merges/masonry/steps/createComposites.js +108 -0
  88. package/dist/core/merges/masonry/steps/resizeImages.d.ts +7 -0
  89. package/dist/core/merges/masonry/steps/resizeImages.js +14 -0
  90. package/dist/core/merges/masonry/steps/splitIntoLanes.d.ts +17 -0
  91. package/dist/core/merges/masonry/steps/splitIntoLanes.js +78 -0
  92. package/dist/core/merges/shared-steps/applyComposites.d.ts +2 -0
  93. package/dist/core/merges/shared-steps/applyComposites.js +16 -0
  94. package/dist/core/merges/shared-steps/createCanvas.d.ts +11 -0
  95. package/dist/core/merges/shared-steps/createCanvas.js +17 -0
  96. package/dist/core/merges/shared-steps/exportCanvas.d.ts +7 -0
  97. package/dist/core/merges/shared-steps/exportCanvas.js +25 -0
  98. package/dist/core/merges/shared-steps/finalizeImagePipelines.d.ts +2 -0
  99. package/dist/core/merges/shared-steps/finalizeImagePipelines.js +9 -0
  100. package/dist/core/merges/shared-steps/loadImages.d.ts +2 -0
  101. package/dist/core/merges/shared-steps/loadImages.js +26 -0
  102. package/dist/core/merges/shared-steps/validateCaptions.d.ts +10 -0
  103. package/dist/core/merges/shared-steps/validateCaptions.js +17 -0
  104. package/dist/core/merges/template/index.d.ts +10 -0
  105. package/dist/core/merges/template/index.js +28 -0
  106. package/dist/core/merges/template/steps/calculateSlotDimensions.d.ts +9 -0
  107. package/dist/core/merges/template/steps/calculateSlotDimensions.js +12 -0
  108. package/dist/core/merges/template/steps/createComposites.d.ts +10 -0
  109. package/dist/core/merges/template/steps/createComposites.js +25 -0
  110. package/dist/core/merges/template/steps/getBlocks.d.ts +13 -0
  111. package/dist/core/merges/template/steps/getBlocks.js +28 -0
  112. package/dist/core/merges/template/types.d.ts +21 -0
  113. package/dist/core/merges/template/types.js +1 -0
  114. package/dist/core/merges/types.d.ts +123 -0
  115. package/dist/core/merges/types.js +1 -0
  116. package/dist/core/modules/messages.d.ts +32 -0
  117. package/dist/core/modules/messages.js +54 -0
  118. package/dist/core/modules/typedEventEmitter.d.ts +7 -0
  119. package/dist/core/modules/typedEventEmitter.js +9 -0
  120. package/dist/core/pipeline/guards.d.ts +4 -0
  121. package/dist/core/pipeline/guards.js +23 -0
  122. package/dist/core/pipeline/mergePipeline.d.ts +60 -0
  123. package/dist/core/pipeline/mergePipeline.js +122 -0
  124. package/dist/core/schemas/collage.d.ts +26 -0
  125. package/dist/core/schemas/collage.js +17 -0
  126. package/dist/core/schemas/grid.d.ts +32 -0
  127. package/dist/core/schemas/grid.js +29 -0
  128. package/dist/core/schemas/masonry.d.ts +56 -0
  129. package/dist/core/schemas/masonry.js +43 -0
  130. package/dist/core/schemas/mergeJob.d.ts +11 -0
  131. package/dist/core/schemas/mergeJob.js +6 -0
  132. package/dist/core/schemas/template.d.ts +34 -0
  133. package/dist/core/schemas/template.js +88 -0
  134. package/dist/core/utils/colors/hexToRgba.d.ts +2 -0
  135. package/dist/core/utils/colors/hexToRgba.js +25 -0
  136. package/dist/core/utils/colors/rgbaToHex.d.ts +2 -0
  137. package/dist/core/utils/colors/rgbaToHex.js +15 -0
  138. package/dist/core/utils/colors/types.d.ts +7 -0
  139. package/dist/core/utils/colors/types.js +1 -0
  140. package/dist/core/utils/fonts/getFontSize.d.ts +9 -0
  141. package/dist/core/utils/fonts/getFontSize.js +40 -0
  142. package/dist/core/utils/images/addImageBorder.d.ts +13 -0
  143. package/dist/core/utils/images/addImageBorder.js +33 -0
  144. package/dist/core/utils/images/getImageHeights.d.ts +2 -0
  145. package/dist/core/utils/images/getImageHeights.js +9 -0
  146. package/dist/core/utils/images/getImageWidths.d.ts +2 -0
  147. package/dist/core/utils/images/getImageWidths.js +9 -0
  148. package/dist/core/utils/images/getSmallestImageDimensions.d.ts +5 -0
  149. package/dist/core/utils/images/getSmallestImageDimensions.js +9 -0
  150. package/dist/core/utils/images/handleImageEdges.d.ts +13 -0
  151. package/dist/core/utils/images/handleImageEdges.js +39 -0
  152. package/dist/core/utils/images/isActualImage.d.ts +5 -0
  153. package/dist/core/utils/images/isActualImage.js +26 -0
  154. package/dist/core/utils/images/parseAspectRatio.d.ts +1 -0
  155. package/dist/core/utils/images/parseAspectRatio.js +22 -0
  156. package/dist/core/utils/images/roundImage.d.ts +9 -0
  157. package/dist/core/utils/images/roundImage.js +27 -0
  158. package/dist/core/utils/images/roundImages.d.ts +8 -0
  159. package/dist/core/utils/images/roundImages.js +19 -0
  160. package/dist/core/utils/images/scaleImage.d.ts +8 -0
  161. package/dist/core/utils/images/scaleImage.js +36 -0
  162. package/dist/core/utils/images/scaleImages.d.ts +8 -0
  163. package/dist/core/utils/images/scaleImages.js +38 -0
  164. package/dist/core/utils/math/median.d.ts +1 -0
  165. package/dist/core/utils/math/median.js +12 -0
  166. package/dist/core/utils/math/randint.d.ts +6 -0
  167. package/dist/core/utils/math/randint.js +11 -0
  168. package/dist/core/utils/math/trimmedMedian.d.ts +1 -0
  169. package/dist/core/utils/math/trimmedMedian.js +12 -0
  170. package/dist/core/utils/svg/createSvgTextBuffer.d.ts +9 -0
  171. package/dist/core/utils/svg/createSvgTextBuffer.js +21 -0
  172. package/dist/validators/aspectRatio.d.ts +2 -0
  173. package/dist/validators/aspectRatio.js +15 -0
  174. package/dist/validators/coercion.d.ts +2 -0
  175. package/dist/validators/coercion.js +12 -0
  176. package/dist/validators/format.d.ts +2 -0
  177. package/dist/validators/format.js +15 -0
  178. package/dist/validators/hexColor.d.ts +7 -0
  179. package/dist/validators/hexColor.js +27 -0
  180. package/dist/validators/index.d.ts +95 -0
  181. package/dist/validators/index.js +64 -0
  182. package/dist/validators/outputFile.d.ts +2 -0
  183. package/dist/validators/outputFile.js +7 -0
  184. package/dist/validators/path.d.ts +3 -0
  185. package/dist/validators/path.js +18 -0
  186. package/dist/validators/sharpImageInput.d.ts +3 -0
  187. package/dist/validators/sharpImageInput.js +6 -0
  188. package/dist/validators/template.d.ts +15 -0
  189. package/dist/validators/template.js +41 -0
  190. package/package.json +26 -9
  191. package/bin/pixeli.js +0 -26
  192. package/commands/merge/collage.js +0 -83
  193. package/commands/merge/grid.js +0 -71
  194. package/commands/merge/helpers/utils.js +0 -11
  195. package/commands/merge/helpers/validations.js +0 -269
  196. package/commands/merge/index.js +0 -12
  197. package/commands/merge/masonry.js +0 -72
  198. package/lib/helpers/loadImages.js +0 -94
  199. package/lib/helpers/progressBar.js +0 -20
  200. package/lib/helpers/templateValidator.js +0 -139
  201. package/lib/helpers/utils.js +0 -208
  202. package/lib/merges/collage-merge/index.js +0 -110
  203. package/lib/merges/collage-merge/presets/artGallery.js +0 -17
  204. package/lib/merges/collage-merge/presets/dashboardShot.js +0 -18
  205. package/lib/merges/collage-merge/presets/horizontalBookSpread.js +0 -15
  206. package/lib/merges/collage-merge/presets/instagramGrid.js +0 -18
  207. package/lib/merges/collage-merge/presets/verticalBookSpread.js +0 -15
  208. package/lib/merges/grid-merge/index.js +0 -152
  209. package/lib/merges/masonry-merge/horizontal.js +0 -157
  210. package/lib/merges/masonry-merge/index.js +0 -57
  211. package/lib/merges/masonry-merge/vertical.js +0 -152
  212. package/lib/merges/merge-utils.js +0 -176
@@ -0,0 +1,122 @@
1
+ import sharp from 'sharp';
2
+ import { MergeError } from '../mergeError.js';
3
+ import { MESSAGES } from '../modules/messages.js';
4
+ import chalk from 'chalk';
5
+ /**
6
+ * Pipeline orchestrator for running a sequence of merge steps that
7
+ * progressively build or modify a final merged image.
8
+ *
9
+ * Generic type parameters:
10
+ * - TOptions: the shape of the validated options object passed to each step.
11
+ * - TState: the shape of the pipeline's runtime state held in MergeContext.
12
+ *
13
+ * The pipeline:
14
+ * - Validates incoming options via a Zod schema (see createPipeline).
15
+ * - Maintains an ordered list of MergeStep functions to execute.
16
+ * - Provides optional progress reporting via an OnProgress callback.
17
+ *
18
+ * Usage:
19
+ * - Create an instance via createPipeline to ensure options are validated and
20
+ * progressInfo is initialized on the supplied MergeContext.
21
+ * - Register steps with use(...).
22
+ * - Execute with run() — in normal usage this returns a Buffer containing the
23
+ * final image. When run in "test" mode (testOptions supplied) the pipeline
24
+ * may perform a dry-run and return void.
25
+ *
26
+ * Errors:
27
+ * - Validation-related errors thrown from createPipeline are surfaced as
28
+ * MergeError instances with type 'validation' or 'internal'.
29
+ * - During execution, any thrown non-MergeError is converted into a runtime
30
+ * Error indicating the offending step name.
31
+ *
32
+ * @template TOptions - Validated options shape used by registered steps.
33
+ * @template TState - Context/state shape managed by the pipeline.
34
+ */
35
+ export class MergePipeline {
36
+ options;
37
+ context;
38
+ onProgress;
39
+ steps = [];
40
+ optionsCounter = 0;
41
+ stateCounter = 0;
42
+ constructor(options, context, onProgress) {
43
+ this.options = options;
44
+ this.context = context;
45
+ this.onProgress = onProgress;
46
+ }
47
+ static async createPipeline(schema, options, context, onProgress) {
48
+ const { success, data, error } = await schema.safeParseAsync(options);
49
+ if (success) {
50
+ // Initialize progressInfo
51
+ const progressInfo = {
52
+ completed: 0,
53
+ total: context.inputs.length,
54
+ phase: 'Initializing',
55
+ };
56
+ // Initialize mergeContext
57
+ const mergeContext = {
58
+ ...context,
59
+ progressInfo,
60
+ };
61
+ // Return the pipeline
62
+ return new MergePipeline(data, mergeContext, onProgress);
63
+ }
64
+ else {
65
+ const path = error.issues[0]?.path;
66
+ const err = error.issues[0]?.message;
67
+ if (!path || !err) {
68
+ throw new MergeError(MESSAGES.ERROR.INTERNAL.message, { type: 'internal', cause: error });
69
+ }
70
+ const errorText = path.length > 0 ? `Invalid value at ${path.join('/')}: ${err}` : `Error: ${err}`;
71
+ throw new MergeError(errorText, { type: 'validation' });
72
+ }
73
+ }
74
+ use(step) {
75
+ this.steps.push(step);
76
+ return this;
77
+ }
78
+ async run(testOptions) {
79
+ let finalImage;
80
+ if (testOptions)
81
+ this.logForDebugging(testOptions);
82
+ for (const step of this.steps) {
83
+ // Use a copy of this.options to ensure that merge steps cannot mutate the original
84
+ const result = await step(this.context, { ...this.options }, this.onProgress);
85
+ if (testOptions)
86
+ this.logForDebugging(testOptions);
87
+ // If the result is a sharp instance, update finalImage
88
+ if (result instanceof Buffer) {
89
+ finalImage = result;
90
+ }
91
+ }
92
+ // If finalImage has a value, return it
93
+ if (finalImage) {
94
+ return finalImage;
95
+ }
96
+ // During a dry run, void returns are allowed (for debugging)
97
+ if (testOptions) {
98
+ return;
99
+ }
100
+ // Normal runs must have a final sharp image produced.
101
+ throw new Error('No merge step produced a final Sharp image.');
102
+ }
103
+ logForDebugging(testOptions) {
104
+ if (testOptions.logOptions) {
105
+ // Log
106
+ console.log(chalk.bgBlueBright(` OPTIONS ${this.optionsCounter++}: `));
107
+ console.log(this.options);
108
+ }
109
+ // Whitespace
110
+ if (testOptions.logOptions && testOptions.logContext)
111
+ console.log();
112
+ if (testOptions.logContext) {
113
+ // Replace images and inputs with their lengths to make context readable
114
+ const { images, inputs, ...trimmedContext } = this.context;
115
+ const modifiedContext = { totalImages: images.length, totalInputs: inputs.length, ...trimmedContext };
116
+ // Log
117
+ console.log(chalk.bgGreen(` CONTEXT ${this.stateCounter++}: `));
118
+ console.log(modifiedContext);
119
+ }
120
+ console.log();
121
+ }
122
+ }
@@ -0,0 +1,26 @@
1
+ import z from 'zod';
2
+ export declare const collageSchema: 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
+ borderWidth: z.ZodDefault<z.ZodNumber>;
13
+ borderColor: z.ZodPrefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<import("../utils/colors/types.js").RGBA, string>>, z.ZodObject<{
14
+ r: z.ZodNumber;
15
+ g: z.ZodNumber;
16
+ b: z.ZodNumber;
17
+ alpha: z.ZodNumber;
18
+ }, z.z.core.$strip>]>>;
19
+ format: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<"webp" | "gif" | "jpeg" | "jpg" | "png" | "tiff" | "avif", string>>>;
20
+ aspectRatio: z.ZodPrefault<z.ZodPipe<z.z.ZodCoercedString<unknown>, z.ZodTransform<number, string>>>;
21
+ imageWidth: z.ZodOptional<z.ZodNumber>;
22
+ columns: z.ZodDefault<z.ZodNumber>;
23
+ overlapPercentage: z.ZodDefault<z.ZodNumber>;
24
+ rotationRange: z.ZodDefault<z.ZodNumber>;
25
+ imageWidthVariance: z.ZodDefault<z.ZodNumber>;
26
+ }, z.z.core.$strict>;
@@ -0,0 +1,17 @@
1
+ import z from 'zod';
2
+ import { VALIDATORS } from '../../validators/index.js';
3
+ export const collageSchema = z.strictObject({
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
+ borderWidth: VALIDATORS.imageWidthVariance.default(20),
9
+ borderColor: VALIDATORS.borderColor.prefault('#000'),
10
+ format: VALIDATORS.format.default('png'),
11
+ aspectRatio: VALIDATORS.aspectRatio.prefault('1:1'),
12
+ imageWidth: VALIDATORS.imageWidth.optional(),
13
+ columns: VALIDATORS.columns.default(4),
14
+ overlapPercentage: VALIDATORS.overlapPercentage.default(25),
15
+ rotationRange: VALIDATORS.rotationRange.default(7),
16
+ imageWidthVariance: VALIDATORS.imageWidthVariance.default(10),
17
+ });
@@ -0,0 +1,32 @@
1
+ import z from 'zod';
2
+ export declare const gridSchema: 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
+ borderWidth: z.ZodDefault<z.ZodNumber>;
13
+ borderColor: z.ZodPrefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<import("../utils/colors/types.js").RGBA, string>>, z.ZodObject<{
14
+ r: z.ZodNumber;
15
+ g: z.ZodNumber;
16
+ b: z.ZodNumber;
17
+ alpha: z.ZodNumber;
18
+ }, z.z.core.$strip>]>>;
19
+ format: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<"webp" | "gif" | "jpeg" | "jpg" | "png" | "tiff" | "avif", string>>>;
20
+ aspectRatio: z.ZodPrefault<z.ZodPipe<z.z.ZodCoercedString<unknown>, z.ZodTransform<number, string>>>;
21
+ imageWidth: z.ZodOptional<z.ZodNumber>;
22
+ columns: z.ZodDefault<z.ZodNumber>;
23
+ caption: z.ZodDefault<z.ZodBoolean>;
24
+ captions: z.ZodOptional<z.ZodArray<z.ZodString>>;
25
+ captionColor: z.ZodPrefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<import("../utils/colors/types.js").RGBA, string>>, z.ZodObject<{
26
+ r: z.ZodNumber;
27
+ g: z.ZodNumber;
28
+ b: z.ZodNumber;
29
+ alpha: z.ZodNumber;
30
+ }, z.z.core.$strip>]>>;
31
+ maxCaptionSize: z.ZodDefault<z.ZodNumber>;
32
+ }, z.z.core.$strict>;
@@ -0,0 +1,29 @@
1
+ import z from 'zod';
2
+ import { VALIDATORS } from '../../validators/index.js';
3
+ export const gridSchema = 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
+ borderWidth: VALIDATORS.borderWidth.default(0),
10
+ borderColor: VALIDATORS.borderColor.prefault('#000'),
11
+ format: VALIDATORS.format.default('png'),
12
+ aspectRatio: VALIDATORS.aspectRatio.prefault('1:1'),
13
+ imageWidth: VALIDATORS.imageWidth.optional(),
14
+ columns: VALIDATORS.columns.default(4),
15
+ caption: VALIDATORS.caption.default(false),
16
+ captions: VALIDATORS.captions.nonempty().optional(),
17
+ captionColor: VALIDATORS.captionColor.prefault('#000'),
18
+ maxCaptionSize: VALIDATORS.maxCaptionSize.default(100),
19
+ })
20
+ .superRefine((opts, ctx) => {
21
+ // Ensure captions are given if caption is set to true
22
+ if (opts.caption && (!opts.captions || opts.captions.length === 0)) {
23
+ ctx.addIssue({
24
+ code: 'custom',
25
+ message: 'Caption texts must be provided.',
26
+ path: ['captions'],
27
+ });
28
+ }
29
+ });
@@ -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,11 @@
1
+ import z from 'zod';
2
+ export declare const mergeJobSchema: z.ZodObject<{
3
+ type: z.ZodEnum<{
4
+ template: "template";
5
+ grid: "grid";
6
+ masonry: "masonry";
7
+ collage: "collage";
8
+ }>;
9
+ inputs: z.ZodArray<z.ZodString>;
10
+ options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
11
+ }, z.z.core.$strict>;
@@ -0,0 +1,6 @@
1
+ import z from 'zod';
2
+ export const mergeJobSchema = z.strictObject({
3
+ type: z.enum(['grid', 'masonry', 'collage', 'template']),
4
+ inputs: z.array(z.string()).min(1),
5
+ options: z.record(z.string(), z.any()).optional(),
6
+ });
@@ -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[]>;