Skip to content

Commit c4b1d80

Browse files
committed
Expose stylesheet and highBitdepth SVG input params
1 parent f92540f commit c4b1d80

File tree

9 files changed

+102
-3
lines changed

9 files changed

+102
-3
lines changed

docs/src/content/docs/api-constructor.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ where the overall height is the `pageHeight` multiplied by the number of `pages`
8080
| [options.join.valign] | <code>string</code> | <code>&quot;&#x27;top&#x27;&quot;</code> | vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`). |
8181
| [options.tiff] | <code>Object</code> | | Describes TIFF specific options. |
8282
| [options.tiff.subifd] | <code>number</code> | <code>-1</code> | Sub Image File Directory to extract for OME-TIFF, defaults to main image. |
83+
| [options.svg] | <code>Object</code> | | Describes SVG specific options. |
84+
| [options.svg.stylesheet] | <code>string</code> | | Custom CSS for SVG input, applied with a User Origin during the CSS cascade. |
85+
| [options.svg.highBitdepth] | <code>boolean</code> | <code>false</code> | Set to `true` to render SVG input at 32-bits per channel (128-bit) instead of 8-bits per channel (32-bit) RGBA. |
8386
| [options.pdf] | <code>Object</code> | | Describes PDF specific options. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. |
8487
| [options.pdf.background] | <code>string</code> \| <code>Object</code> | | 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. |
8588
| [options.openSlide] | <code>Object</code> | | Describes OpenSlide specific options. Requires the use of a globally-installed libvips compiled with support for OpenSlide. |

docs/src/content/docs/changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Requires libvips v8.17.0
1414

1515
* Deprecate top-level, format-specific constructor parameters, e.g. `subifd` becomes `tiff.subifd`.
1616

17+
* Expose `stylesheet` and `highBitdepth` SVG input parameters.
18+
1719
* Expose `keepDuplicateFrames` GIF output parameter.
1820

1921
* Expose JPEG 2000 `oneshot` decoder option.

lib/constructor.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ const debuglog = util.debuglog('sharp');
190190
* @param {string} [options.join.valign='top'] - vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`).
191191
* @param {Object} [options.tiff] - Describes TIFF specific options.
192192
* @param {number} [options.tiff.subifd=-1] - Sub Image File Directory to extract for OME-TIFF, defaults to main image.
193+
* @param {Object} [options.svg] - Describes SVG specific options.
194+
* @param {string} [options.svg.stylesheet] - Custom CSS for SVG input, applied with a User Origin during the CSS cascade.
195+
* @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.
193196
* @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.
194197
* @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.
195198
* @param {Object} [options.openSlide] - Describes OpenSlide specific options. Requires the use of a globally-installed libvips compiled with support for OpenSlide.

lib/index.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,8 @@ declare namespace sharp {
10051005
page?: number | undefined;
10061006
/** TIFF specific input options */
10071007
tiff?: TiffInputOptions | undefined;
1008+
/** SVG specific input options */
1009+
svg?: SvgInputOptions | undefined;
10081010
/** PDF specific input options */
10091011
pdf?: PdfInputOptions | undefined;
10101012
/** OpenSlide specific input options */
@@ -1127,6 +1129,13 @@ declare namespace sharp {
11271129
subifd?: number | undefined;
11281130
}
11291131

1132+
interface SvgInputOptions {
1133+
/** Custom CSS for SVG input, applied with a User Origin during the CSS cascade. */
1134+
stylesheet?: string | undefined;
1135+
/** Set to `true` to render SVG input at 32-bits per channel (128-bit) instead of 8-bits per channel (32-bit) RGBA. */
1136+
highBitdepth?: boolean | undefined;
1137+
}
1138+
11301139
interface PdfInputOptions {
11311140
/** 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. */
11321141
background?: Colour | Color | undefined;

lib/input.js

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,27 @@ const align = {
2222
high: 'high'
2323
};
2424

25+
const inputStreamParameters = [
26+
// Limits and error handling
27+
'failOn', 'limitInputPixels', 'unlimited',
28+
// Format-generic
29+
'animated', 'autoOrient', 'density', 'ignoreIcc', 'page', 'pages', 'sequentialRead',
30+
// Format-specific
31+
'jp2', 'openSlide', 'pdf', 'raw', 'svg', 'tiff',
32+
// Deprecated
33+
'failOnError', 'level', 'pdfBackground', 'subifd'
34+
];
35+
2536
/**
2637
* Extract input options, if any, from an object.
2738
* @private
2839
*/
2940
function _inputOptionsFromObject (obj) {
30-
const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot } = obj;
31-
return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot].some(is.defined)
32-
? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot }
41+
const params = inputStreamParameters
42+
.filter(p => is.defined(obj[p]))
43+
.map(p => ([p, obj[p]]));
44+
return params.length
45+
? Object.fromEntries(params)
3346
: undefined;
3447
}
3548

@@ -260,6 +273,23 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
260273
throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd);
261274
}
262275
}
276+
// SVG specific options
277+
if (is.object(inputOptions.svg)) {
278+
if (is.defined(inputOptions.svg.stylesheet)) {
279+
if (is.string(inputOptions.svg.stylesheet)) {
280+
inputDescriptor.svgStylesheet = inputOptions.svg.stylesheet;
281+
} else {
282+
throw is.invalidParameterError('svg.stylesheet', 'string', inputOptions.svg.stylesheet);
283+
}
284+
}
285+
if (is.defined(inputOptions.svg.highBitdepth)) {
286+
if (is.bool(inputOptions.svg.highBitdepth)) {
287+
inputDescriptor.svgHighBitdepth = inputOptions.svg.highBitdepth;
288+
} else {
289+
throw is.invalidParameterError('svg.highBitdepth', 'boolean', inputOptions.svg.highBitdepth);
290+
}
291+
}
292+
}
263293
// PDF specific options
264294
if (is.object(inputOptions.pdf) && is.defined(inputOptions.pdf.background)) {
265295
inputDescriptor.pdfBackground = this._getBackgroundColourOption(inputOptions.pdf.background);

src/common.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ namespace sharp {
101101
if (HasAttr(input, "page")) {
102102
descriptor->page = AttrAsUint32(input, "page");
103103
}
104+
// SVG
105+
if (HasAttr(input, "svgStylesheet")) {
106+
descriptor->svgStylesheet = AttrAsStr(input, "svgStylesheet");
107+
}
108+
if (HasAttr(input, "svgHighBitdepth")) {
109+
descriptor->svgHighBitdepth = AttrAsBool(input, "svgHighBitdepth");
110+
}
104111
// Multi-level input (OpenSlide)
105112
if (HasAttr(input, "level")) {
106113
descriptor->level = AttrAsUint32(input, "level");
@@ -429,6 +436,10 @@ namespace sharp {
429436
option->set("n", descriptor->pages);
430437
option->set("page", descriptor->page);
431438
}
439+
if (imageType == ImageType::SVG) {
440+
option->set("stylesheet", descriptor->svgStylesheet.data());
441+
option->set("high_bitdepth", descriptor->svgHighBitdepth);
442+
}
432443
if (imageType == ImageType::OPENSLIDE) {
433444
option->set("level", descriptor->level);
434445
}

src/common.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ namespace sharp {
7777
std::vector<double> joinBackground;
7878
VipsAlign joinHalign;
7979
VipsAlign joinValign;
80+
std::string svgStylesheet;
81+
bool svgHighBitdepth;
8082
std::vector<double> pdfBackground;
8183
bool jp2Oneshot;
8284

@@ -121,6 +123,7 @@ namespace sharp {
121123
joinBackground{ 0.0, 0.0, 0.0, 255.0 },
122124
joinHalign(VIPS_ALIGN_LOW),
123125
joinValign(VIPS_ALIGN_LOW),
126+
svgHighBitdepth(false),
124127
pdfBackground{ 255.0, 255.0, 255.0, 255.0 },
125128
jp2Oneshot(false) {}
126129
};

test/types/sharp.test-d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,9 @@ sharp({ openSlide: { level: 0 } });
730730
sharp({ level: 0 }); // Deprecated
731731
sharp({ jp2: { oneshot: true } });
732732
sharp({ jp2: { oneshot: false } });
733+
sharp({ svg: { stylesheet: 'test' }});
734+
sharp({ svg: { highBitdepth: true }});
735+
sharp({ svg: { highBitdepth: false }});
733736

734737
sharp({ autoOrient: true });
735738
sharp({ autoOrient: false });

test/unit/svg.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,41 @@ describe('SVG input', function () {
139139
assert.strictEqual(info.channels, 4);
140140
});
141141

142+
it('Can apply custom CSS', async () => {
143+
const svg = `<?xml version="1.0" encoding="UTF-8"?>
144+
<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg">
145+
<circle cx="5" cy="5" r="4" fill="green" />
146+
</svg>`;
147+
const stylesheet = 'circle { fill: red }';
148+
149+
const [r, g, b, a] = await sharp(Buffer.from(svg), { svg: { stylesheet } })
150+
.extract({ left: 5, top: 5, width: 1, height: 1 })
151+
.raw()
152+
.toBuffer();
153+
154+
assert.deepEqual([r, g, b, a], [255, 0, 0, 255]);
155+
});
156+
157+
it('Invalid stylesheet input option throws', () =>
158+
assert.throws(
159+
() => sharp({ svg: { stylesheet: 123 } }),
160+
/Expected string for svg\.stylesheet but received 123 of type number/
161+
)
162+
);
163+
164+
it('Valid highBitdepth input option does not throw', () =>
165+
assert.doesNotThrow(
166+
() => sharp({ svg: { highBitdepth: true } })
167+
)
168+
);
169+
170+
it('Invalid highBitdepth input option throws', () =>
171+
assert.throws(
172+
() => sharp({ svg: { highBitdepth: 123 } }),
173+
/Expected boolean for svg\.highBitdepth but received 123 of type number/
174+
)
175+
);
176+
142177
it('Fails to render SVG larger than 32767x32767', () =>
143178
assert.rejects(
144179
() => sharp(Buffer.from('<svg xmlns="http://www.w3.org/2000/svg" width="32768" height="1" />')).toBuffer(),

0 commit comments

Comments
 (0)