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.
- package/README.md +341 -88
- package/dist/cli/commands/collage/index.d.ts +2 -0
- package/dist/cli/commands/collage/index.js +125 -0
- package/dist/cli/commands/grid/index.d.ts +2 -0
- package/dist/cli/commands/grid/index.js +127 -0
- package/dist/cli/commands/masonry/index.d.ts +2 -0
- package/dist/cli/commands/masonry/index.js +129 -0
- package/dist/cli/commands/template/index.d.ts +2 -0
- package/dist/cli/commands/template/index.js +123 -0
- package/dist/cli/commands/template/presets/artGallery.d.ts +15 -0
- package/dist/cli/commands/template/presets/artGallery.js +15 -0
- package/dist/cli/commands/template/presets/dashboardShot.d.ts +15 -0
- package/dist/cli/commands/template/presets/dashboardShot.js +16 -0
- package/dist/cli/commands/template/presets/horizontalBookSpread.d.ts +15 -0
- package/dist/cli/commands/template/presets/horizontalBookSpread.js +13 -0
- package/dist/cli/commands/template/presets/instagramGrid.d.ts +15 -0
- package/dist/cli/commands/template/presets/instagramGrid.js +16 -0
- package/dist/cli/commands/template/presets/verticalBookSpread.d.ts +15 -0
- package/dist/cli/commands/template/presets/verticalBookSpread.js +13 -0
- package/dist/cli/commands/template/presets.d.ts +73 -0
- package/{lib/merges/collage-merge → dist/cli/commands/template}/presets.js +6 -8
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +24 -0
- package/dist/cli/modules/loadImages.d.ts +15 -0
- package/dist/cli/modules/loadImages.js +74 -0
- package/dist/cli/modules/progressBar.d.ts +10 -0
- package/dist/cli/modules/progressBar.js +34 -0
- package/dist/cli/schemas/collage.d.ts +29 -0
- package/dist/cli/schemas/collage.js +38 -0
- package/dist/cli/schemas/grid.d.ts +34 -0
- package/dist/cli/schemas/grid.js +38 -0
- package/dist/cli/schemas/masonry.d.ts +62 -0
- package/dist/cli/schemas/masonry.js +62 -0
- package/dist/cli/schemas/template.d.ts +31 -0
- package/dist/cli/schemas/template.js +49 -0
- package/dist/cli/utils/buildCommandFromSchema.d.ts +8 -0
- package/dist/cli/utils/buildCommandFromSchema.js +55 -0
- package/dist/cli/utils/configureCommandErrors.d.ts +2 -0
- package/dist/cli/utils/configureCommandErrors.js +22 -0
- package/dist/cli/utils/stringFormatter.d.ts +1 -0
- package/dist/cli/utils/stringFormatter.js +3 -0
- package/dist/cli/utils/toErrorMessage.d.ts +4 -0
- package/dist/cli/utils/toErrorMessage.js +22 -0
- package/dist/core/helpers.d.ts +10 -0
- package/dist/core/helpers.js +42 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/mergeError.d.ts +9 -0
- package/dist/core/mergeError.js +10 -0
- package/dist/core/merges/collage/index.d.ts +9 -0
- package/dist/core/merges/collage/index.js +32 -0
- package/dist/core/merges/collage/steps/calculateImageDimensions.d.ts +12 -0
- package/dist/core/merges/collage/steps/calculateImageDimensions.js +18 -0
- package/dist/core/merges/collage/steps/createComposites.d.ts +8 -0
- package/dist/core/merges/collage/steps/createComposites.js +58 -0
- package/dist/core/merges/collage/steps/resizeAndBorderImages.d.ts +12 -0
- package/dist/core/merges/collage/steps/resizeAndBorderImages.js +26 -0
- package/dist/core/merges/collage/steps/rotateImages.d.ts +7 -0
- package/dist/core/merges/collage/steps/rotateImages.js +9 -0
- package/dist/core/merges/grid/index.d.ts +12 -0
- package/dist/core/merges/grid/index.js +36 -0
- package/dist/core/merges/grid/steps/calculateCanvasDimensions.d.ts +8 -0
- package/dist/core/merges/grid/steps/calculateCanvasDimensions.js +18 -0
- package/dist/core/merges/grid/steps/calculateFontSize.d.ts +7 -0
- package/dist/core/merges/grid/steps/calculateFontSize.js +19 -0
- package/dist/core/merges/grid/steps/calculateImageDimensions.d.ts +8 -0
- package/dist/core/merges/grid/steps/calculateImageDimensions.js +18 -0
- package/dist/core/merges/grid/steps/createComposites.d.ts +10 -0
- package/dist/core/merges/grid/steps/createComposites.js +63 -0
- package/dist/core/merges/grid/steps/prepareImages.d.ts +10 -0
- package/dist/core/merges/grid/steps/prepareImages.js +29 -0
- package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.d.ts +7 -0
- package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.js +17 -0
- package/dist/core/merges/index.d.ts +3 -0
- package/dist/core/merges/index.js +3 -0
- package/dist/core/merges/masonry/index.d.ts +10 -0
- package/dist/core/merges/masonry/index.js +32 -0
- package/dist/core/merges/masonry/steps/calculateCanvasDimensions.d.ts +15 -0
- package/dist/core/merges/masonry/steps/calculateCanvasDimensions.js +17 -0
- package/dist/core/merges/masonry/steps/calculateLaneSize.d.ts +9 -0
- package/dist/core/merges/masonry/steps/calculateLaneSize.js +27 -0
- package/dist/core/merges/masonry/steps/createComposites.d.ts +25 -0
- package/dist/core/merges/masonry/steps/createComposites.js +108 -0
- package/dist/core/merges/masonry/steps/resizeImages.d.ts +7 -0
- package/dist/core/merges/masonry/steps/resizeImages.js +14 -0
- package/dist/core/merges/masonry/steps/splitIntoLanes.d.ts +17 -0
- package/dist/core/merges/masonry/steps/splitIntoLanes.js +78 -0
- package/dist/core/merges/shared-steps/applyComposites.d.ts +2 -0
- package/dist/core/merges/shared-steps/applyComposites.js +16 -0
- package/dist/core/merges/shared-steps/createCanvas.d.ts +11 -0
- package/dist/core/merges/shared-steps/createCanvas.js +17 -0
- package/dist/core/merges/shared-steps/exportCanvas.d.ts +7 -0
- package/dist/core/merges/shared-steps/exportCanvas.js +25 -0
- package/dist/core/merges/shared-steps/finalizeImagePipelines.d.ts +2 -0
- package/dist/core/merges/shared-steps/finalizeImagePipelines.js +9 -0
- package/dist/core/merges/shared-steps/loadImages.d.ts +2 -0
- package/dist/core/merges/shared-steps/loadImages.js +26 -0
- package/dist/core/merges/shared-steps/validateCaptions.d.ts +10 -0
- package/dist/core/merges/shared-steps/validateCaptions.js +17 -0
- package/dist/core/merges/template/index.d.ts +10 -0
- package/dist/core/merges/template/index.js +28 -0
- package/dist/core/merges/template/steps/calculateSlotDimensions.d.ts +9 -0
- package/dist/core/merges/template/steps/calculateSlotDimensions.js +12 -0
- package/dist/core/merges/template/steps/createComposites.d.ts +10 -0
- package/dist/core/merges/template/steps/createComposites.js +25 -0
- package/dist/core/merges/template/steps/getBlocks.d.ts +13 -0
- package/dist/core/merges/template/steps/getBlocks.js +28 -0
- package/dist/core/merges/template/types.d.ts +21 -0
- package/dist/core/merges/template/types.js +1 -0
- package/dist/core/merges/types.d.ts +102 -0
- package/dist/core/merges/types.js +1 -0
- package/dist/core/modules/messages.d.ts +32 -0
- package/dist/core/modules/messages.js +54 -0
- package/dist/core/pipeline/guards.d.ts +4 -0
- package/dist/core/pipeline/guards.js +23 -0
- package/dist/core/pipeline/mergePipeline.d.ts +60 -0
- package/dist/core/pipeline/mergePipeline.js +122 -0
- package/dist/core/schemas/collage.d.ts +26 -0
- package/dist/core/schemas/collage.js +17 -0
- package/dist/core/schemas/grid.d.ts +32 -0
- package/dist/core/schemas/grid.js +29 -0
- package/dist/core/schemas/masonry.d.ts +56 -0
- package/dist/core/schemas/masonry.js +43 -0
- package/dist/core/schemas/template.d.ts +34 -0
- package/dist/core/schemas/template.js +88 -0
- package/dist/core/utils/colors/hexToRgba.d.ts +2 -0
- package/dist/core/utils/colors/hexToRgba.js +25 -0
- package/dist/core/utils/colors/rgbaToHex.d.ts +2 -0
- package/dist/core/utils/colors/rgbaToHex.js +15 -0
- package/dist/core/utils/colors/types.d.ts +7 -0
- package/dist/core/utils/colors/types.js +1 -0
- package/dist/core/utils/fonts/getFontSize.d.ts +9 -0
- package/dist/core/utils/fonts/getFontSize.js +40 -0
- package/dist/core/utils/images/addImageBorder.d.ts +13 -0
- package/dist/core/utils/images/addImageBorder.js +33 -0
- package/dist/core/utils/images/getImageHeights.d.ts +2 -0
- package/dist/core/utils/images/getImageHeights.js +9 -0
- package/dist/core/utils/images/getImageWidths.d.ts +2 -0
- package/dist/core/utils/images/getImageWidths.js +9 -0
- package/dist/core/utils/images/getSmallestImageDimensions.d.ts +5 -0
- package/dist/core/utils/images/getSmallestImageDimensions.js +9 -0
- package/dist/core/utils/images/handleImageEdges.d.ts +13 -0
- package/dist/core/utils/images/handleImageEdges.js +39 -0
- package/dist/core/utils/images/isActualImage.d.ts +5 -0
- package/dist/core/utils/images/isActualImage.js +26 -0
- package/dist/core/utils/images/parseAspectRatio.d.ts +1 -0
- package/dist/core/utils/images/parseAspectRatio.js +22 -0
- package/dist/core/utils/images/roundImage.d.ts +9 -0
- package/dist/core/utils/images/roundImage.js +27 -0
- package/dist/core/utils/images/roundImages.d.ts +8 -0
- package/dist/core/utils/images/roundImages.js +19 -0
- package/dist/core/utils/images/scaleImage.d.ts +8 -0
- package/dist/core/utils/images/scaleImage.js +36 -0
- package/dist/core/utils/images/scaleImages.d.ts +8 -0
- package/dist/core/utils/images/scaleImages.js +38 -0
- package/dist/core/utils/math/median.d.ts +1 -0
- package/dist/core/utils/math/median.js +12 -0
- package/dist/core/utils/math/randint.d.ts +6 -0
- package/dist/core/utils/math/randint.js +11 -0
- package/dist/core/utils/math/trimmedMedian.d.ts +1 -0
- package/dist/core/utils/math/trimmedMedian.js +12 -0
- package/dist/core/utils/svg/createSvgTextBuffer.d.ts +9 -0
- package/dist/core/utils/svg/createSvgTextBuffer.js +21 -0
- package/dist/validators/aspectRatio.d.ts +2 -0
- package/dist/validators/aspectRatio.js +15 -0
- package/dist/validators/coercion.d.ts +2 -0
- package/dist/validators/coercion.js +12 -0
- package/dist/validators/format.d.ts +2 -0
- package/dist/validators/format.js +15 -0
- package/dist/validators/hexColor.d.ts +7 -0
- package/dist/validators/hexColor.js +27 -0
- package/dist/validators/index.d.ts +95 -0
- package/dist/validators/index.js +64 -0
- package/dist/validators/outputFile.d.ts +2 -0
- package/dist/validators/outputFile.js +7 -0
- package/dist/validators/path.d.ts +3 -0
- package/dist/validators/path.js +18 -0
- package/dist/validators/sharpImageInput.d.ts +3 -0
- package/dist/validators/sharpImageInput.js +6 -0
- package/dist/validators/template.d.ts +15 -0
- package/dist/validators/template.js +41 -0
- package/package.json +26 -9
- package/bin/pixeli.js +0 -26
- package/commands/merge/collage.js +0 -83
- package/commands/merge/grid.js +0 -71
- package/commands/merge/helpers/utils.js +0 -11
- package/commands/merge/helpers/validations.js +0 -269
- package/commands/merge/index.js +0 -12
- package/commands/merge/masonry.js +0 -72
- package/lib/helpers/loadImages.js +0 -94
- package/lib/helpers/progressBar.js +0 -20
- package/lib/helpers/templateValidator.js +0 -139
- package/lib/helpers/utils.js +0 -208
- package/lib/merges/collage-merge/index.js +0 -110
- package/lib/merges/collage-merge/presets/artGallery.js +0 -17
- package/lib/merges/collage-merge/presets/dashboardShot.js +0 -18
- package/lib/merges/collage-merge/presets/horizontalBookSpread.js +0 -15
- package/lib/merges/collage-merge/presets/instagramGrid.js +0 -18
- package/lib/merges/collage-merge/presets/verticalBookSpread.js +0 -15
- package/lib/merges/grid-merge/index.js +0 -152
- package/lib/merges/masonry-merge/horizontal.js +0 -157
- package/lib/merges/masonry-merge/index.js +0 -57
- package/lib/merges/masonry-merge/vertical.js +0 -152
- 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,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,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 @@
|
|
|
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,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,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 {};
|