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.
- package/README.md +362 -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 +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/jobs/batchRunner.d.ts +44 -0
- package/dist/core/jobs/batchRunner.js +90 -0
- package/dist/core/jobs/types.d.ts +10 -0
- package/dist/core/jobs/types.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 +5 -0
- package/dist/core/merges/index.js +4 -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 +123 -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/modules/typedEventEmitter.d.ts +7 -0
- package/dist/core/modules/typedEventEmitter.js +9 -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/mergeJob.d.ts +11 -0
- package/dist/core/schemas/mergeJob.js +6 -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,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,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
|
+
};
|