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,9 @@
|
|
|
1
|
+
import type { MergeStep } from '../../../pipeline/mergePipeline.js';
|
|
2
|
+
import type { Template } from '../types.js';
|
|
3
|
+
import type { TemplateState } from '../index.js';
|
|
4
|
+
interface Options {
|
|
5
|
+
gap: number;
|
|
6
|
+
template: Template;
|
|
7
|
+
}
|
|
8
|
+
export declare const calculateCanvasDimensions: MergeStep<Options, TemplateState>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const calculateCanvasDimensions = async (context, options, _onProgress) => {
|
|
2
|
+
// Calculate each column's width and height
|
|
3
|
+
const workableCanvasWidth = options.template.canvas.width - options.gap * (options.template.canvas.columns + 1);
|
|
4
|
+
const workableCanvasHeight = options.template.canvas.height - options.gap * (options.template.canvas.rows + 1);
|
|
5
|
+
const slotWidth = workableCanvasWidth / options.template.canvas.columns;
|
|
6
|
+
const slotHeight = workableCanvasHeight / options.template.canvas.rows;
|
|
7
|
+
// Assign to state
|
|
8
|
+
context.state.canvasWidth = options.template.canvas.width;
|
|
9
|
+
context.state.canvasHeight = options.template.canvas.height;
|
|
10
|
+
context.state.slotWidth = slotWidth;
|
|
11
|
+
context.state.slotHeight = slotHeight;
|
|
12
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MergeStep } from '../../../pipeline/mergePipeline.js';
|
|
2
|
+
import type { Template } from '../types.js';
|
|
3
|
+
import type { TemplateState } from '../index.js';
|
|
4
|
+
interface Options {
|
|
5
|
+
template: Template;
|
|
6
|
+
gap: number;
|
|
7
|
+
cornerRadius: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const createComposites: MergeStep<Options, TemplateState>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { requireState } from '../../../pipeline/guards.js';
|
|
2
|
+
export const createComposites = async (context, options, onProgress) => {
|
|
3
|
+
// Require needed states
|
|
4
|
+
requireState(context, 'blocks');
|
|
5
|
+
requireState(context, 'slotWidth');
|
|
6
|
+
requireState(context, 'slotHeight');
|
|
7
|
+
// Collect composites
|
|
8
|
+
const composites = [];
|
|
9
|
+
for (const block of context.state.blocks) {
|
|
10
|
+
const x = (block.col - 1) * context.state.slotWidth + block.col * options.gap;
|
|
11
|
+
const y = (block.row - 1) * context.state.slotHeight + block.row * options.gap;
|
|
12
|
+
composites.push({
|
|
13
|
+
input: block.imageBuffer,
|
|
14
|
+
left: Math.floor(x),
|
|
15
|
+
top: Math.floor(y),
|
|
16
|
+
});
|
|
17
|
+
// Update progress
|
|
18
|
+
if (onProgress) {
|
|
19
|
+
context.progressInfo.phase = 'Merging images';
|
|
20
|
+
context.progressInfo.completed += 1;
|
|
21
|
+
onProgress({ ...context.progressInfo });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
context.composites = composites;
|
|
25
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MergeStep } from '../../../pipeline/mergePipeline.js';
|
|
2
|
+
import type { Template } from '../types.js';
|
|
3
|
+
import type { TemplateState } from '../index.js';
|
|
4
|
+
import type { RGBA } from '../../../utils/colors/types.js';
|
|
5
|
+
interface Options {
|
|
6
|
+
template: Template;
|
|
7
|
+
gap: number;
|
|
8
|
+
borderWidth: number;
|
|
9
|
+
borderColor: RGBA;
|
|
10
|
+
cornerRadius: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const getBlocks: MergeStep<Options, TemplateState>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { requireNonEmptyArray, requireState } from '../../../pipeline/guards.js';
|
|
2
|
+
import { handleImageEdges } from '../../../utils/images/handleImageEdges.js';
|
|
3
|
+
export const getBlocks = async (context, options, _onProgress) => {
|
|
4
|
+
// Require needed states
|
|
5
|
+
requireState(context, 'slotWidth');
|
|
6
|
+
requireState(context, 'slotHeight');
|
|
7
|
+
requireNonEmptyArray(context.images, 'images');
|
|
8
|
+
const blocks = [];
|
|
9
|
+
for (let i = 0; i < options.template.slots.length && i < context.images.length; i++) {
|
|
10
|
+
const slot = options.template.slots[i];
|
|
11
|
+
const image = context.images[i];
|
|
12
|
+
// Calculate image width and height
|
|
13
|
+
const width = Math.floor(slot.colSpan * context.state.slotWidth + (slot.colSpan - 1) * options.gap);
|
|
14
|
+
const height = Math.floor(slot.rowSpan * context.state.slotHeight + (slot.rowSpan - 1) * options.gap);
|
|
15
|
+
// Resize image
|
|
16
|
+
const resizedImage = image.resize({ width, height });
|
|
17
|
+
const borderedImage = await handleImageEdges(resizedImage, {
|
|
18
|
+
borderWidth: options.borderWidth,
|
|
19
|
+
borderHeight: options.borderWidth,
|
|
20
|
+
borderColor: options.borderColor,
|
|
21
|
+
cornerRadius: options.cornerRadius,
|
|
22
|
+
imageWidth: width,
|
|
23
|
+
imageHeight: height,
|
|
24
|
+
});
|
|
25
|
+
blocks.push({ imageBuffer: await borderedImage.toBuffer(), col: slot.col, row: slot.row });
|
|
26
|
+
}
|
|
27
|
+
context.state.blocks = blocks;
|
|
28
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface Canvas {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
columns: number;
|
|
5
|
+
rows: number;
|
|
6
|
+
}
|
|
7
|
+
export interface Slot {
|
|
8
|
+
col: number;
|
|
9
|
+
row: number;
|
|
10
|
+
colSpan: number;
|
|
11
|
+
rowSpan: number;
|
|
12
|
+
}
|
|
13
|
+
export interface Template {
|
|
14
|
+
canvas: Canvas;
|
|
15
|
+
slots: Slot[];
|
|
16
|
+
}
|
|
17
|
+
export interface Block {
|
|
18
|
+
imageBuffer: Buffer;
|
|
19
|
+
col: number;
|
|
20
|
+
row: number;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import sharp from 'sharp';
|
|
2
|
+
import type { Color } from '../utils/colors/types.js';
|
|
3
|
+
import type { Template } from './template/types.js';
|
|
4
|
+
export interface ProgressInfo {
|
|
5
|
+
/** How many images have been processed so far */
|
|
6
|
+
completed: number;
|
|
7
|
+
/** The total number of images to be processed */
|
|
8
|
+
total: number;
|
|
9
|
+
/** The current phase of the merge */
|
|
10
|
+
phase: string;
|
|
11
|
+
}
|
|
12
|
+
export type OnProgress = (info: ProgressInfo) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Generic image merge command.
|
|
15
|
+
*
|
|
16
|
+
* @template T - Merge-specific configuration options
|
|
17
|
+
* @param imageInputs - Input images to merge
|
|
18
|
+
* @param options - Merge options
|
|
19
|
+
* @param onProgress - Optional progress callback
|
|
20
|
+
* @returns A Promise resolving to the merged image buffer
|
|
21
|
+
*/
|
|
22
|
+
interface MergeCommand<T> {
|
|
23
|
+
(imageInputs: sharp.SharpInput[], options: T, onProgress?: OnProgress): Promise<Buffer>;
|
|
24
|
+
}
|
|
25
|
+
interface BaseMergeOptions {
|
|
26
|
+
/** Whether to randomize image order before merging. */
|
|
27
|
+
shuffle?: boolean;
|
|
28
|
+
/** Rounded corner radius in pixels. */
|
|
29
|
+
cornerRadius?: number;
|
|
30
|
+
/** Gap between images in pixels. */
|
|
31
|
+
gap?: number;
|
|
32
|
+
/** Background canvas color. */
|
|
33
|
+
canvasColor?: Color;
|
|
34
|
+
/** Width of the border around each image. Borders are placed internally in each image. */
|
|
35
|
+
borderWidth?: number;
|
|
36
|
+
/** Color of the border around each image. */
|
|
37
|
+
borderColor?: Color;
|
|
38
|
+
/** Output image format (png, jpeg, webp). */
|
|
39
|
+
format?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface GridMergeOptions extends BaseMergeOptions {
|
|
42
|
+
/** The aspect ratio of each image. Used to calculate image height based on given width.
|
|
43
|
+
*
|
|
44
|
+
* Examples include `16:9`, `3x2` or `1.777`. */
|
|
45
|
+
aspectRatio?: string | number;
|
|
46
|
+
/** Width of each image cell in pixels, uses the median image width if undefined. */
|
|
47
|
+
imageWidth?: number | undefined;
|
|
48
|
+
/** Number of columns in the grid. */
|
|
49
|
+
columns?: number;
|
|
50
|
+
/** Enable captions under images. */
|
|
51
|
+
caption?: boolean;
|
|
52
|
+
/** Captions text (one per image, in order). */
|
|
53
|
+
captions?: string[];
|
|
54
|
+
/** Caption text color. */
|
|
55
|
+
captionColor?: Color;
|
|
56
|
+
/** Maximum caption font size in pixels. */
|
|
57
|
+
maxCaptionSize?: number;
|
|
58
|
+
}
|
|
59
|
+
export interface MasonryMergeOptions extends BaseMergeOptions {
|
|
60
|
+
/** The height of each row, defaults to the trimmed median image height if undefined. Only applied in horizontal flows. */
|
|
61
|
+
rowHeight?: number | undefined;
|
|
62
|
+
/** The width of each column, defaults to the trimmed median image width if undefined. Only applied in vertical flows. */
|
|
63
|
+
columnWidth?: number | undefined;
|
|
64
|
+
/** The width of the entire canvas. Only required in horizontal flows. */
|
|
65
|
+
canvasWidth?: number | undefined;
|
|
66
|
+
/** The width of the entire canvas. Only required in vertical flows. */
|
|
67
|
+
canvasHeight?: number | undefined;
|
|
68
|
+
/** The orientation of the masonry layout. */
|
|
69
|
+
flow: 'horizontal' | 'vertical';
|
|
70
|
+
/** The horizontal alignment of each row. Only applied in horizontal layouts. */
|
|
71
|
+
hAlign?: 'left' | 'center' | 'right' | 'justified';
|
|
72
|
+
/** The vertical alignment of each column. Only applied in vertical layouts. */
|
|
73
|
+
vAlign?: 'top' | 'middle' | 'bottom' | 'justified';
|
|
74
|
+
}
|
|
75
|
+
interface TemplateMergeOptions extends BaseMergeOptions {
|
|
76
|
+
template: Template;
|
|
77
|
+
}
|
|
78
|
+
interface CollageMergeOptions extends Omit<BaseMergeOptions, 'gap'> {
|
|
79
|
+
/** Width of each image cell in pixels, uses the median image width if undefined. */
|
|
80
|
+
imageWidth?: number | undefined;
|
|
81
|
+
/** The aspect ratio of each image. Used to calculate image height based on given width.
|
|
82
|
+
*
|
|
83
|
+
* Examples include `16:9`, `3x2` or `1.777`. */
|
|
84
|
+
aspectRatio?: string | number;
|
|
85
|
+
/** Number of columns in the collage grid. */
|
|
86
|
+
columns?: number;
|
|
87
|
+
/** The number of pixels to potentially variate `imageWidth` by. Used to create random-sized images in the collage.
|
|
88
|
+
*
|
|
89
|
+
* For example, a value of `50` results in images being up to `50` pixels wider or narrower than the given `imageWidth`. */
|
|
90
|
+
imageWidthVariance?: number;
|
|
91
|
+
/** The estimated percentage of overlap for every image pair. A higher percentage creates a tighter collage. */
|
|
92
|
+
overlapPercentage?: number;
|
|
93
|
+
/** The maximum and minimum degree to rotate each image.
|
|
94
|
+
*
|
|
95
|
+
* For example, a value of `10` will result in a random degree being picked from `-10` to `+10` degrees. */
|
|
96
|
+
rotationRange?: number;
|
|
97
|
+
}
|
|
98
|
+
export type GridMerge = MergeCommand<GridMergeOptions>;
|
|
99
|
+
export type MasonryMerge = MergeCommand<MasonryMergeOptions>;
|
|
100
|
+
export type TemplateMerge = MergeCommand<TemplateMergeOptions>;
|
|
101
|
+
export type CollageMerge = MergeCommand<CollageMergeOptions>;
|
|
102
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import sharp from 'sharp';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
interface Message {
|
|
2
|
+
message: string;
|
|
3
|
+
chalk?: (text: string) => string;
|
|
4
|
+
}
|
|
5
|
+
export declare const MESSAGES: {
|
|
6
|
+
WARNINGS: {
|
|
7
|
+
IGNORED_FILES: {
|
|
8
|
+
message: string;
|
|
9
|
+
chalk: import("chalk").ChalkInstance;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
SUCCESS: {
|
|
13
|
+
OUTPUT: {
|
|
14
|
+
message: string;
|
|
15
|
+
chalk: import("chalk").ChalkInstance;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
ERROR: {
|
|
19
|
+
INTERNAL: {
|
|
20
|
+
message: string;
|
|
21
|
+
chalk: import("chalk").ChalkInstance;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export declare class MessageRenderer {
|
|
26
|
+
#private;
|
|
27
|
+
message: Message;
|
|
28
|
+
constructor(message: Message, ...inputs: (string | number)[]);
|
|
29
|
+
render(): void;
|
|
30
|
+
confirm(defaultAnswer?: boolean): Promise<boolean>;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import readline from 'node:readline';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { formatString } from '../../cli/utils/stringFormatter.js';
|
|
4
|
+
export const MESSAGES = {
|
|
5
|
+
WARNINGS: {
|
|
6
|
+
IGNORED_FILES: {
|
|
7
|
+
message: '{0}\nThe following files will be ignored. Proceed?',
|
|
8
|
+
chalk: chalk.yellow,
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
SUCCESS: {
|
|
12
|
+
OUTPUT: {
|
|
13
|
+
message: 'Image created at {0}.',
|
|
14
|
+
chalk: chalk.blue,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
ERROR: {
|
|
18
|
+
INTERNAL: {
|
|
19
|
+
message: 'An internal error has occurred.',
|
|
20
|
+
chalk: chalk.red,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
export class MessageRenderer {
|
|
25
|
+
message;
|
|
26
|
+
#formattedMessage;
|
|
27
|
+
#message;
|
|
28
|
+
constructor(message, ...inputs) {
|
|
29
|
+
this.message = message;
|
|
30
|
+
this.#message = message;
|
|
31
|
+
const base = formatString(message.message, ...inputs);
|
|
32
|
+
this.#formattedMessage = message.chalk ? message.chalk(base) : base;
|
|
33
|
+
}
|
|
34
|
+
render() {
|
|
35
|
+
console.log(this.#formattedMessage);
|
|
36
|
+
}
|
|
37
|
+
confirm(defaultAnswer = true) {
|
|
38
|
+
const rl = readline.createInterface({
|
|
39
|
+
input: process.stdin,
|
|
40
|
+
output: process.stdout,
|
|
41
|
+
});
|
|
42
|
+
const options = defaultAnswer ? '(Y/n)' : '(y/N)';
|
|
43
|
+
const question = this.#message.chalk
|
|
44
|
+
? `${this.#formattedMessage} ${this.#message.chalk(options)} `
|
|
45
|
+
: `${this.#formattedMessage} ${options} `;
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
rl.question(question, (value) => {
|
|
48
|
+
const cleanedValue = value.toLowerCase().trim();
|
|
49
|
+
resolve((!cleanedValue.length && !!defaultAnswer) || cleanedValue === 'y');
|
|
50
|
+
rl.close();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MergeContext } from './mergePipeline.js';
|
|
2
|
+
export declare function requireState<TState, K extends keyof TState>(context: MergeContext<TState>, key: K): asserts context is MergeContext<TState & Required<Pick<TState, K>>>;
|
|
3
|
+
export declare function requireContextProp<TState, K extends keyof MergeContext<TState>>(context: MergeContext<TState>, key: K): asserts context is MergeContext<TState> & Required<Pick<MergeContext<TState>, K>>;
|
|
4
|
+
export declare function requireNonEmptyArray<T>(arr: readonly T[], name?: string): asserts arr is readonly [T, ...T[]];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { MergeError } from '../mergeError.js';
|
|
2
|
+
// |-------------------|
|
|
3
|
+
// | Structural Guards |
|
|
4
|
+
// |-------------------|
|
|
5
|
+
export function requireState(context, key) {
|
|
6
|
+
if (context.state[key] === undefined) {
|
|
7
|
+
throw new MergeError(`State "${String(key)}" was not initialized`, { type: 'internal' });
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function requireContextProp(context, key) {
|
|
11
|
+
const value = context[key];
|
|
12
|
+
if (value === undefined) {
|
|
13
|
+
throw new MergeError(`Context "${String(key)}" was not initialized`, { type: 'internal' });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
// |------------------|
|
|
17
|
+
// | Invariant Guards |
|
|
18
|
+
// |------------------|
|
|
19
|
+
export function requireNonEmptyArray(arr, name = 'Array') {
|
|
20
|
+
if (arr.length === 0) {
|
|
21
|
+
throw new MergeError(`"${name}" must not be empty`, { type: 'internal' });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import sharp from 'sharp';
|
|
2
|
+
import type z from 'zod';
|
|
3
|
+
import type { OnProgress, ProgressInfo } from '../merges/types.js';
|
|
4
|
+
export interface MergeContext<TState> {
|
|
5
|
+
inputs: sharp.SharpInput[];
|
|
6
|
+
progressInfo: ProgressInfo;
|
|
7
|
+
images: sharp.Sharp[];
|
|
8
|
+
canvas?: sharp.Sharp;
|
|
9
|
+
composites: sharp.OverlayOptions[];
|
|
10
|
+
captions: string[];
|
|
11
|
+
state: TState;
|
|
12
|
+
}
|
|
13
|
+
export type MergeStep<TOptions, TState> = (context: MergeContext<TState>, options: TOptions, onProgress?: OnProgress) => Promise<Buffer | void>;
|
|
14
|
+
/**
|
|
15
|
+
* Pipeline orchestrator for running a sequence of merge steps that
|
|
16
|
+
* progressively build or modify a final merged image.
|
|
17
|
+
*
|
|
18
|
+
* Generic type parameters:
|
|
19
|
+
* - TOptions: the shape of the validated options object passed to each step.
|
|
20
|
+
* - TState: the shape of the pipeline's runtime state held in MergeContext.
|
|
21
|
+
*
|
|
22
|
+
* The pipeline:
|
|
23
|
+
* - Validates incoming options via a Zod schema (see createPipeline).
|
|
24
|
+
* - Maintains an ordered list of MergeStep functions to execute.
|
|
25
|
+
* - Provides optional progress reporting via an OnProgress callback.
|
|
26
|
+
*
|
|
27
|
+
* Usage:
|
|
28
|
+
* - Create an instance via createPipeline to ensure options are validated and
|
|
29
|
+
* progressInfo is initialized on the supplied MergeContext.
|
|
30
|
+
* - Register steps with use(...).
|
|
31
|
+
* - Execute with run() — in normal usage this returns a Buffer containing the
|
|
32
|
+
* final image. When run in "test" mode (testOptions supplied) the pipeline
|
|
33
|
+
* may perform a dry-run and return void.
|
|
34
|
+
*
|
|
35
|
+
* Errors:
|
|
36
|
+
* - Validation-related errors thrown from createPipeline are surfaced as
|
|
37
|
+
* MergeError instances with type 'validation' or 'internal'.
|
|
38
|
+
* - During execution, any thrown non-MergeError is converted into a runtime
|
|
39
|
+
* Error indicating the offending step name.
|
|
40
|
+
*
|
|
41
|
+
* @template TOptions - Validated options shape used by registered steps.
|
|
42
|
+
* @template TState - Context/state shape managed by the pipeline.
|
|
43
|
+
*/
|
|
44
|
+
export declare class MergePipeline<TOptions, TState> {
|
|
45
|
+
private options;
|
|
46
|
+
private context;
|
|
47
|
+
private onProgress?;
|
|
48
|
+
private steps;
|
|
49
|
+
private optionsCounter;
|
|
50
|
+
private stateCounter;
|
|
51
|
+
constructor(options: TOptions, context: MergeContext<TState>, onProgress?: OnProgress | undefined);
|
|
52
|
+
static createPipeline<TZodSchema extends z.ZodType, TOptions, TState>(schema: TZodSchema, options: TOptions, context: Omit<MergeContext<TState>, 'progressInfo'>, onProgress?: OnProgress): Promise<MergePipeline<z.z.core.output<TZodSchema>, TState>>;
|
|
53
|
+
use(step: MergeStep<TOptions, TState>): this;
|
|
54
|
+
run(): Promise<Buffer>;
|
|
55
|
+
run(testOptions: {
|
|
56
|
+
logOptions?: boolean;
|
|
57
|
+
logContext?: boolean;
|
|
58
|
+
}): Promise<Buffer | void>;
|
|
59
|
+
private logForDebugging;
|
|
60
|
+
}
|
|
@@ -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
|
+
});
|