Skip to content

Commit 5b916b3

Browse files
committed
feat(image): implement svg_min
1 parent 11e93a8 commit 5b916b3

8 files changed

Lines changed: 151 additions & 3 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@ Cargo.lock
1414
.turbo
1515
*.tsbuildinfo
1616
optimized-lossless.*
17+
optimized.svg
1718
quantized.png
19+
lib
20+
dist

optimize-test.js

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,66 @@
11
const { readFileSync, writeFileSync } = require('fs')
22

3-
const { losslessCompressPng, compressJpeg, pngQuantize } = require('./packages/binding')
3+
const { losslessCompressPng, compressJpeg, pngQuantize, svgMin } = require('./packages/binding')
44

55
const PNG = readFileSync('./un-optimized.png')
6+
const SVG = `<?xml version="1.0" encoding="iso-8859-1"?>
7+
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
8+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
9+
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
10+
width="489.589px" height="489.589px" viewBox="0 0 489.589 489.589" style="enable-background:new 0 0 489.589 489.589;"
11+
xml:space="preserve">
12+
<g>
13+
<g>
14+
<path d="M484.536,30.412c-1.034,3.078-2.545,6.233-4.652,9.279c-7.39,10.654-18.507,15.733-24.838,11.343
15+
c-3.896-2.707-5.046-8.383-3.635-14.827c-6.448,1.404-12.127,0.258-14.836-3.645c-4.384-6.324,0.701-17.44,11.35-24.832
16+
c3.041-2.108,6.199-3.625,9.28-4.652l-1.698-1.687L427.246,0l-10.531,10.545c0.204,12.864-5.843,27.475-17.549,39.17
17+
c-11.698,11.7-26.305,17.751-39.177,17.544l-0.705,0.709c7.942,9.993,5.113,26.908-6.645,39.649l-97.701,99.254
18+
c-16.212-1.154-40.134-7.153-48.211-15.222c-15.333-15.333-3.178-38.175,25.773-48.339c12.693-4.462,31.346-12.728,48.71-17.909
19+
l-95.248-0.21l-25.812,109.717l-79.421-17.376L0.782,363.311l90.57-47.548l-1.03,1.026c3.675,1.671,7.206,3.45,10.614,5.289
20+
c-7.065,8.54-6.696,21.16,1.305,29.154l36.904,36.9c8.421,8.432,22.019,8.464,30.529,0.185c3.274,5.55,4.829,8.984,4.829,8.984
21+
l2.136-2.132l-49.56,94.419l145.77-79.928l-17.895-81.815l108.21-25.455l-0.204-94.904c-5.186,17.288-13.397,35.814-17.833,48.445
22+
c-10.167,28.95-33.014,41.093-48.338,25.776c-7.979-7.987-13.914-31.387-15.156-47.634l99.048-100.611
23+
c12.819-11.066,29.378-13.393,38.992-5.171c-0.673-13.188,5.422-28.427,17.532-40.539c12.712-12.711,28.85-18.755,42.467-17.334
24+
l9.137-9.137L487.43,33.31L484.536,30.412z"/>
25+
</g>
26+
</g>
27+
<g>
28+
</g>
29+
<g>
30+
</g>
31+
<g>
32+
</g>
33+
<g>
34+
</g>
35+
<g>
36+
</g>
37+
<g>
38+
</g>
39+
<g>
40+
</g>
41+
<g>
42+
</g>
43+
<g>
44+
</g>
45+
<g>
46+
</g>
47+
<g>
48+
</g>
49+
<g>
50+
</g>
51+
<g>
52+
</g>
53+
<g>
54+
</g>
55+
<g>
56+
</g>
57+
</svg>
58+
`
659

760
writeFileSync('optimized-lossless.png', losslessCompressPng(PNG))
861

962
writeFileSync('quantized.png', pngQuantize(PNG))
1063

1164
writeFileSync('optimized-lossless.jpg', compressJpeg(readFileSync('./un-optimized.jpg')))
65+
66+
writeFileSync('optimized.svg', svgMin(SVG))

packages/binding/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ libc = "0.2"
1717
lodepng = "3"
1818
napi = {version = "2", default-features = false, features = ["napi3"]}
1919
napi-derive = {version = "2", default-features = false, features = ["type-def"]}
20+
usvg = {version = "0.20", features = ["export"]}
21+
xmlwriter = "0.1"
2022

2123
[dependencies.oxipng]
2224
default-features = false

packages/binding/index.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,16 @@ export interface PngQuantOptions {
7171
posterization?: number | undefined | null
7272
}
7373
export function pngQuantize(input: Buffer, options?: PngQuantOptions | undefined | null): Buffer
74+
export const enum Ident {
75+
None = 0,
76+
Two = 2,
77+
Four = 4,
78+
Tab = 5
79+
}
80+
export interface SvgMinOptions {
81+
idPrefix?: string | undefined | null
82+
useSingleQuote?: boolean | undefined | null
83+
indent?: Ident | undefined | null
84+
attributesIndent?: Ident | undefined | null
85+
}
86+
export function svgMin(input: string | Buffer, options?: SvgMinOptions | undefined | null): string

packages/binding/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,10 @@ if (!nativeBinding) {
221221
throw new Error(`Failed to load native binding`)
222222
}
223223

224-
const { losslessCompressPng, compressJpeg, pngQuantize } = nativeBinding
224+
const { losslessCompressPng, compressJpeg, pngQuantize, Ident, svgMin } = nativeBinding
225225

226226
module.exports.losslessCompressPng = losslessCompressPng
227227
module.exports.compressJpeg = compressJpeg
228228
module.exports.pngQuantize = pngQuantize
229+
module.exports.Ident = Ident
230+
module.exports.svgMin = svgMin

packages/binding/src/lib.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,77 @@ pub fn png_quantize(input: Buffer, options: Option<PngQuantOptions>) -> Result<B
260260
})?;
261261
Ok(output.into())
262262
}
263+
264+
#[napi]
265+
pub enum Ident {
266+
None,
267+
Two = 2,
268+
Four = 4,
269+
Tab,
270+
}
271+
272+
#[napi(object)]
273+
pub struct SvgMinOptions {
274+
pub id_prefix: Option<String>,
275+
pub use_single_quote: Option<bool>,
276+
pub indent: Option<Ident>,
277+
pub attributes_indent: Option<Ident>,
278+
}
279+
280+
#[inline(always)]
281+
fn ident_to_xml_ident(id: &Ident) -> xmlwriter::Indent {
282+
match id {
283+
&Ident::None => xmlwriter::Indent::None,
284+
&Ident::Two => xmlwriter::Indent::Spaces(2),
285+
&Ident::Four => xmlwriter::Indent::Spaces(4),
286+
&Ident::Tab => xmlwriter::Indent::Tabs,
287+
}
288+
}
289+
290+
#[napi]
291+
pub fn svg_min(input: Either<String, Buffer>, options: Option<SvgMinOptions>) -> Result<String> {
292+
let (tree, len) = match &input {
293+
Either::A(s) => {
294+
usvg::Tree::from_str(s.as_str(), &usvg::Options::default().to_ref()).map(|t| (t, s.len()))
295+
}
296+
Either::B(b) => {
297+
usvg::Tree::from_data(b.as_ref(), &usvg::Options::default().to_ref()).map(|t| (t, b.len()))
298+
}
299+
}
300+
.map_err(|err| {
301+
Error::new(
302+
Status::InvalidArg,
303+
format!("Parse svg from input data failed {}", err),
304+
)
305+
})?;
306+
let options = options.unwrap_or(SvgMinOptions {
307+
id_prefix: None,
308+
use_single_quote: Some(true),
309+
indent: Some(Ident::None),
310+
attributes_indent: Some(Ident::None),
311+
});
312+
let result = tree.to_string(&usvg::XmlOptions {
313+
id_prefix: options.id_prefix,
314+
writer_opts: xmlwriter::Options {
315+
use_single_quote: options.use_single_quote.unwrap_or(true),
316+
indent: options
317+
.indent
318+
.as_ref()
319+
.map(ident_to_xml_ident)
320+
.unwrap_or(xmlwriter::Indent::None),
321+
attributes_indent: options
322+
.attributes_indent
323+
.as_ref()
324+
.map(ident_to_xml_ident)
325+
.unwrap_or(xmlwriter::Indent::None),
326+
},
327+
});
328+
if result.len() < len {
329+
Ok(result)
330+
} else {
331+
Ok(match input {
332+
Either::A(a) => a,
333+
Either::B(b) => unsafe { String::from_utf8_unchecked(b.to_vec()) },
334+
})
335+
}
336+
}

packages/rollup-plugin/lib/index.d.ts

Whitespace-only changes.

packages/rollup-plugin/lib/index.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)