ipx 0.7.0 → 0.8.1

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 CHANGED
@@ -46,31 +46,33 @@ Keep original format (`png`) and set width to `200`:
46
46
 
47
47
  `http://localhost:3000/w_200/static/buffalo.png`
48
48
 
49
- Resize to `200px` using `embed` method and change format to `webp`:
49
+ Resize to `200x200px` using `embed` method and change format to `webp`:
50
50
 
51
- `http://localhost:3000/embed,f_webp,s_200/static/buffalo.png`
51
+ `http://localhost:3000/embed,f_webp,s_200x200/static/buffalo.png`
52
52
 
53
53
  ### Modifiers
54
54
 
55
- | Property | Docs | Example | Comments |
56
- | --------- | :-------------------------------------------------------------- | :----------------------------------------------- | :-------------------------------------------------------------------- |
57
- | width | \_ | `http://localhost:3000/width_200/buffalo.png` |
58
- | height | \_ | `http://localhost:3000/height_200/buffalo.png` |
59
- | trim | [Docs](https://sharp.pixelplumbing.com/api-resize#trim) | `http://localhost:3000/trim_100/buffalo.png` |
60
- | format | [Docs](https://sharp.pixelplumbing.com/api-output#toformat) | `http://localhost:3000/format_webp/buffalo.png` | Supported format: `jpg`, `jpeg`, `png`, `webp`, `avif`, `gif`, `heif` |
61
- | quality | \_ | `http://localhost:3000/quality_50/buffalo.png` | Accepted values: 0 to 100 |
62
- | rotate | [Docs](https://sharp.pixelplumbing.com/api-operation#rotate) | `http://localhost:3000/rotate_45/buffalo.png` |
63
- | flip | [Docs](https://sharp.pixelplumbing.com/api-operation#flip) | `http://localhost:3000/flip/buffalo.png` |
64
- | flop | [Docs](https://sharp.pixelplumbing.com/api-operation#flop) | `http://localhost:3000/flop/buffalo.png` |
65
- | sharpen | [Docs](https://sharp.pixelplumbing.com/api-operation#sharpen) | `http://localhost:3000/sharpen_30/buffalo.png` |
66
- | median | [Docs](https://sharp.pixelplumbing.com/api-operation#median) | `http://localhost:3000/median_10/buffalo.png` |
67
- | gamma | [Docs](https://sharp.pixelplumbing.com/api-operation#gamma) | `http://localhost:3000/gamma_3/buffalo.png` |
68
- | negate | [Docs](https://sharp.pixelplumbing.com/api-operation#negate) | `http://localhost:3000/negate/buffalo.png` |
69
- | normalize | [Docs](https://sharp.pixelplumbing.com/api-operation#normalize) | `http://localhost:3000/normalize/buffalo.png` |
70
- | threshold | [Docs](https://sharp.pixelplumbing.com/api-operation#threshold) | `http://localhost:3000/threshold_10/buffalo.png` |
71
- | tint | [Docs](https://sharp.pixelplumbing.com/api-colour#tint) | `http://localhost:3000/tint_1098123/buffalo.png` |
72
- | grayscale | [Docs](https://sharp.pixelplumbing.com/api-colour#grayscale) | `http://localhost:3000/grayscale/buffalo.png` |
73
- | animated | - | `http://localhost:3000/animated/buffalo.gif` | Experimental |
55
+ | Property | Docs | Example | Comments |
56
+ | ----------- | :-------------------------------------------------------------- | :------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
57
+ | width / w | \_ | `http://localhost:3000/width_200/buffalo.png` |
58
+ | height / h | \_ | `http://localhost:3000/height_200/buffalo.png` |
59
+ | resize / s | \_ | `http://localhost:3000/s_200x200/buffalo.png` |
60
+ | trim | [Docs](https://sharp.pixelplumbing.com/api-resize#trim) | `http://localhost:3000/trim_100/buffalo.png` |
61
+ | format | [Docs](https://sharp.pixelplumbing.com/api-output#toformat) | `http://localhost:3000/format_webp/buffalo.png` | Supported format: `jpg`, `jpeg`, `png`, `webp`, `avif`, `gif`, `heif` |
62
+ | quality / q | \_ | `http://localhost:3000/quality_50/buffalo.png` | Accepted values: 0 to 100 |
63
+ | rotate | [Docs](https://sharp.pixelplumbing.com/api-operation#rotate) | `http://localhost:3000/rotate_45/buffalo.png` |
64
+ | enlarge | \_ | `http://localhost:3000/enlarge,s_2000x2000/buffalo.png` | Allow the image to be upscaled. By default the returned image will never be larger than the source in any dimension, while preserving the requested aspect ratio. |
65
+ | flip | [Docs](https://sharp.pixelplumbing.com/api-operation#flip) | `http://localhost:3000/flip/buffalo.png` |
66
+ | flop | [Docs](https://sharp.pixelplumbing.com/api-operation#flop) | `http://localhost:3000/flop/buffalo.png` |
67
+ | sharpen | [Docs](https://sharp.pixelplumbing.com/api-operation#sharpen) | `http://localhost:3000/sharpen_30/buffalo.png` |
68
+ | median | [Docs](https://sharp.pixelplumbing.com/api-operation#median) | `http://localhost:3000/median_10/buffalo.png` |
69
+ | gamma | [Docs](https://sharp.pixelplumbing.com/api-operation#gamma) | `http://localhost:3000/gamma_3/buffalo.png` |
70
+ | negate | [Docs](https://sharp.pixelplumbing.com/api-operation#negate) | `http://localhost:3000/negate/buffalo.png` |
71
+ | normalize | [Docs](https://sharp.pixelplumbing.com/api-operation#normalize) | `http://localhost:3000/normalize/buffalo.png` |
72
+ | threshold | [Docs](https://sharp.pixelplumbing.com/api-operation#threshold) | `http://localhost:3000/threshold_10/buffalo.png` |
73
+ | tint | [Docs](https://sharp.pixelplumbing.com/api-colour#tint) | `http://localhost:3000/tint_1098123/buffalo.png` |
74
+ | grayscale | [Docs](https://sharp.pixelplumbing.com/api-colour#grayscale) | `http://localhost:3000/grayscale/buffalo.png` |
75
+ | animated | - | `http://localhost:3000/animated/buffalo.gif` | Experimental |
74
76
 
75
77
  ### Config
76
78
 
package/dist/cli.js CHANGED
@@ -37,6 +37,7 @@ const Handlers = /*#__PURE__*/Object.freeze({
37
37
  get quality () { return quality; },
38
38
  get fit () { return fit; },
39
39
  get background () { return background; },
40
+ get enlarge () { return enlarge; },
40
41
  get width () { return width; },
41
42
  get height () { return height; },
42
43
  get resize () { return resize; },
@@ -125,12 +126,12 @@ const createHTTPSource = (options) => {
125
126
  }
126
127
  const hosts = domains.map((domain) => ufo.parseURL(domain, "https://").host);
127
128
  return async (id, reqOptions) => {
128
- const parsedUrl = ufo.parseURL(id, "https://");
129
- if (!parsedUrl.host) {
129
+ const url = new URL(id);
130
+ if (!url.hostname) {
130
131
  throw createError("Hostname is missing: " + id, 403);
131
132
  }
132
- if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) => parsedUrl.host === host)) {
133
- throw createError("Forbidden host: " + parsedUrl.host, 403);
133
+ if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) => url.hostname === host)) {
134
+ throw createError("Forbidden host: " + url.hostname, 403);
134
135
  }
135
136
  const response = await fetch__default['default'](id, {
136
137
  agent: id.startsWith("https") ? httpsAgent : httpAgent
@@ -173,6 +174,19 @@ function applyHandler(ctx, pipe, handler, argsStr) {
173
174
  const args = handler.args ? parseArgs(argsStr, handler.args) : [];
174
175
  return handler.apply(ctx, pipe, ...args);
175
176
  }
177
+ function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
178
+ const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
179
+ let { width, height } = desiredDimensions;
180
+ if (width > sourceDimensions.width) {
181
+ width = sourceDimensions.width;
182
+ height = Math.round(sourceDimensions.width / desiredAspectRatio);
183
+ }
184
+ if (height > sourceDimensions.height) {
185
+ height = sourceDimensions.height;
186
+ width = Math.round(sourceDimensions.height * desiredAspectRatio);
187
+ }
188
+ return { width, height };
189
+ }
176
190
 
177
191
  const quality = {
178
192
  args: [VArg],
@@ -194,27 +208,40 @@ const background = {
194
208
  args: [VArg],
195
209
  order: -1,
196
210
  apply: (context, _pipe, background2) => {
211
+ background2 = String(background2);
197
212
  if (!background2.startsWith("#") && (HEX_RE.test(background2) || SHORTHEX_RE.test(background2))) {
198
213
  background2 = "#" + background2;
199
214
  }
200
215
  context.background = background2;
201
216
  }
202
217
  };
218
+ const enlarge = {
219
+ args: [],
220
+ apply: (context) => {
221
+ context.enlarge = true;
222
+ }
223
+ };
203
224
  const width = {
204
225
  args: [VArg],
205
- apply: (_context, pipe, width2) => {
206
- return pipe.resize(width2, null);
226
+ apply: (context, pipe, width2) => {
227
+ return pipe.resize(width2, null, { withoutEnlargement: !context.enlarge });
207
228
  }
208
229
  };
209
230
  const height = {
210
231
  args: [VArg],
211
- apply: (_context, pipe, height2) => {
212
- return pipe.resize(null, height2);
232
+ apply: (context, pipe, height2) => {
233
+ return pipe.resize(null, height2, { withoutEnlargement: !context.enlarge });
213
234
  }
214
235
  };
215
236
  const resize = {
216
237
  args: [VArg, VArg, VArg],
217
- apply: (context, pipe, width2, height2) => {
238
+ apply: (context, pipe, size) => {
239
+ let [width2, height2] = String(size).split("x").map((v) => Number(v));
240
+ if (!context.enlarge) {
241
+ const clamped = clampDimensionsPreservingAspectRatio(context.meta, { width: width2, height: height2 });
242
+ width2 = clamped.width;
243
+ height2 = clamped.height;
244
+ }
218
245
  return pipe.resize(width2, height2, {
219
246
  fit: context.fit,
220
247
  background: context.background
@@ -253,8 +280,10 @@ const extract = {
253
280
  };
254
281
  const rotate = {
255
282
  args: [VArg],
256
- apply: (_context, pipe, angel) => {
257
- return pipe.rotate(angel);
283
+ apply: (context, pipe, angel) => {
284
+ return pipe.rotate(angel, {
285
+ background: context.background
286
+ });
258
287
  }
259
288
  };
260
289
  const flip = {
@@ -415,7 +444,7 @@ function createIPX(userOptions) {
415
444
  const bKey = (b.handler.order || b.name || "").toString();
416
445
  return aKey.localeCompare(bKey);
417
446
  });
418
- const handlerCtx = {};
447
+ const handlerCtx = { meta };
419
448
  for (const h of handlers) {
420
449
  sharp = applyHandler(handlerCtx, sharp, h.handler, h.args) || sharp;
421
450
  }
package/dist/index.cjs CHANGED
@@ -34,6 +34,7 @@ const Handlers = /*#__PURE__*/Object.freeze({
34
34
  get quality () { return quality; },
35
35
  get fit () { return fit; },
36
36
  get background () { return background; },
37
+ get enlarge () { return enlarge; },
37
38
  get width () { return width; },
38
39
  get height () { return height; },
39
40
  get resize () { return resize; },
@@ -122,12 +123,12 @@ const createHTTPSource = (options) => {
122
123
  }
123
124
  const hosts = domains.map((domain) => ufo.parseURL(domain, "https://").host);
124
125
  return async (id, reqOptions) => {
125
- const parsedUrl = ufo.parseURL(id, "https://");
126
- if (!parsedUrl.host) {
126
+ const url = new URL(id);
127
+ if (!url.hostname) {
127
128
  throw createError("Hostname is missing: " + id, 403);
128
129
  }
129
- if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) => parsedUrl.host === host)) {
130
- throw createError("Forbidden host: " + parsedUrl.host, 403);
130
+ if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) => url.hostname === host)) {
131
+ throw createError("Forbidden host: " + url.hostname, 403);
131
132
  }
132
133
  const response = await fetch__default['default'](id, {
133
134
  agent: id.startsWith("https") ? httpsAgent : httpAgent
@@ -170,6 +171,19 @@ function applyHandler(ctx, pipe, handler, argsStr) {
170
171
  const args = handler.args ? parseArgs(argsStr, handler.args) : [];
171
172
  return handler.apply(ctx, pipe, ...args);
172
173
  }
174
+ function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
175
+ const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
176
+ let { width, height } = desiredDimensions;
177
+ if (width > sourceDimensions.width) {
178
+ width = sourceDimensions.width;
179
+ height = Math.round(sourceDimensions.width / desiredAspectRatio);
180
+ }
181
+ if (height > sourceDimensions.height) {
182
+ height = sourceDimensions.height;
183
+ width = Math.round(sourceDimensions.height * desiredAspectRatio);
184
+ }
185
+ return { width, height };
186
+ }
173
187
 
174
188
  const quality = {
175
189
  args: [VArg],
@@ -191,27 +205,40 @@ const background = {
191
205
  args: [VArg],
192
206
  order: -1,
193
207
  apply: (context, _pipe, background2) => {
208
+ background2 = String(background2);
194
209
  if (!background2.startsWith("#") && (HEX_RE.test(background2) || SHORTHEX_RE.test(background2))) {
195
210
  background2 = "#" + background2;
196
211
  }
197
212
  context.background = background2;
198
213
  }
199
214
  };
215
+ const enlarge = {
216
+ args: [],
217
+ apply: (context) => {
218
+ context.enlarge = true;
219
+ }
220
+ };
200
221
  const width = {
201
222
  args: [VArg],
202
- apply: (_context, pipe, width2) => {
203
- return pipe.resize(width2, null);
223
+ apply: (context, pipe, width2) => {
224
+ return pipe.resize(width2, null, { withoutEnlargement: !context.enlarge });
204
225
  }
205
226
  };
206
227
  const height = {
207
228
  args: [VArg],
208
- apply: (_context, pipe, height2) => {
209
- return pipe.resize(null, height2);
229
+ apply: (context, pipe, height2) => {
230
+ return pipe.resize(null, height2, { withoutEnlargement: !context.enlarge });
210
231
  }
211
232
  };
212
233
  const resize = {
213
234
  args: [VArg, VArg, VArg],
214
- apply: (context, pipe, width2, height2) => {
235
+ apply: (context, pipe, size) => {
236
+ let [width2, height2] = String(size).split("x").map((v) => Number(v));
237
+ if (!context.enlarge) {
238
+ const clamped = clampDimensionsPreservingAspectRatio(context.meta, { width: width2, height: height2 });
239
+ width2 = clamped.width;
240
+ height2 = clamped.height;
241
+ }
215
242
  return pipe.resize(width2, height2, {
216
243
  fit: context.fit,
217
244
  background: context.background
@@ -250,8 +277,10 @@ const extract = {
250
277
  };
251
278
  const rotate = {
252
279
  args: [VArg],
253
- apply: (_context, pipe, angel) => {
254
- return pipe.rotate(angel);
280
+ apply: (context, pipe, angel) => {
281
+ return pipe.rotate(angel, {
282
+ background: context.background
283
+ });
255
284
  }
256
285
  };
257
286
  const flip = {
@@ -412,7 +441,7 @@ function createIPX(userOptions) {
412
441
  const bKey = (b.handler.order || b.name || "").toString();
413
442
  return aKey.localeCompare(bKey);
414
443
  });
415
- const handlerCtx = {};
444
+ const handlerCtx = { meta };
416
445
  for (const h of handlers) {
417
446
  sharp = applyHandler(handlerCtx, sharp, h.handler, h.args) || sharp;
418
447
  }
package/dist/index.mjs CHANGED
@@ -17,6 +17,7 @@ var Handlers = /*#__PURE__*/Object.freeze({
17
17
  get quality () { return quality; },
18
18
  get fit () { return fit; },
19
19
  get background () { return background; },
20
+ get enlarge () { return enlarge; },
20
21
  get width () { return width; },
21
22
  get height () { return height; },
22
23
  get resize () { return resize; },
@@ -105,12 +106,12 @@ const createHTTPSource = (options) => {
105
106
  }
106
107
  const hosts = domains.map((domain) => parseURL(domain, "https://").host);
107
108
  return async (id, reqOptions) => {
108
- const parsedUrl = parseURL(id, "https://");
109
- if (!parsedUrl.host) {
109
+ const url = new URL(id);
110
+ if (!url.hostname) {
110
111
  throw createError("Hostname is missing: " + id, 403);
111
112
  }
112
- if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) => parsedUrl.host === host)) {
113
- throw createError("Forbidden host: " + parsedUrl.host, 403);
113
+ if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) => url.hostname === host)) {
114
+ throw createError("Forbidden host: " + url.hostname, 403);
114
115
  }
115
116
  const response = await fetch(id, {
116
117
  agent: id.startsWith("https") ? httpsAgent : httpAgent
@@ -153,6 +154,19 @@ function applyHandler(ctx, pipe, handler, argsStr) {
153
154
  const args = handler.args ? parseArgs(argsStr, handler.args) : [];
154
155
  return handler.apply(ctx, pipe, ...args);
155
156
  }
157
+ function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
158
+ const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
159
+ let { width, height } = desiredDimensions;
160
+ if (width > sourceDimensions.width) {
161
+ width = sourceDimensions.width;
162
+ height = Math.round(sourceDimensions.width / desiredAspectRatio);
163
+ }
164
+ if (height > sourceDimensions.height) {
165
+ height = sourceDimensions.height;
166
+ width = Math.round(sourceDimensions.height * desiredAspectRatio);
167
+ }
168
+ return { width, height };
169
+ }
156
170
 
157
171
  const quality = {
158
172
  args: [VArg],
@@ -174,27 +188,40 @@ const background = {
174
188
  args: [VArg],
175
189
  order: -1,
176
190
  apply: (context, _pipe, background2) => {
191
+ background2 = String(background2);
177
192
  if (!background2.startsWith("#") && (HEX_RE.test(background2) || SHORTHEX_RE.test(background2))) {
178
193
  background2 = "#" + background2;
179
194
  }
180
195
  context.background = background2;
181
196
  }
182
197
  };
198
+ const enlarge = {
199
+ args: [],
200
+ apply: (context) => {
201
+ context.enlarge = true;
202
+ }
203
+ };
183
204
  const width = {
184
205
  args: [VArg],
185
- apply: (_context, pipe, width2) => {
186
- return pipe.resize(width2, null);
206
+ apply: (context, pipe, width2) => {
207
+ return pipe.resize(width2, null, { withoutEnlargement: !context.enlarge });
187
208
  }
188
209
  };
189
210
  const height = {
190
211
  args: [VArg],
191
- apply: (_context, pipe, height2) => {
192
- return pipe.resize(null, height2);
212
+ apply: (context, pipe, height2) => {
213
+ return pipe.resize(null, height2, { withoutEnlargement: !context.enlarge });
193
214
  }
194
215
  };
195
216
  const resize = {
196
217
  args: [VArg, VArg, VArg],
197
- apply: (context, pipe, width2, height2) => {
218
+ apply: (context, pipe, size) => {
219
+ let [width2, height2] = String(size).split("x").map((v) => Number(v));
220
+ if (!context.enlarge) {
221
+ const clamped = clampDimensionsPreservingAspectRatio(context.meta, { width: width2, height: height2 });
222
+ width2 = clamped.width;
223
+ height2 = clamped.height;
224
+ }
198
225
  return pipe.resize(width2, height2, {
199
226
  fit: context.fit,
200
227
  background: context.background
@@ -233,8 +260,10 @@ const extract = {
233
260
  };
234
261
  const rotate = {
235
262
  args: [VArg],
236
- apply: (_context, pipe, angel) => {
237
- return pipe.rotate(angel);
263
+ apply: (context, pipe, angel) => {
264
+ return pipe.rotate(angel, {
265
+ background: context.background
266
+ });
238
267
  }
239
268
  };
240
269
  const flip = {
@@ -395,7 +424,7 @@ function createIPX(userOptions) {
395
424
  const bKey = (b.handler.order || b.name || "").toString();
396
425
  return aKey.localeCompare(bKey);
397
426
  });
398
- const handlerCtx = {};
427
+ const handlerCtx = { meta };
399
428
  for (const h of handlers) {
400
429
  sharp = applyHandler(handlerCtx, sharp, h.handler, h.args) || sharp;
401
430
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ipx",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "repository": "unjs/ipx",
5
5
  "license": "MIT",
6
6
  "exports": {
@@ -35,8 +35,8 @@
35
35
  "is-valid-path": "^0.1.1",
36
36
  "listhen": "^0.2.4",
37
37
  "node-fetch": "^2.6.1",
38
- "sharp": "^0.28.3",
39
- "ufo": "^0.7.7",
38
+ "sharp": "^0.29.0",
39
+ "ufo": "^0.7.9",
40
40
  "xss": "^1.0.9"
41
41
  },
42
42
  "devDependencies": {