From 2aaf5a2dc501ab38ebb1882fc546eca3fd5e9454 Mon Sep 17 00:00:00 2001 From: Tofandel Date: Thu, 30 Oct 2025 16:09:52 +0100 Subject: [PATCH] Fix clamping for different fit than cover and don't clamp SVGs by default --- src/handlers/handlers.ts | 17 +++++++++---- src/handlers/utils.ts | 24 +++++++++++++++---- src/ipx.ts | 3 ++- test.mjs | 2 +- test/handlers/utils.test.ts | 48 +++++++++++++++++++++++++++++++------ 5 files changed, 76 insertions(+), 18 deletions(-) diff --git a/src/handlers/handlers.ts b/src/handlers/handlers.ts index 6935dc1c..5773aae0 100644 --- a/src/handlers/handlers.ts +++ b/src/handlers/handlers.ts @@ -94,11 +94,18 @@ export const resize: Handler = { height = width; } // sharp's `withoutEnlargement` doesn't respect the requested aspect ratio, so we need to do it ourselves - if (!context.enlarge) { - const clamped = clampDimensionsPreservingAspectRatio(context.meta, { - width, - height, - }); + // By default don't clamp svgs unless explicitly desired with enlarge=false + if ( + context.meta?.type === "svg" ? context.enlarge === false : !context.enlarge + ) { + const clamped = clampDimensionsPreservingAspectRatio( + context.fit, + context.meta, + { + width, + height, + }, + ); width = clamped.width; height = clamped.height; } diff --git a/src/handlers/utils.ts b/src/handlers/utils.ts index 2e2211dd..0ce8bba3 100644 --- a/src/handlers/utils.ts +++ b/src/handlers/utils.ts @@ -35,18 +35,34 @@ export function applyHandler( } export function clampDimensionsPreservingAspectRatio( + fit: HandlerContext["fit"], sourceDimensions: ImageMeta, desiredDimensions: { width: number; height: number }, ) { const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height; + const sourceAspectRatio = sourceDimensions.width / sourceDimensions.height; let { width, height } = desiredDimensions; if (sourceDimensions.width && width > sourceDimensions.width) { - width = sourceDimensions.width; - height = Math.round(sourceDimensions.width / desiredAspectRatio); + if ( + ["contain", "fill", "inside"].includes(fit) && + sourceAspectRatio < desiredAspectRatio + ) { + width = Math.round(height * desiredAspectRatio); + } else { + width = sourceDimensions.width; + height = Math.round(sourceDimensions.width / desiredAspectRatio); + } } if (sourceDimensions.height && height > sourceDimensions.height) { - height = sourceDimensions.height; - width = Math.round(sourceDimensions.height * desiredAspectRatio); + if ( + ["contain", "fill", "inside"].includes(fit) && + sourceAspectRatio > desiredAspectRatio + ) { + height = Math.round(width / desiredAspectRatio); + } else { + height = sourceDimensions.height; + width = Math.round(sourceDimensions.height * desiredAspectRatio); + } } return { width, height }; diff --git a/src/ipx.ts b/src/ipx.ts index 617d68d3..9efbeeed 100644 --- a/src/ipx.ts +++ b/src/ipx.ts @@ -287,6 +287,7 @@ export function createIPX(userOptions: IPXOptions): IPX { if (mFormat === "jpg") { mFormat = "jpeg"; } + const format = mFormat && SUPPORTED_FORMATS.has(mFormat) ? mFormat @@ -295,7 +296,7 @@ export function createIPX(userOptions: IPXOptions): IPX { : "jpeg"; // Use original SVG if format is not specified - if (imageMeta.type === "svg" && !mFormat) { + if (imageMeta.type === "svg" && (!mFormat || mFormat === "svg")) { if (options.svgo === false) { return { data: sourceData, diff --git a/test.mjs b/test.mjs index e67444fb..332ebc6d 100644 --- a/test.mjs +++ b/test.mjs @@ -5,5 +5,5 @@ const ipx = createIPX({ }); const source = await ipx("../assets2/bliss.jpg"); // access file outside ./public dir because of same prefix folder -const { data, format } = await source.process(); +const { format } = await source.process(); console.log(format); // print image format diff --git a/test/handlers/utils.test.ts b/test/handlers/utils.test.ts index 25e679ba..55c76c34 100644 --- a/test/handlers/utils.test.ts +++ b/test/handlers/utils.test.ts @@ -22,12 +22,46 @@ describe("utils", () => { }); it("clampDimensionsPreservingAspectRatio", () => { - const sourceDimensions = { width: 200, height: 100 }; - const desiredDimensions = { width: 300, height: 150 }; - const result = clampDimensionsPreservingAspectRatio( - sourceDimensions, - desiredDimensions, - ); - expect(result).toEqual({ width: 200, height: 100 }); + const dimensions = [ + { + source: [200, 100], + desired: [300, 150], + expected: [200, 100], + }, + { + source: [200, 150], + desired: [150, 200], + expected: [113, 150], + }, + { + source: [150, 200], + desired: [200, 150], + expected: [150, 113], + }, + { + source: [211, 40], + desired: [170, 170, "contain"], + expected: [170, 170], + }, + { + source: [211, 40], + desired: [220, 110, "contain"], + expected: [211, 106], + }, + { + source: [40, 211], + desired: [110, 220, "contain"], + expected: [106, 211], + }, + ]; + + for (const d of dimensions) { + const result = clampDimensionsPreservingAspectRatio( + d.desired[2] ?? null, + { width: d.source[0], height: d.source[1] }, + { width: d.desired[0], height: d.desired[1] }, + ); + expect(result).toEqual({ width: d.expected[0], height: d.expected[1] }); + } }); });