Skip to content

Commit 57e0066

Browse files
committed
feat: add encoding options for overlay input paths and texts
1 parent bc03620 commit 57e0066

File tree

4 files changed

+165
-7
lines changed

4 files changed

+165
-7
lines changed

src/interfaces/Transformation.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,17 @@ export interface TextOverlay extends BaseOverlay {
523523
*/
524524
text: string;
525525

526+
/**
527+
* Specifies how the overlay input text should be encoded. The default is `auto`, which means the SDK will initially treat the text as plain text to improve URL readability. If the text contains special characters, the SDK will automatically switch to `base64` encoding.
528+
*
529+
* You can also explicitly set the encoding to either `plain` or `base64`.
530+
*
531+
* The `plain` option uses the format `i-{input}`, while `base64` uses `ie-{base64_encoded_input}`.
532+
*
533+
* * Regardless of the encoding method, the input text is always percent-encoded to ensure it is URL-safe.
534+
*/
535+
encoding: "auto" | "plain" | "base64";
536+
526537
/**
527538
* Control styling of the text overlay.
528539
*/
@@ -537,6 +548,19 @@ export interface ImageOverlay extends BaseOverlay {
537548
*/
538549
input: string;
539550

551+
/**
552+
* Specifies how the overlay input path should be encoded. The default is `auto`, which means the SDK will initially treat the path as plain text to improve URL readability. If the path contains special characters, the SDK will automatically switch to `base64` encoding.
553+
*
554+
* You can also explicitly set the encoding to either `plain` or `base64`.
555+
*
556+
* The `plain` option uses the format `i-{input}`, while `base64` uses `ie-{base64_encoded_input}`.
557+
*
558+
* * Regardless of the encoding method:
559+
* - Leading and trailing slashes are removed.
560+
* - Any remaining slashes within the path are replaced with `@@` when using plain text.
561+
*/
562+
encoding: "auto" | "plain" | "base64";
563+
540564
/**
541565
* Array of transformations to be applied to the overlay image. Supported transformations depends on the base/parent asset.
542566
*
@@ -552,6 +576,19 @@ export interface VideoOverlay extends BaseOverlay {
552576
*/
553577
input: string;
554578

579+
/**
580+
* Specifies how the overlay input path should be encoded. The default is `auto`, which means the SDK will initially treat the path as plain text to improve URL readability. If the path contains special characters, the SDK will automatically switch to `base64` encoding.
581+
*
582+
* You can also explicitly set the encoding to either `plain` or `base64`.
583+
*
584+
* The `plain` option uses the format `i-{input}`, while `base64` uses `ie-{base64_encoded_input}`.
585+
*
586+
* * Regardless of the encoding method:
587+
* - Leading and trailing slashes are removed.
588+
* - Any remaining slashes within the path are replaced with `@@` when using plain text.
589+
*/
590+
encoding: "auto" | "plain" | "base64";
591+
555592
/**
556593
* Array of transformation to be applied to the overlay video. Except `streamingResolutions`, all other video transformations are supported.
557594
*
@@ -567,6 +604,19 @@ export interface SubtitleOverlay extends BaseOverlay {
567604
*/
568605
input: string;
569606

607+
/**
608+
* Specifies how the overlay input path should be encoded. The default is `auto`, which means the SDK will initially treat the path as plain text to improve URL readability. If the path contains special characters, the SDK will automatically switch to `base64` encoding.
609+
*
610+
* You can also explicitly set the encoding to either `plain` or `base64`.
611+
*
612+
* The `plain` option uses the format `i-{input}`, while `base64` uses `ie-{base64_encoded_input}`.
613+
*
614+
* * Regardless of the encoding method:
615+
* - Leading and trailing slashes are removed.
616+
* - Any remaining slashes within the path are replaced with `@@` when using plain text.
617+
*/
618+
encoding: "auto" | "plain" | "base64";
619+
570620
/**
571621
* Control styling of the subtitle.
572622
*

src/url/builder.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { ImageKitOptions, UrlOptions } from "../interfaces";
22
import { ImageOverlay, SolidColorOverlay, SubtitleOverlay, TextOverlay, Transformation, VideoOverlay } from "../interfaces/Transformation";
33
import transformationUtils, { safeBtoa } from "../utils/transformation";
44
const TRANSFORMATION_PARAMETER = "tr";
5+
const SIMPLE_OVERLAY_PATH_REGEX = new RegExp('^[a-zA-Z0-9-._,/ ]*$')
6+
const SIMPLE_OVERLAY_TEXT_REGEX = new RegExp('^[a-zA-Z0-9-._, ]*$') // These characters are selected by testing actual URLs on both path and query parameters. If and when backend starts supporting wide range of characters, this regex should be updated to improve URL readability.
57

68
function removeTrailingSlash(str: string) {
79
if (typeof str == "string" && str[str.length - 1] == "/") {
@@ -73,6 +75,34 @@ export const buildURL = (opts: UrlOptions & ImageKitOptions) => {
7375
return urlObj.href;
7476
};
7577

78+
function processInputPath(str: string, enccoding: string): string {
79+
// Remove leading and trailing slashes
80+
str = removeTrailingSlash(removeLeadingSlash(str));
81+
if(enccoding === "plain") {
82+
return `i-${str.replace(/\//g, "@@")}`;
83+
}
84+
if(enccoding === "base64") {
85+
return `ie-${encodeURIComponent(safeBtoa(str))}`;
86+
}
87+
if (SIMPLE_OVERLAY_PATH_REGEX.test(str)) {
88+
return `i-${str.replace(/\//g, "@@")}`;
89+
} else {
90+
return `ie-${encodeURIComponent(safeBtoa(str))}`;
91+
}
92+
}
93+
94+
function processText(str: string, enccoding: TextOverlay["encoding"]): string {
95+
if (enccoding === "plain") {
96+
return `i-${encodeURIComponent(str)}`;
97+
}
98+
if (enccoding === "base64") {
99+
return `ie-${encodeURIComponent(safeBtoa(str))}`;
100+
}
101+
if (SIMPLE_OVERLAY_TEXT_REGEX.test(str)) {
102+
return `i-${encodeURIComponent(str)}`;
103+
}
104+
return `ie-${encodeURIComponent(safeBtoa(str))}`;
105+
}
76106

77107
function processOverlay(overlay: Transformation["overlay"]): string | undefined {
78108
const entries = [];
@@ -91,16 +121,19 @@ function processOverlay(overlay: Transformation["overlay"]): string | undefined
91121
if (!textOverlay.text) {
92122
return;
93123
}
124+
const enccoding = textOverlay.encoding || "auto";
125+
94126
entries.push("l-text");
95-
entries.push(`ie-${encodeURIComponent(safeBtoa(textOverlay.text))}`);
127+
entries.push(processText(textOverlay.text, enccoding));
96128
}
97129
break;
98130
case "image":
99131
entries.push("l-image");
100132
{
101133
const imageOverlay = overlay as ImageOverlay;
134+
const enccoding = imageOverlay.encoding || "auto";
102135
if (imageOverlay.input) {
103-
entries.push(`i-${imageOverlay.input}`);
136+
entries.push(processInputPath(imageOverlay.input, enccoding));
104137
} else {
105138
return;
106139
}
@@ -110,8 +143,9 @@ function processOverlay(overlay: Transformation["overlay"]): string | undefined
110143
entries.push("l-video");
111144
{
112145
const videoOverlay = overlay as VideoOverlay;
146+
const enccoding = videoOverlay.encoding || "auto";
113147
if (videoOverlay.input) {
114-
entries.push(`i-${videoOverlay.input}`);
148+
entries.push(processInputPath(videoOverlay.input, enccoding));
115149
} else {
116150
return;
117151
}
@@ -121,8 +155,9 @@ function processOverlay(overlay: Transformation["overlay"]): string | undefined
121155
entries.push("l-subtitle");
122156
{
123157
const subtitleOverlay = overlay as SubtitleOverlay;
158+
const enccoding = subtitleOverlay.encoding || "auto";
124159
if (subtitleOverlay.input) {
125-
entries.push(`i-${subtitleOverlay.input}`);
160+
entries.push(processInputPath(subtitleOverlay.input, enccoding));
126161
} else {
127162
return;
128163
}

src/utils/transformation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default {
3636
}
3737

3838
export const safeBtoa = function (str: string): string {
39-
if (typeof btoa !== "undefined") {
39+
if (typeof window !== "undefined") {
4040
return btoa(str);
4141
} else {
4242
// Node fallback

test/url-generation/overlay.js

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe("Overlay Transformation Test Cases", function () {
7676
}
7777
}]
7878
});
79-
expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:l-text,ie-${encodeURIComponent(safeBtoa("Minimal Text"))},l-end/base-image.jpg`);
79+
expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-${encodeURIComponent("Minimal Text")},l-end/base-image.jpg`);
8080
});
8181

8282
it('Image overlay generates correct URL with input logo.png', function () {
@@ -268,6 +268,79 @@ describe("Overlay Transformation Test Cases", function () {
268268
]
269269
});
270270

271-
expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:l-text,ie-${encodeURIComponent(safeBtoa("Every thing"))},lxo-10,lyo-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,fs-20,ff-Arial,co-0000ff,ia-left,pa-5,al-7,tg-b,bg-red,r-10,rt-N45,fl-h,lh-20,l-end:l-image,i-logo.png,lxo-10,lyo-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-text,ie-${encodeURIComponent(safeBtoa("Nested text overlay"))},l-end,l-end:l-video,i-play-pause-loop.mp4,lxo-10,lyo-20,lfo-center,lso-5,leo-15,ldu-10,l-end:l-subtitle,i-subtitle.srt,lxo-10,lyo-20,lfo-center,lso-5,leo-15,ldu-10,l-end:l-image,i-ik_canvas,bg-FF0000,lxo-10,lyo-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-end/base-image.jpg`)
271+
expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-${encodeURIComponent("Every thing")},lxo-10,lyo-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,fs-20,ff-Arial,co-0000ff,ia-left,pa-5,al-7,tg-b,bg-red,r-10,rt-N45,fl-h,lh-20,l-end:l-image,i-logo.png,lxo-10,lyo-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-text,i-${encodeURIComponent("Nested text overlay")},l-end,l-end:l-video,i-play-pause-loop.mp4,lxo-10,lyo-20,lfo-center,lso-5,leo-15,ldu-10,l-end:l-subtitle,i-subtitle.srt,lxo-10,lyo-20,lfo-center,lso-5,leo-15,ldu-10,l-end:l-image,i-ik_canvas,bg-FF0000,lxo-10,lyo-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-end/base-image.jpg`)
272272
});
273273
});
274+
275+
276+
describe("Edge cases", function () {
277+
const imagekit = new ImageKit({
278+
...initializationParams,
279+
urlEndpoint: "https://ik.imagekit.io/demo", // Using real url to test correctness quickly by clicking link
280+
});
281+
282+
it('Nested simple path, should use i instead of ie, handle slash properly', function () {
283+
const url = imagekit.url({
284+
path: "/medium_cafe_B1iTdD0C.jpg",
285+
transformation: [{
286+
overlay: {
287+
type: "image",
288+
input: "/customer_logo/nykaa.png",
289+
}
290+
}]
291+
});
292+
expect(url).equal(`https://ik.imagekit.io/demo/tr:l-image,i-customer_logo@@nykaa.png,l-end/medium_cafe_B1iTdD0C.jpg`);
293+
});
294+
295+
it('Nested non-simple path, should use ie instead of i', function () {
296+
const url = imagekit.url({
297+
path: "/medium_cafe_B1iTdD0C.jpg",
298+
transformation: [{
299+
overlay: {
300+
type: "image",
301+
input: "/customer_logo/Ñykaa.png"
302+
}
303+
}]
304+
});
305+
expect(url).equal(`https://ik.imagekit.io/demo/tr:l-image,ie-Y3VzdG9tZXJfbG9nby9OzIN5a2FhLnBuZw%3D%3D,l-end/medium_cafe_B1iTdD0C.jpg`);
306+
});
307+
308+
it('Simple text overlay, should use i instead of ie', function () {
309+
const url = imagekit.url({
310+
path: "/medium_cafe_B1iTdD0C.jpg",
311+
transformation: [{
312+
overlay: {
313+
type: "text",
314+
text: "Manu",
315+
}
316+
}]
317+
});
318+
expect(url).equal(`https://ik.imagekit.io/demo/tr:l-text,i-Manu,l-end/medium_cafe_B1iTdD0C.jpg`);
319+
});
320+
321+
it('Simple text overlay with spaces and comma, should use i instead of ie', function () {
322+
const url = imagekit.url({
323+
path: "/medium_cafe_B1iTdD0C.jpg",
324+
transformation: [{
325+
overlay: {
326+
type: "text",
327+
text: "alnum123-._, ",
328+
}
329+
}]
330+
});
331+
expect(url).equal(`https://ik.imagekit.io/demo/tr:l-text,i-${encodeURIComponent("alnum123-._, ")},l-end/medium_cafe_B1iTdD0C.jpg`);
332+
});
333+
334+
it('Non simple text overlay, should use ie instead of i', function () {
335+
const url = imagekit.url({
336+
path: "/medium_cafe_B1iTdD0C.jpg",
337+
transformation: [{
338+
overlay: {
339+
type: "text",
340+
text: "Let's use ©, ®, ™, etc",
341+
}
342+
}]
343+
});
344+
expect(url).equal(`https://ik.imagekit.io/demo/tr:l-text,ie-TGV0J3MgdXNlIMKpLCDCriwg4oSiLCBldGM%3D,l-end/medium_cafe_B1iTdD0C.jpg`);
345+
});
346+
});

0 commit comments

Comments
 (0)