diff --git a/.changeset/fair-flowers-yawn.md b/.changeset/fair-flowers-yawn.md new file mode 100644 index 00000000..65c41aee --- /dev/null +++ b/.changeset/fair-flowers-yawn.md @@ -0,0 +1,6 @@ +--- +"@imgproxy/imgproxy-js-core": minor +--- + +- Add `ch` (chessboard order) position to `watermark` option. +- Add `watermark_rotate` option. diff --git a/src/options/watermark.ts b/src/options/watermark.ts index 95f00b7f..9921731c 100644 --- a/src/options/watermark.ts +++ b/src/options/watermark.ts @@ -17,6 +17,7 @@ const currentPositions = { soea: true, sowe: true, re: true, + ch: true, }; const getOpt = (options: WatermarkOptionsPartial): Watermark | undefined => diff --git a/src/options/watermarkRotate.ts b/src/options/watermarkRotate.ts new file mode 100644 index 00000000..bbbb64b6 --- /dev/null +++ b/src/options/watermarkRotate.ts @@ -0,0 +1,24 @@ +import type { + WatermarkRotate, + WatermarkRotateOptionsPartial, +} from "../types/watermarkRotate"; +import { guardIsUndef, guardIsNotNum } from "../utils"; + +const getOpt = ( + options: WatermarkRotateOptionsPartial +): WatermarkRotate | undefined => + options.watermark_rotate ?? options.wm_rot ?? options.wmr; + +const test = (options: WatermarkRotateOptionsPartial): boolean => + getOpt(options) !== undefined; + +const build = (options: WatermarkRotateOptionsPartial): string => { + const watermarkRotateOpts = getOpt(options); + + guardIsUndef(watermarkRotateOpts, "watermark_rotate"); + guardIsNotNum(watermarkRotateOpts, "watermark_rotate"); + + return `wmr:${watermarkRotateOpts}`; +}; + +export { test, build }; diff --git a/src/types/index.ts b/src/types/index.ts index c1a9a52a..1b12b5f4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -59,6 +59,7 @@ import type { TrimOptionsPartial } from "./trim"; import type { UnsharpMaskingOptionsPartial } from "./unsharpMasking"; import type { VideoThumbnailSecondOptionsPartial } from "../typesShared/videoThumbnailSecond"; import type { WatermarkOptionsPartial } from "./watermark"; +import type { WatermarkRotateOptionsPartial } from "./watermarkRotate"; import type { WatermarkShadowOptionsPartial } from "./watermarkShadow"; import type { WatermarkSizeOptionsPartial } from "./watermarkSize"; import type { WatermarkTextOptionsPartial } from "./watermarkText"; @@ -133,6 +134,7 @@ export type Options = AdjustOptionsPartial & WatermarkSizeOptionsPartial & WatermarkTextOptionsPartial & WatermarkUrlOptionsPartial & + WatermarkRotateOptionsPartial & WebpOptionsPartial & WidthOptionsPartial & ZoomOptionsPartial & diff --git a/src/types/watermark.ts b/src/types/watermark.ts index ad31d8f9..25b2acc1 100644 --- a/src/types/watermark.ts +++ b/src/types/watermark.ts @@ -3,7 +3,7 @@ * * @param {number} opacity - watermark opacity modifier. * Final opacity is calculated like general opacity option * opacity watermark. Value range: `0` - `1`. - * @param {"ce" | "no" | "so" | "ea" | "we" | "noea" | "nowe" | "soea" | "sowe" | "re"} [position="ce"] - + * @param {"ce" | "no" | "so" | "ea" | "we" | "noea" | "nowe" | "soea" | "sowe" | "re" | "ch"} [position="ce"] - * (optional) specifies the position of the watermark. Default `"ce"`. * * Available values: @@ -17,15 +17,16 @@ * - `soea`: south-east (bottom-right corner) * - `sowe`: south-west (bottom-left corner) * - `re`: repeat and tile the watermark to fill the entire image + * - `ch`: **PRO feature** same as `re` but watermarks are placed in a chessboard order * * @param {number} [x_offset] - (optional) specifies the horizontal offset for the watermark. * You can use negative values, this means that the watermark will be shifted towards the edge that is selected. * That is, shifted by the selected number of pixels beyond the edge. - * When using `re` position, these values define the spacing between the tiles. + * When using `re` or `ch` position, these values define the spacing between the tiles. * @param {number} [y_offset] - (optional) specifies the vertical offset for the watermark. * You can use negative values, this means that the watermark will be shifted towards the edge that is selected. * That is, shifted by the selected number of pixels beyond the edge. - * When using `re` position, these values define the spacing between the tiles. + * When using `re` or `ch` position, these values define the spacing between the tiles. * @param {number} [scale] - (optional) a floating-point number that defines * the watermark size relative to the resultant image size. * When set to 0 or when omitted, the watermark size won’t be changed. @@ -65,7 +66,8 @@ interface Watermark { | "nowe" | "soea" | "sowe" - | "re"; + | "re" + | "ch"; x_offset?: number; y_offset?: number; scale?: number; diff --git a/src/types/watermarkRotate.ts b/src/types/watermarkRotate.ts new file mode 100644 index 00000000..eba6089e --- /dev/null +++ b/src/types/watermarkRotate.ts @@ -0,0 +1,26 @@ +/** + * *Watermark rotate option*. **PRO feature** + * + * Rotates the watermark on the specified angle (clockwise). + * The orientation from the image metadata is applied before the rotation. + * + * @default 0 + * + * @see {@link https://docs.imgproxy.net/generating_the_url?id=watermark-rotate | watermark rotate option imgproxy docs} + */ +type WatermarkRotate = number; + +/** + * *Watermark rotate*. **PRO feature** + * + * To describe the Watermark rotate option, you can use the keyword `watermark_rotate`, `wm_rot`, or `wmr`. + * + * @see {@link https://docs.imgproxy.net/generating_the_url?id=watermark-rotate | watermark rotate option imgproxy docs} + */ +interface WatermarkRotateOptionsPartial { + watermark_rotate?: WatermarkRotate; + wm_rot?: WatermarkRotate; + wmr?: WatermarkRotate; +} + +export { WatermarkRotate, WatermarkRotateOptionsPartial }; diff --git a/tests/optionsBasic/watermark.test.ts b/tests/optionsBasic/watermark.test.ts index 28868e8a..3e390c60 100644 --- a/tests/optionsBasic/watermark.test.ts +++ b/tests/optionsBasic/watermark.test.ts @@ -59,7 +59,13 @@ describe("watermark", () => { // @ts-expect-error: Let's ignore an error (check for users with vanilla js). build({ watermark: { opacity: 0.2, position: "top" } }) ).toThrow( - "watermark.position is invalid. Valid values are: ce, no, so, ea, we, noea, nowe, soea, sowe, re" + "watermark.position is invalid. Valid values are: ce, no, so, ea, we, noea, nowe, soea, sowe, re, ch" + ); + }); + + it("should accept ch position", () => { + expect(build({ watermark: { opacity: 0.2, position: "ch" } })).toEqual( + "wm:0.2:ch" ); }); diff --git a/tests/optionsBasic/watermarkRotate.test.ts b/tests/optionsBasic/watermarkRotate.test.ts new file mode 100644 index 00000000..0acb14bf --- /dev/null +++ b/tests/optionsBasic/watermarkRotate.test.ts @@ -0,0 +1,66 @@ +import { assertType, describe, expect, expectTypeOf, it } from "vitest"; +import { test, build } from "../../src/options/watermarkRotate"; +import { Options } from "../../src/types"; + +const NAMES = ["watermark_rotate", "wm_rot", "wmr"] as const; + +describe.each(NAMES)("%s", name => { + describe("test", () => { + it("should return true if option is defined", () => { + expect(test({ [name]: 45 })).toEqual(true); + }); + + it("should return true if option is equal 0", () => { + expect(test({ [name]: 0 })).toEqual(true); + }); + + it("should return false if option is undefined", () => { + expect(test({})).toEqual(false); + }); + }); + + describe("build", () => { + it("should throw an error if option is undefined", () => { + expect(() => build({})).toThrow("watermark_rotate option is undefined"); + }); + + it("should throw an error if option is not a number", () => { + expect(() => build({ [name]: "150" })).toThrow( + "watermark_rotate option is not a number" + ); + }); + + it("should buld result", () => { + expect(build({ [name]: 45 })).toEqual("wmr:45"); + }); + + it("should correctly handle 0 value", () => { + expect(build({ [name]: 0 })).toEqual("wmr:0"); + }); + }); +}); + +describe("Check `watermark_rotate` type declarations", () => { + it("watermark_rotate option should have `number` type", () => { + expectTypeOf(build).parameter(0).toEqualTypeOf<{ + watermark_rotate?: number; + wm_rot?: number; + wmr?: number; + }>(); + expectTypeOf(build).returns.toEqualTypeOf(); + }); + + it("check TS type declaration", () => { + assertType({ + watermark_rotate: 45, + }); + + assertType({ + wm_rot: 45, + }); + + assertType({ + wmr: 45, + }); + }); +});