diff --git a/Cargo.lock b/Cargo.lock index 92ae6f32b..de95a90ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1735,12 +1735,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rctree" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" - [[package]] name = "redox_syscall" version = "0.4.1" @@ -1798,9 +1792,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "resvg" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" +checksum = "5c34501046959e06470ba62a2dc7f31c15f94ac250d842a45f9e012f4ee40c1e" dependencies = [ "gif", "jpeg-decoder", @@ -2170,12 +2164,14 @@ checksum = "09eab8a83bff89ba2200bd4c59be45c7c787f988431b936099a5a266c957f2f9" [[package]] name = "svg2pdf" version = "0.9.1" -source = "git+https://github.com/typst/svg2pdf?rev=11f34c7#11f34c7f407d504c49db19e4e2b521fce4c8622f" +source = "git+https://github.com/typst/svg2pdf?rev=49891ef#49891ef48eee1a03f2ed090541d88fd0193bf2c9" dependencies = [ "image", "miniz_oxide", "once_cell", "pdf-writer", + "resvg", + "tiny-skia", "usvg", ] @@ -2859,9 +2855,9 @@ dependencies = [ [[package]] name = "usvg" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756" +checksum = "377f62b4a3c173de8654c1aa80ab1dac1154e6f13a779a9943e53780120d1625" dependencies = [ "base64", "log", @@ -2874,9 +2870,9 @@ dependencies = [ [[package]] name = "usvg-parser" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" +checksum = "351a05e6f2023d6b4e946f734240a3927aefdcf930d7d42587a2c8a8869814b0" dependencies = [ "data-url", "flate2", @@ -2892,9 +2888,9 @@ dependencies = [ [[package]] name = "usvg-text-layout" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d383a3965de199d7f96d4e11a44dd859f46e86de7f3dca9a39bf82605da0a37c" +checksum = "8c41888b9d5cf431fe852eaf9d047bbde83251b98f1749c2f08b1071e6db46e2" dependencies = [ "fontdb", "kurbo", @@ -2908,11 +2904,10 @@ dependencies = [ [[package]] name = "usvg-tree" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3" +checksum = "18863e0404ed153d6e56362c5b1146db9f4f262a3244e3cf2dbe7d8a85909f05" dependencies = [ - "rctree", "strict-num", "svgtypes", "tiny-skia-path", diff --git a/Cargo.toml b/Cargo.toml index ad1d416a5..8f93a1901 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ pulldown-cmark = "0.9" quote = "1" rayon = "1.7.0" regex = "1" -resvg = { version = "0.37.0", default-features = false, features = ["raster-images"] } +resvg = { version = "0.38.0", default-features = false, features = ["raster-images"] } roxmltree = "0.19" rustls = "0.21" # in sync with ureq rustls-pemfile = "1" # in sync with rustls @@ -91,7 +91,7 @@ siphasher = "1" smallvec = { version = "1.11.1", features = ["union", "const_generics", "const_new"] } stacker = "0.1.15" subsetter = "0.1.1" -svg2pdf = { git = "https://github.com/typst/svg2pdf", rev = "11f34c7" } +svg2pdf = { git = "https://github.com/typst/svg2pdf", rev = "49891ef" } syn = { version = "2", features = ["full", "extra-traits"] } syntect = { version = "5", default-features = false, features = ["parsing", "regex-fancy", "plist-load", "yaml-load"] } tar = "0.4" @@ -111,7 +111,7 @@ unicode-script = "0.5" unicode-segmentation = "1" unscanny = "0.1" ureq = "2" -usvg = { version = "0.37", default-features = false, features = ["text"] } +usvg = { version = "0.38.0", default-features = false, features = ["text"] } walkdir = "2" wasmi = "0.31.0" xmlparser = "0.13.5" diff --git a/crates/typst-render/src/lib.rs b/crates/typst-render/src/lib.rs index 5718a7c0c..afdfeeef1 100644 --- a/crates/typst-render/src/lib.rs +++ b/crates/typst-render/src/lib.rs @@ -19,7 +19,7 @@ use typst::visualize::{ Color, DashPattern, FixedStroke, Geometry, Gradient, Image, ImageKind, LineCap, LineJoin, Paint, Path, PathItem, Pattern, RasterFormat, RelativeTo, Shape, }; -use usvg::{NodeExt, TreeParsing}; +use usvg::TreeParsing; /// Export a frame into a raster image. /// @@ -272,8 +272,8 @@ fn render_svg_glyph( // Parse SVG. let opts = usvg::Options::default(); - let usvg_tree = usvg::Tree::from_xmltree(&document, &opts).ok()?; - let tree = resvg::Tree::from_usvg(&usvg_tree); + let mut tree = usvg::Tree::from_xmltree(&document, &opts).ok()?; + tree.calculate_bounding_boxes(); let view_box = tree.view_box.rect; // If there's no viewbox defined, use the em square for our scale @@ -298,10 +298,8 @@ fn render_svg_glyph( // See https://github.com/RazrFalcon/resvg/issues/602 for why // using the svg size is problematic here. let mut bbox = usvg::BBox::default(); - for node in usvg_tree.root.descendants() { - if let Some(rect) = node.calculate_bbox() { - bbox = bbox.expand(rect); - } + if let Some(tree_bbox) = tree.root.bounding_box { + bbox = bbox.expand(tree_bbox); } // Compute the bbox after the transform is applied. @@ -320,7 +318,7 @@ fn render_svg_glyph( // We offset our transform so that the pixmap starts at the edge of the bbox. let ts = ts.post_translate(-bbox.left() as f32, -bbox.top() as f32); - tree.render(ts, &mut pixmap.as_mut()); + resvg::render(&tree, ts, &mut pixmap.as_mut()); canvas.draw_pixmap( bbox.left(), @@ -757,12 +755,11 @@ fn scaled_texture(image: &Image, w: u32, h: u32) -> Option> { // of `with`. ImageKind::Svg(svg) => unsafe { svg.with(|tree| { - let tree = resvg::Tree::from_usvg(tree); let ts = tiny_skia::Transform::from_scale( w as f32 / tree.size.width(), h as f32 / tree.size.height(), ); - tree.render(ts, &mut pixmap.as_mut()) + resvg::render(tree, ts, &mut pixmap.as_mut()) }); }, } diff --git a/crates/typst/src/visualize/image/svg.rs b/crates/typst/src/visualize/image/svg.rs index a4cf3807c..20e1b0b9a 100644 --- a/crates/typst/src/visualize/image/svg.rs +++ b/crates/typst/src/visualize/image/svg.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use comemo::Tracked; use ecow::EcoString; use siphasher::sip128::Hasher128; -use usvg::{NodeExt, TreeParsing, TreeTextToPath}; +use usvg::{Node, PostProcessingSteps, TreeParsing, TreePostProc}; use crate::diag::{format_xml_like_error, StrResult}; use crate::foundations::Bytes; @@ -56,10 +56,11 @@ impl SvgImage { let mut tree = usvg::Tree::from_data(&data, &opts).map_err(format_usvg_error)?; let mut font_hash = 0; if tree.has_text_nodes() { - let (fontdb, hash) = load_svg_fonts(world, &tree, families); - tree.convert_text(&fontdb); + let (fontdb, hash) = load_svg_fonts(world, &mut tree, families); + tree.postprocess(PostProcessingSteps::default(), &fontdb); font_hash = hash; } + tree.calculate_bounding_boxes(); Ok(Self(Arc::new(Repr { data, size: tree_size(&tree), @@ -128,7 +129,7 @@ impl Hash for Repr { /// Discover and load the fonts referenced by an SVG. fn load_svg_fonts( world: Tracked, - tree: &usvg::Tree, + tree: &mut usvg::Tree, families: &[String], ) -> (fontdb::Database, u128) { let book = world.book(); @@ -153,56 +154,70 @@ fn load_svg_fonts( }; // Determine the best font for each text node. - traverse_svg(&tree.root, &mut |node| { - let usvg::NodeKind::Text(text) = &mut *node.borrow_mut() else { return }; - for chunk in &mut text.chunks { - 'spans: for span in &mut chunk.spans { - let Some(text) = chunk.text.get(span.start..span.end) else { continue }; - let variant = FontVariant { - style: span.font.style.into(), - weight: FontWeight::from_number(span.font.weight), - stretch: span.font.stretch.into(), - }; - - // Find a font that covers the whole text among the span's fonts - // and the current document font families. - let mut like = None; - for family in span.font.families.iter().chain(families) { - let Some(id) = book.select(&family.to_lowercase(), variant) else { + for child in &mut tree.root.children { + traverse_svg(child, &mut |node| { + let usvg::Node::Text(ref mut text) = node else { return }; + for chunk in &mut text.chunks { + 'spans: for span in &mut chunk.spans { + let Some(text) = chunk.text.get(span.start..span.end) else { continue; }; - let Some(info) = book.info(id) else { continue }; - like.get_or_insert(info); + let variant = FontVariant { + style: span.font.style.into(), + weight: FontWeight::from_number(span.font.weight), + stretch: span.font.stretch.into(), + }; - if text.chars().all(|c| info.coverage.contains(c as u32)) { + // Find a font that covers the whole text among the span's fonts + // and the current document font families. + let mut like = None; + for family in span.font.families.iter().chain(families) { + let Some(id) = book.select(&family.to_lowercase(), variant) + else { + continue; + }; + let Some(info) = book.info(id) else { continue }; + like.get_or_insert(info); + + if text.chars().all(|c| info.coverage.contains(c as u32)) { + if let Some(usvg_family) = load_into_db(id) { + span.font.families = vec![usvg_family]; + continue 'spans; + } + } + } + + // If we didn't find a match, select a fallback font. + if let Some(id) = book.select_fallback(like, variant, text) { if let Some(usvg_family) = load_into_db(id) { span.font.families = vec![usvg_family]; - continue 'spans; } } } - - // If we didn't find a match, select a fallback font. - if let Some(id) = book.select_fallback(like, variant, text) { - if let Some(usvg_family) = load_into_db(id) { - span.font.families = vec![usvg_family]; - } - } } - } - }); + }); + } (fontdb, hasher.finish128().as_u128()) } /// Search for all font families referenced by an SVG. -fn traverse_svg(node: &usvg::Node, f: &mut F) +fn traverse_svg(node: &mut usvg::Node, f: &mut F) where - F: FnMut(&usvg::Node), + F: FnMut(&mut usvg::Node), { - for descendant in node.descendants() { - f(&descendant); - descendant.subroots(|subroot| traverse_svg(&subroot, f)) + f(node); + + node.subroots_mut(|subroot| { + for child in &mut subroot.children { + traverse_svg(child, f); + } + }); + + if let Node::Group(ref mut group) = node { + for child in &mut group.children { + traverse_svg(child, f); + } } }