diff --git a/docs/src/content/docs/api-constructor.md b/docs/src/content/docs/api-constructor.md
index 7c7e8e9c..139e08f4 100644
--- a/docs/src/content/docs/api-constructor.md
+++ b/docs/src/content/docs/api-constructor.md
@@ -80,6 +80,9 @@ where the overall height is the `pageHeight` multiplied by the number of `pages`
| [options.join.valign] | string
| "'top'"
| vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`). |
| [options.tiff] | Object
| | Describes TIFF specific options. |
| [options.tiff.subifd] | number
| -1
| Sub Image File Directory to extract for OME-TIFF, defaults to main image. |
+| [options.svg] | Object
| | Describes SVG specific options. |
+| [options.svg.stylesheet] | string
| | Custom CSS for SVG input, applied with a User Origin during the CSS cascade. |
+| [options.svg.highBitdepth] | boolean
| false
| Set to `true` to render SVG input at 32-bits per channel (128-bit) instead of 8-bits per channel (32-bit) RGBA. |
| [options.pdf] | Object
| | Describes PDF specific options. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. |
| [options.pdf.background] | string
\| Object
| | Background colour to use when PDF is partially transparent. Parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
| [options.openSlide] | Object
| | Describes OpenSlide specific options. Requires the use of a globally-installed libvips compiled with support for OpenSlide. |
diff --git a/docs/src/content/docs/changelog.md b/docs/src/content/docs/changelog.md
index 125f774f..d39669d7 100644
--- a/docs/src/content/docs/changelog.md
+++ b/docs/src/content/docs/changelog.md
@@ -14,6 +14,8 @@ Requires libvips v8.17.0
* Deprecate top-level, format-specific constructor parameters, e.g. `subifd` becomes `tiff.subifd`.
+* Expose `stylesheet` and `highBitdepth` SVG input parameters.
+
* Expose `keepDuplicateFrames` GIF output parameter.
* Expose JPEG 2000 `oneshot` decoder option.
diff --git a/lib/constructor.js b/lib/constructor.js
index 5d86c422..daac3b5f 100644
--- a/lib/constructor.js
+++ b/lib/constructor.js
@@ -190,6 +190,9 @@ const debuglog = util.debuglog('sharp');
* @param {string} [options.join.valign='top'] - vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`).
* @param {Object} [options.tiff] - Describes TIFF specific options.
* @param {number} [options.tiff.subifd=-1] - Sub Image File Directory to extract for OME-TIFF, defaults to main image.
+ * @param {Object} [options.svg] - Describes SVG specific options.
+ * @param {string} [options.svg.stylesheet] - Custom CSS for SVG input, applied with a User Origin during the CSS cascade.
+ * @param {boolean} [options.svg.highBitdepth=false] - Set to `true` to render SVG input at 32-bits per channel (128-bit) instead of 8-bits per channel (32-bit) RGBA.
* @param {Object} [options.pdf] - Describes PDF specific options. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick.
* @param {string|Object} [options.pdf.background] - Background colour to use when PDF is partially transparent. Parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {Object} [options.openSlide] - Describes OpenSlide specific options. Requires the use of a globally-installed libvips compiled with support for OpenSlide.
diff --git a/lib/index.d.ts b/lib/index.d.ts
index 1ac6fc24..4b39148e 100644
--- a/lib/index.d.ts
+++ b/lib/index.d.ts
@@ -1005,6 +1005,8 @@ declare namespace sharp {
page?: number | undefined;
/** TIFF specific input options */
tiff?: TiffInputOptions | undefined;
+ /** SVG specific input options */
+ svg?: SvgInputOptions | undefined;
/** PDF specific input options */
pdf?: PdfInputOptions | undefined;
/** OpenSlide specific input options */
@@ -1127,6 +1129,13 @@ declare namespace sharp {
subifd?: number | undefined;
}
+ interface SvgInputOptions {
+ /** Custom CSS for SVG input, applied with a User Origin during the CSS cascade. */
+ stylesheet?: string | undefined;
+ /** Set to `true` to render SVG input at 32-bits per channel (128-bit) instead of 8-bits per channel (32-bit) RGBA. */
+ highBitdepth?: boolean | undefined;
+ }
+
interface PdfInputOptions {
/** Background colour to use when PDF is partially transparent. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. */
background?: Colour | Color | undefined;
diff --git a/lib/input.js b/lib/input.js
index d1571555..26aa2f54 100644
--- a/lib/input.js
+++ b/lib/input.js
@@ -22,14 +22,27 @@ const align = {
high: 'high'
};
+const inputStreamParameters = [
+ // Limits and error handling
+ 'failOn', 'limitInputPixels', 'unlimited',
+ // Format-generic
+ 'animated', 'autoOrient', 'density', 'ignoreIcc', 'page', 'pages', 'sequentialRead',
+ // Format-specific
+ 'jp2', 'openSlide', 'pdf', 'raw', 'svg', 'tiff',
+ // Deprecated
+ 'failOnError', 'level', 'pdfBackground', 'subifd'
+];
+
/**
* Extract input options, if any, from an object.
* @private
*/
function _inputOptionsFromObject (obj) {
- const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot } = obj;
- return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot].some(is.defined)
- ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot }
+ const params = inputStreamParameters
+ .filter(p => is.defined(obj[p]))
+ .map(p => ([p, obj[p]]));
+ return params.length
+ ? Object.fromEntries(params)
: undefined;
}
@@ -260,6 +273,23 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd);
}
}
+ // SVG specific options
+ if (is.object(inputOptions.svg)) {
+ if (is.defined(inputOptions.svg.stylesheet)) {
+ if (is.string(inputOptions.svg.stylesheet)) {
+ inputDescriptor.svgStylesheet = inputOptions.svg.stylesheet;
+ } else {
+ throw is.invalidParameterError('svg.stylesheet', 'string', inputOptions.svg.stylesheet);
+ }
+ }
+ if (is.defined(inputOptions.svg.highBitdepth)) {
+ if (is.bool(inputOptions.svg.highBitdepth)) {
+ inputDescriptor.svgHighBitdepth = inputOptions.svg.highBitdepth;
+ } else {
+ throw is.invalidParameterError('svg.highBitdepth', 'boolean', inputOptions.svg.highBitdepth);
+ }
+ }
+ }
// PDF specific options
if (is.object(inputOptions.pdf) && is.defined(inputOptions.pdf.background)) {
inputDescriptor.pdfBackground = this._getBackgroundColourOption(inputOptions.pdf.background);
diff --git a/src/common.cc b/src/common.cc
index 57ae4aef..32437068 100644
--- a/src/common.cc
+++ b/src/common.cc
@@ -101,6 +101,13 @@ namespace sharp {
if (HasAttr(input, "page")) {
descriptor->page = AttrAsUint32(input, "page");
}
+ // SVG
+ if (HasAttr(input, "svgStylesheet")) {
+ descriptor->svgStylesheet = AttrAsStr(input, "svgStylesheet");
+ }
+ if (HasAttr(input, "svgHighBitdepth")) {
+ descriptor->svgHighBitdepth = AttrAsBool(input, "svgHighBitdepth");
+ }
// Multi-level input (OpenSlide)
if (HasAttr(input, "level")) {
descriptor->level = AttrAsUint32(input, "level");
@@ -429,6 +436,10 @@ namespace sharp {
option->set("n", descriptor->pages);
option->set("page", descriptor->page);
}
+ if (imageType == ImageType::SVG) {
+ option->set("stylesheet", descriptor->svgStylesheet.data());
+ option->set("high_bitdepth", descriptor->svgHighBitdepth);
+ }
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
diff --git a/src/common.h b/src/common.h
index b926972b..0b37dbeb 100644
--- a/src/common.h
+++ b/src/common.h
@@ -77,6 +77,8 @@ namespace sharp {
std::vector joinBackground;
VipsAlign joinHalign;
VipsAlign joinValign;
+ std::string svgStylesheet;
+ bool svgHighBitdepth;
std::vector pdfBackground;
bool jp2Oneshot;
@@ -121,6 +123,7 @@ namespace sharp {
joinBackground{ 0.0, 0.0, 0.0, 255.0 },
joinHalign(VIPS_ALIGN_LOW),
joinValign(VIPS_ALIGN_LOW),
+ svgHighBitdepth(false),
pdfBackground{ 255.0, 255.0, 255.0, 255.0 },
jp2Oneshot(false) {}
};
diff --git a/test/types/sharp.test-d.ts b/test/types/sharp.test-d.ts
index 8575f1e6..d00cbd29 100644
--- a/test/types/sharp.test-d.ts
+++ b/test/types/sharp.test-d.ts
@@ -730,6 +730,9 @@ sharp({ openSlide: { level: 0 } });
sharp({ level: 0 }); // Deprecated
sharp({ jp2: { oneshot: true } });
sharp({ jp2: { oneshot: false } });
+sharp({ svg: { stylesheet: 'test' }});
+sharp({ svg: { highBitdepth: true }});
+sharp({ svg: { highBitdepth: false }});
sharp({ autoOrient: true });
sharp({ autoOrient: false });
diff --git a/test/unit/svg.js b/test/unit/svg.js
index 8f147b0a..6591cf2d 100644
--- a/test/unit/svg.js
+++ b/test/unit/svg.js
@@ -139,6 +139,41 @@ describe('SVG input', function () {
assert.strictEqual(info.channels, 4);
});
+ it('Can apply custom CSS', async () => {
+ const svg = `
+ `;
+ const stylesheet = 'circle { fill: red }';
+
+ const [r, g, b, a] = await sharp(Buffer.from(svg), { svg: { stylesheet } })
+ .extract({ left: 5, top: 5, width: 1, height: 1 })
+ .raw()
+ .toBuffer();
+
+ assert.deepEqual([r, g, b, a], [255, 0, 0, 255]);
+ });
+
+ it('Invalid stylesheet input option throws', () =>
+ assert.throws(
+ () => sharp({ svg: { stylesheet: 123 } }),
+ /Expected string for svg\.stylesheet but received 123 of type number/
+ )
+ );
+
+ it('Valid highBitdepth input option does not throw', () =>
+ assert.doesNotThrow(
+ () => sharp({ svg: { highBitdepth: true } })
+ )
+ );
+
+ it('Invalid highBitdepth input option throws', () =>
+ assert.throws(
+ () => sharp({ svg: { highBitdepth: 123 } }),
+ /Expected boolean for svg\.highBitdepth but received 123 of type number/
+ )
+ );
+
it('Fails to render SVG larger than 32767x32767', () =>
assert.rejects(
() => sharp(Buffer.from('')).toBuffer(),