From ad721f788db2d8cb2d467e1eceeaa3f4854cb3fa Mon Sep 17 00:00:00 2001 From: Jeremy James Date: Fri, 27 Dec 2024 15:04:19 +0000 Subject: [PATCH 1/3] Support floating point viewbox in resvg binary Avoid rounding sizes for images until creating the pixmap, and always use the ceil for that to avoid truncating images. This requires a corresponding change to tiny-skia to add scale_by/scale_to_width/scale_to_height functions to tiny_skia_path::Size to match the implementations in IntSize. --- crates/resvg/src/main.rs | 45 ++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/crates/resvg/src/main.rs b/crates/resvg/src/main.rs index c4d15283f..9394aa998 100644 --- a/crates/resvg/src/main.rs +++ b/crates/resvg/src/main.rs @@ -389,30 +389,29 @@ enum FitTo { /// Keep original size. Original, /// Scale to width. - Width(u32), + Width(f32), /// Scale to height. - Height(u32), + Height(f32), /// Scale to size. - Size(u32, u32), + Size(f32, f32), /// Zoom by factor. Zoom(f32), } impl FitTo { - fn fit_to_size(&self, size: tiny_skia::IntSize) -> Option { + fn fit_to_size(&self, size: tiny_skia::Size) -> Option { match *self { FitTo::Original => Some(size), FitTo::Width(w) => size.scale_to_width(w), FitTo::Height(h) => size.scale_to_height(h), - FitTo::Size(w, h) => tiny_skia::IntSize::from_wh(w, h).map(|s| size.scale_to(s)), + FitTo::Size(w, h) => tiny_skia::Size::from_wh(w, h).map(|s| size.scale_to(s)), FitTo::Zoom(z) => size.scale_by(z), } } - fn fit_to_transform(&self, size: tiny_skia::IntSize) -> tiny_skia::Transform { - let size1 = size.to_size(); - let size2 = match self.fit_to_size(size) { - Some(v) => v.to_size(), + fn fit_to_transform(&self, size1: tiny_skia::Size) -> tiny_skia::Transform { + let size2 = match self.fit_to_size(size1) { + Some(v) => v, None => return tiny_skia::Transform::default(), }; tiny_skia::Transform::from_scale( @@ -526,13 +525,13 @@ fn parse_args() -> Result { let mut default_size = usvg::Size::from_wh(100.0, 100.0).unwrap(); if let (Some(w), Some(h)) = (args.width, args.height) { default_size = usvg::Size::from_wh(w as f32, h as f32).unwrap(); - fit_to = FitTo::Size(w, h); + fit_to = FitTo::Size(w as f32, h as f32); } else if let Some(w) = args.width { default_size = usvg::Size::from_wh(w as f32, 100.0).unwrap(); - fit_to = FitTo::Width(w); + fit_to = FitTo::Width(w as f32); } else if let Some(h) = args.height { default_size = usvg::Size::from_wh(100.0, h as f32).unwrap(); - fit_to = FitTo::Height(h); + fit_to = FitTo::Height(h as f32); } else if let Some(z) = args.zoom { fit_to = FitTo::Zoom(z); } @@ -681,11 +680,13 @@ fn render_svg(args: &Args, tree: &usvg::Tree) -> Result Result Result Result Date: Thu, 10 Jul 2025 22:04:06 +0200 Subject: [PATCH 2/3] Link newest tiny-skia --- Cargo.lock | 6 ++---- crates/resvg/Cargo.toml | 2 +- crates/usvg/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13bf5c5d9..a8406d991 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -567,8 +567,7 @@ dependencies = [ [[package]] name = "tiny-skia" version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +source = "git+https://github.com/linebender/tiny-skia?rev=68b198a#68b198a7210a6bbf752b43d6bc4db62445730313" dependencies = [ "arrayref", "arrayvec", @@ -582,8 +581,7 @@ dependencies = [ [[package]] name = "tiny-skia-path" version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +source = "git+https://github.com/linebender/tiny-skia?rev=68b198a#68b198a7210a6bbf752b43d6bc4db62445730313" dependencies = [ "arrayref", "bytemuck", diff --git a/crates/resvg/Cargo.toml b/crates/resvg/Cargo.toml index c7ea975fd..cf45e3db0 100644 --- a/crates/resvg/Cargo.toml +++ b/crates/resvg/Cargo.toml @@ -21,7 +21,7 @@ log = "0.4" pico-args = { version = "0.5", features = ["eq-separator"] } rgb = "0.8" svgtypes = "0.15.2" -tiny-skia = "0.11.4" +tiny-skia = { git = "https://github.com/linebender/tiny-skia", rev = "68b198a" } usvg = { path = "../usvg", version = "0.44.0", default-features = false } zune-jpeg = { version = "0.4", optional = true } diff --git a/crates/usvg/Cargo.toml b/crates/usvg/Cargo.toml index b3e4b1b65..7471695c9 100644 --- a/crates/usvg/Cargo.toml +++ b/crates/usvg/Cargo.toml @@ -23,7 +23,7 @@ log = "0.4" pico-args = { version = "0.5", features = ["eq-separator"] } strict-num = "0.1.1" svgtypes = "0.15.2" -tiny-skia-path = "0.11.4" +tiny-skia-path = { git = "https://github.com/linebender/tiny-skia", rev = "68b198a" } xmlwriter = "0.1" # parser From 5f520389999b9b041d1204b48eb3c59af5562af8 Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Thu, 10 Jul 2025 22:13:59 +0200 Subject: [PATCH 3/3] Update tests --- .../text/text-rendering/optimizeSpeed.png | Bin 1754 -> 13746 bytes .../text/text-rendering/with-underline.png | Bin 837 -> 11581 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/crates/resvg/tests/tests/text/text-rendering/optimizeSpeed.png b/crates/resvg/tests/tests/text/text-rendering/optimizeSpeed.png index 535d25b0a01178079a4653f1e60111560a362115..11135d5af6da92e9353040f3c120b9ced77875cb 100644 GIT binary patch literal 13746 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 O=Z;-3p8Ko4hyMrq?(TE| literal 1754 zcmb_deKga17@wC-UWUqWc9UdYM;DojZZsA~Hq5exjkcJrTd5PS8$&6f%*zV%5+&I$ z=9fxKUaQ&E%{@2s5~WfqLMJaLg*(=N_x^Y9=bZ2J`Fx+}`99}4&-2GOlNjI)*FtJR zAP~5(k5>=`qGGpl)~G2w0aeM~3KA0iNjL@nD^gQa6@D(4tFRmnN0}2I9v&7JMkEpm z1cK7zwF;$nU4`O_qoZR=N{YfN{Wdl>=H})Kt+XbqP=<_+jg5?qlv;NcO0A`(rJ@)K{Y?(;9t0wdMyJOfK632%$>bF7nX?yid3hxz0zr92 zb$z`^bXVNk*7@LRPhbDbk=NrBQ`2+4k0U=qAkb=Guid1C!HJ$g6%P*x)B)nLaWSji zB4POsF7z*5v%!PSnmIH%2G)d^_v;%orFj;9S>Z&36daMR%t;_rBQVvjKGQa3vlsm)$?~MsP-||N1^`^q*6SMP^U9+-u4pn!v z>^Fu^`ZSSTfMDV%i4=aI+`|=;zye7hgSgc`;Y(t`Afn6;t5rNho2P^nU<58Qq`zvQ zoPN%ly}g^uxSp;yYe3OFW#+eWn|;jSLRVqo#o|*uHd%{=QSlt8+F5Y*4_DE9JX4Af ztVCVJR7hv8A?ZGg`5_%!!ea{~nNf)4@eS3lQ+Ie6gO+W%^WcNpr<=5y;OBtoq0KrJ zP=#N6v5@00_Kll)Ro31$D5j3$vuq|EjyMh4Hx(z6O{)mwn$Dki$x_+wn;$MRnAn%J z0~G{8cB3YG!G7tsF1U3bm>4l|rU4gt*f6BT7Dd6_;}*sgb@^Wzu<5|6qqMVF_}KiK z8i9VqHX}2st-qtMS<^Erbu9~93T6>u7HsLA66>`FdpjQCZ<0=-{QeYu`I`L!p%sRc zyz1&uuN)>uZ43N$xyJ&;Sk>tG$jt|RIrNzb54irt@5NVH@K4k|ym~uQf`#uYuo@X(z z+lGgONt?n=xONMoYYCLcy4sSkM=4p?K&M@}8oL2;`Y7VLdj*!j%!Iogz6{3BwCNPF zUcM6o8+0Vuz#Wv@*vx6?s+Rl+Y2~omGg(e|)&7xX*aI6cpG!|{rx?7oy`#PL{q0BJ zY+%EWC4!e1J;j(-xNSY-)n4=NcRbzP(mY{*Msi29ETe7*y4zRnz*9yh18T6Jtor@A z0Q$>!rz6t@es1u{Q88@6#rsgqGBLqUTT1r$dV;0*z3JPQOG}Z>i4l}DgfVqztK-nb zFJCqKhay_tT6@r@M3eYl`zL|Y zR*xsDsLxW}&@NO&Z%=!hV|G$OPt7_AR!cS0e?zoLi#v?xu~E2w=!QGsH+_YYIcNXCH{l8gJtouo(^X zLlUxR&|7*`(W4+^tlCW!SK6>3Gy`}gL7^I!qMbiwh&w^A{BNh2pY`M;ZJX({>}gSU z2lB>7A?_T7qh0rsG)W~jkY zm@rz=mA+ymy4=0IPjox}A#&4jLmkR9M`9|AR7^(u$E5zN@LL(PqFAc3(2oVIQ*2?8 qsSE)UNoLm@qjAP)%kZR?`JKZ9OfhRV-mQA&7x2Xec-3L)X@3KxS?@>y diff --git a/crates/resvg/tests/tests/text/text-rendering/with-underline.png b/crates/resvg/tests/tests/text/text-rendering/with-underline.png index 1e0405119b7fe34144fd0dc0a6712ab4c5f9b920..dfbde5957b8b79ce6d2b0c8e779bc8a9c59eb03d 100644 GIT binary patch literal 11581 zcmeHNe{dAl9lsC=3=|8-={ZDum8htommdy+5VExclSXNx)=Y*GF0lbmkr443xr-zl zoxvnVxKXDh$B(;CGD@3NGLkW%9=$}b(wa-QolY8&fs3KuL4sb6^~mjIZ(qM}Hz(PB z7kdA+V>>$odzt<5-sgS4pYMiwYJmT((f7(JU_f`G&_enxs|u|AG{_W2rq* ztuiFnk+-1+oUj7aD z;>jH~?=-A?Zm76-_sH9)#yUEWM;u>Q&2_GxcydeiP)TW&xt2S}kQTmwq@v#CgU26^ z(Fa>n{ z@##$y=cb3f`bBS5Qdjzj_;h}_5(e(*6pWV2k(2eqODDvq$^7clt$+e`V?4d%3(Vrv zq`JcxM+e$00;?9$_Ks^@1AwD?2(6NUNwBNyy)oDlFctRyw6wjKP5FM)h5nxWscvfO zBOn>=A{C2@9PC;LYv>7`bwtm4ff&)L(*%bidm={|?#SeccNyUdBkTfFc>^R}U4QrQ z9rQa}Dr>J9>1%s{?x>EWm~a$u<@-+@+E@Jp}@C*Yi(frlYq++JrCC4 z@{)!_9|K&BMSm^=xUK|%!9tWIdvsmxAV7QR>^}gPkgxO@7Atj2rzlflY_dZu1xOx=;eU0P2uMaHGfmzXnaWX*VgC&of5* zTc^h8L&Qdoz7vFt+U3ecoX2!?X|$MCZgnEoZHA0s*%@c~DD}Ba%Pi%v6pePEXNgS| zdc(8mZoy=u(}Iev8P7azZ@B@gJfF9iPm@~+>bCjsf)a{Pxr+?x{(@22IqO|QHJ;I! z$v9>koNq#AAJq^Z&eXSt9iQaUvms|!2o*N%QS)lHZK*yNi|SS%m#SX|e{~ASh+0at zAd7K?trf!d(XyuvYl3arF#cqw{)oBH4cP@~Wmp|T}KmHN^8V|u51-7wMSV`1%gmi2kXEFdM0Ba3v zVds5)hpUC#!vNYWc<2Xq57GNb!rX$PQqqNQqWfet2*L`ETE2`;-$_p2j&V6=1gZn2+((oF&^^9+sVE;`GC|FTMwzco!ME11A2-oyEZ?LZ%sYH)%=H;5 z^w(lirhkBiuJElN1C!=q)t$^1In*|b6m&2LS_4q@ZkZI=oj?-CAtuNh|t~& zYM8>ddL{$peTUovjaUaov6X@;eoLC-uRvCT3gKcSER!DH7nW68>~8_By7BOM4q)Ej z;t4?js&Ro83L*N|!YB2hMHgPe4bZoOj+FDZScJJ)#duXs zj8X>xcnSLDY!U;p)pK2UC?Rv%`rM$dU$o@klP0qidxTmK*iFde+OL$h*2DL^={(RT z7Ro^UC)OSR#0K$i;1jxDtS~-c5P$1*!qn+Qm4%Vo*zHFA zxXz(?DS5A0a1727f2W=wYQ7@X7Ex|I!E%z%5e~~^QaKO+TfFFyAU2JWv8fM;3C!+u z)9r-6vV+OQOf3%W)3KvCgMDJv)tNcXR&Y|ldWo5!nG*`ln1);}c*0efW+{@TfkGUi z^YGz5aBL!_Z}_1;71gD$~W_FEq5v}GLVKv@C-oHeD1bbdFklIBD z7DuXG8R$JXnC`TVb%CM9H>YOs7a~L{XpOgOi{f}w4h_JPJLYGBnygR3s4(Cf81O;m z!ch*ZE8x*KiKY@CcL^7G)(&-Vtt`-)-XIjxdb)H4I1snLaX+;j?VH);3vZT zDOp&+*~})GiB5yy{yxc=S2;&9uT^|w%m%;Qfp#d@(t%d_==nre7brLUI{xeTdeGic zPVNSUJy}fQmXP-{sq{BE??bqJe?cLQ55`b3l6pjD@ literal 837 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&7G|JGckpC4Af*`K6XFV_;h?dxv7w;>!UZzw z>+6B+Bch9z04c7LAirP+0egq|g#3cpbN*jhkjlWobkftsF{I*Fl0cADi^If&SyvTU zx+T|i^|H$vuv?vGIIvE(;Dlgvq6~lRhq`snqB1gD=dWf}cyaKv=IXib>c7j(o_b9B z^#5?$<(t1|p6THU-}YKqCt^dw!6sI2F`Wo&77)21Z#`J#wS78N9IgNlBk+65%kteD zuWVC2ebqK{)ds8FlSQ*P@%iPPi28d6Xna^ri|WBs!qyv0uB09<2|JN{$V=<0_Lj>c zVSDfR{h7JqbmTqvAIh5#J)UuP(vq|Z9P@Z|6Rs9diI_L}vdlEz%wK8q|C_!~TfRZ% zs>+_bmpOYZbyBBgM;MuG4q3L*D{b$y-3K;RuJKrtHSJa9rdg%^m1};PpT1FJz2oMZ z36ZXo`e&ccYTK1|_0OMKr;E0x$||25cx-Gg`AZ{?YxLqNcqr;&{r;lV0UB z)a4_O#*1yfqr5ujK+)|DuSB)KrTlcd-6JYhqQ1}OefJmT{@Zr-)fWRlPk!dTy?(>J zdF9jZ&E9w~D1QI{S+&UwdDj-`Lr z#S5Pf-Z<^*nyZ>$pKFDEe$ewrYTG2ytg4tW{%0GC!q)`vX`34JQbzTGO<0j?W9#S0 z%a@&sgXBL=UhE_^-Nac)}HjQ`aA0$N6Ox*3M&r{-~4O!uEXpfw`K`69pyQ6tXV*UiD6!s VwQ5vm_AyZE_jL7hS?83{1OV}wb^-tZ