diff --git a/crates/typst-layout/src/image.rs b/crates/typst-layout/src/image.rs index 84a602823..628fe10d6 100644 --- a/crates/typst-layout/src/image.rs +++ b/crates/typst-layout/src/image.rs @@ -55,6 +55,7 @@ pub fn layout_image( elem.alt(styles), engine.world, &families(styles).collect::>(), + elem.flatten_text(styles), ) .at(span)?; diff --git a/crates/typst-library/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs index 868a3c5b4..fddb4acb9 100644 --- a/crates/typst-library/src/visualize/image/mod.rs +++ b/crates/typst-library/src/visualize/image/mod.rs @@ -94,6 +94,12 @@ pub struct ImageElem { /// ``` #[default(ImageFit::Cover)] pub fit: ImageFit, + + /// Whether text in SVG images should be converted into paths before + /// embedding. This will result in the text becoming unselectable in + /// the output. + #[default(false)] + pub flatten_text: bool, } #[scope] @@ -246,13 +252,14 @@ impl Image { alt: Option, world: Tracked, families: &[&str], + flatten_text: bool, ) -> StrResult { let kind = match format { ImageFormat::Raster(format) => { ImageKind::Raster(RasterImage::new(data, format)?) } ImageFormat::Vector(VectorFormat::Svg) => { - ImageKind::Svg(SvgImage::with_fonts(data, world, families)?) + ImageKind::Svg(SvgImage::with_fonts(data, world, flatten_text, families)?) } }; diff --git a/crates/typst-library/src/visualize/image/svg.rs b/crates/typst-library/src/visualize/image/svg.rs index f7a498a83..6b6a1b6b2 100644 --- a/crates/typst-library/src/visualize/image/svg.rs +++ b/crates/typst-library/src/visualize/image/svg.rs @@ -22,6 +22,7 @@ pub struct SvgImage(Arc); struct Repr { data: Bytes, size: Axes, + flatten_text: bool, font_hash: u128, tree: usvg::Tree, } @@ -32,7 +33,13 @@ impl SvgImage { pub fn new(data: Bytes) -> StrResult { let tree = usvg::Tree::from_data(&data, &base_options()).map_err(format_usvg_error)?; - Ok(Self(Arc::new(Repr { data, size: tree_size(&tree), font_hash: 0, tree }))) + Ok(Self(Arc::new(Repr { + data, + size: tree_size(&tree), + font_hash: 0, + flatten_text: false, + tree, + }))) } /// Decode an SVG image with access to fonts. @@ -40,6 +47,7 @@ impl SvgImage { pub fn with_fonts( data: Bytes, world: Tracked, + flatten_text: bool, families: &[&str], ) -> StrResult { let book = world.book(); @@ -60,7 +68,13 @@ impl SvgImage { ) .map_err(format_usvg_error)?; let font_hash = resolver.into_inner().unwrap().finish(); - Ok(Self(Arc::new(Repr { data, size: tree_size(&tree), font_hash, tree }))) + Ok(Self(Arc::new(Repr { + data, + size: tree_size(&tree), + font_hash, + flatten_text, + tree, + }))) } /// The raw image data. @@ -73,6 +87,11 @@ impl SvgImage { self.0.size.x } + /// Whether the SVG's text should be flattened. + pub fn flatten_text(&self) -> bool { + self.0.flatten_text + } + /// The SVG's height in pixels. pub fn height(&self) -> f64 { self.0.size.y diff --git a/crates/typst-pdf/src/image.rs b/crates/typst-pdf/src/image.rs index 9651d31ba..bff7bfefa 100644 --- a/crates/typst-pdf/src/image.rs +++ b/crates/typst-pdf/src/image.rs @@ -208,7 +208,11 @@ fn encode_svg( ) -> Result<(Chunk, Ref), svg2pdf::ConversionError> { svg2pdf::to_chunk( svg.tree(), - svg2pdf::ConversionOptions { pdfa, ..Default::default() }, + svg2pdf::ConversionOptions { + pdfa, + embed_text: !svg.flatten_text(), + ..Default::default() + }, ) }