From 4993bae782f7fa17bc9ae90700841bc842a7d416 Mon Sep 17 00:00:00 2001 From: frozolotl Date: Fri, 13 Dec 2024 16:30:13 +0100 Subject: [PATCH] refactor: merge Image::new and Image::with_fonts --- crates/typst-layout/src/image.rs | 16 ++-- crates/typst-library/src/text/font/color.rs | 19 ++-- .../typst-library/src/visualize/image/mod.rs | 95 ++++++++----------- .../typst-library/src/visualize/image/svg.rs | 79 ++++++++------- crates/typst-svg/src/text.rs | 9 +- tests/suite/visualize/image.typ | 2 +- 6 files changed, 109 insertions(+), 111 deletions(-) diff --git a/crates/typst-layout/src/image.rs b/crates/typst-layout/src/image.rs index 1ab90c3c8..79c2c73d6 100644 --- a/crates/typst-layout/src/image.rs +++ b/crates/typst-layout/src/image.rs @@ -10,8 +10,8 @@ use typst_library::layout::{ use typst_library::loading::Readable; use typst_library::text::families; use typst_library::visualize::{ - Curve, Image, ImageElem, ImageFit, ImageFormat, ImageSource, RasterFormat, - VectorFormat, + Curve, Image, ImageElem, ImageFit, ImageFormat, ImageOptions, ImageSource, + RasterFormat, VectorFormat, }; /// Layout the image. @@ -57,13 +57,15 @@ pub fn layout_image( } // Construct the image itself. - let image = Image::with_fonts( + let image = Image::new( source.clone(), format, - elem.alt(styles), - engine.world, - &families(styles).map(|f| f.as_str()).collect::>(), - elem.flatten_text(styles), + &ImageOptions { + alt: elem.alt(styles), + world: Some(engine.world), + families: &families(styles).map(|f| f.as_str()).collect::>(), + flatten_text: elem.flatten_text(styles), + }, ) .at(span)?; diff --git a/crates/typst-library/src/text/font/color.rs b/crates/typst-library/src/text/font/color.rs index 33a828f9f..25ba79d25 100644 --- a/crates/typst-library/src/text/font/color.rs +++ b/crates/typst-library/src/text/font/color.rs @@ -105,9 +105,12 @@ fn draw_raster_glyph( upem: Abs, raster_image: ttf_parser::RasterGlyphImage, ) -> Option<()> { - let image = - Image::new(Bytes::from(raster_image.data).into(), RasterFormat::Png.into(), None) - .ok()?; + let image = Image::new( + Bytes::from(raster_image.data).into(), + RasterFormat::Png.into(), + &Default::default(), + ) + .ok()?; // Apple Color emoji doesn't provide offset information (or at least // not in a way ttf-parser understands), so we artificially shift their @@ -180,8 +183,12 @@ fn draw_colr_glyph( let data = svg.end_document().into_bytes(); - let image = - Image::new(Bytes::from(data).into(), VectorFormat::Svg.into(), None).ok()?; + let image = Image::new( + Bytes::from(data).into(), + VectorFormat::Svg.into(), + &Default::default(), + ) + .ok()?; let y_shift = Abs::pt(upem.to_pt() - y_max); let position = Point::new(Abs::pt(x_min), y_shift); @@ -257,7 +264,7 @@ fn draw_svg_glyph( ); let source = ImageSource::Readable(Readable::Str(wrapper_svg.into())); - let image = Image::new(source, VectorFormat::Svg.into(), None).ok()?; + let image = Image::new(source, VectorFormat::Svg.into(), &Default::default()).ok()?; let position = Point::new(Abs::pt(left), Abs::pt(top) + upem); let size = Size::new(Abs::pt(width), Abs::pt(height)); diff --git a/crates/typst-library/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs index 8b1fec3ce..6688e2d68 100644 --- a/crates/typst-library/src/visualize/image/mod.rs +++ b/crates/typst-library/src/visualize/image/mod.rs @@ -8,6 +8,7 @@ pub use self::raster::{RasterFormat, RasterImage}; pub use self::svg::SvgImage; use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; use std::sync::Arc; use comemo::Tracked; @@ -209,17 +210,6 @@ struct Repr { alt: Option, } -/// A kind of image. -#[derive(Hash)] -pub enum ImageKind { - /// A raster image. - Raster(RasterImage), - /// An SVG image. - Svg(SvgImage), - /// An image constructed from a pixmap. - Pixmap(Pixmap), -} - impl Image { /// When scaling an image to it's natural size, we default to this DPI /// if the image doesn't contain DPI metadata. @@ -234,7 +224,7 @@ impl Image { pub fn new( source: ImageSource, format: ImageFormat, - alt: Option, + options: &ImageOptions, ) -> StrResult { let kind = match format { ImageFormat::Raster(format) => { @@ -247,7 +237,7 @@ impl Image { let ImageSource::Readable(readable) = source else { bail!("expected readable source for the given format (str or bytes)"); }; - ImageKind::Svg(SvgImage::new(readable.into())?) + ImageKind::Svg(SvgImage::new(readable.into(), options)?) } ImageFormat::Pixmap(format) => { let ImageSource::Pixmap(source) = source else { @@ -257,47 +247,7 @@ impl Image { } }; - Ok(Self(Arc::new(LazyHash::new(Repr { kind, alt })))) - } - - /// Create a possibly font-dependent image from a buffer and a format. - #[comemo::memoize] - #[typst_macros::time(name = "load image")] - pub fn with_fonts( - source: ImageSource, - format: ImageFormat, - alt: Option, - world: Tracked, - families: &[&str], - flatten_text: bool, - ) -> StrResult { - let kind = match format { - ImageFormat::Raster(format) => { - let ImageSource::Readable(readable) = source else { - bail!("expected readable source for the given format (str or bytes)"); - }; - ImageKind::Raster(RasterImage::new(readable.into(), format)?) - } - ImageFormat::Vector(VectorFormat::Svg) => { - let ImageSource::Readable(readable) = source else { - bail!("expected readable source for the given format (str or bytes)"); - }; - ImageKind::Svg(SvgImage::with_fonts( - readable.into(), - world, - flatten_text, - families, - )?) - } - ImageFormat::Pixmap(format) => { - let ImageSource::Pixmap(source) = source else { - bail!("source must be pixmap"); - }; - ImageKind::Pixmap(Pixmap::new(source, format)?) - } - }; - - Ok(Self(Arc::new(LazyHash::new(Repr { kind, alt })))) + Ok(Self(Arc::new(LazyHash::new(Repr { kind, alt: options.alt.clone() })))) } /// The format of the image. @@ -433,3 +383,40 @@ cast! { v: VectorFormat => Self::Vector(v), v: PixmapFormat => Self::Pixmap(v), } + +/// A kind of image. +#[derive(Hash)] +pub enum ImageKind { + /// A raster image. + Raster(RasterImage), + /// An SVG image. + Svg(SvgImage), + /// An image constructed from a pixmap. + Pixmap(Pixmap), +} + +pub struct ImageOptions<'a> { + pub alt: Option, + pub world: Option>, + pub families: &'a [&'a str], + pub flatten_text: bool, +} + +impl Default for ImageOptions<'_> { + fn default() -> Self { + ImageOptions { + alt: None, + world: None, + families: &[], + flatten_text: false, + } + } +} + +impl Hash for ImageOptions<'_> { + fn hash(&self, state: &mut H) { + self.alt.hash(state); + self.families.hash(state); + self.flatten_text.hash(state); + } +} diff --git a/crates/typst-library/src/visualize/image/svg.rs b/crates/typst-library/src/visualize/image/svg.rs index 6b6a1b6b2..f649cd238 100644 --- a/crates/typst-library/src/visualize/image/svg.rs +++ b/crates/typst-library/src/visualize/image/svg.rs @@ -14,6 +14,8 @@ use crate::text::{ }; use crate::World; +use super::ImageOptions; + /// A decoded SVG. #[derive(Clone, Hash)] pub struct SvgImage(Arc); @@ -28,51 +30,48 @@ struct Repr { } impl SvgImage { - /// Decode an SVG image without fonts. + /// Decode an SVG image. #[comemo::memoize] - 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, - flatten_text: false, - tree, - }))) - } - - /// Decode an SVG image with access to fonts. - #[comemo::memoize] - pub fn with_fonts( - data: Bytes, - world: Tracked, - flatten_text: bool, - families: &[&str], - ) -> StrResult { - let book = world.book(); - let resolver = Mutex::new(FontResolver::new(world, book, families)); - let tree = usvg::Tree::from_data( - &data, - &usvg::Options { - font_resolver: usvg::FontResolver { - select_font: Box::new(|font, db| { - resolver.lock().unwrap().select_font(font, db) - }), - select_fallback: Box::new(|c, exclude_fonts, db| { - resolver.lock().unwrap().select_fallback(c, exclude_fonts, db) - }), - }, - ..base_options() - }, - ) - .map_err(format_usvg_error)?; - let font_hash = resolver.into_inner().unwrap().finish(); + pub fn new(data: Bytes, options: &ImageOptions) -> StrResult { + let (tree, font_hash) = match options.world { + Some(world) => { + let book = world.book(); + let resolver = + Mutex::new(FontResolver::new(world, book, options.families)); + let tree = usvg::Tree::from_data( + &data, + &usvg::Options { + font_resolver: usvg::FontResolver { + select_font: Box::new(|font, db| { + resolver.lock().unwrap().select_font(font, db) + }), + select_fallback: Box::new(|c, exclude_fonts, db| { + resolver.lock().unwrap().select_fallback( + c, + exclude_fonts, + db, + ) + }), + }, + ..base_options() + }, + ) + .map_err(format_usvg_error)?; + let font_hash = resolver.into_inner().unwrap().finish(); + (tree, font_hash) + } + None => { + let tree = usvg::Tree::from_data(&data, &base_options()) + .map_err(format_usvg_error)?; + let font_hash = 0; + (tree, font_hash) + } + }; Ok(Self(Arc::new(Repr { data, size: tree_size(&tree), font_hash, - flatten_text, + flatten_text: options.flatten_text, tree, }))) } diff --git a/crates/typst-svg/src/text.rs b/crates/typst-svg/src/text.rs index a1bd286a0..a83d03ce4 100644 --- a/crates/typst-svg/src/text.rs +++ b/crates/typst-svg/src/text.rs @@ -244,9 +244,12 @@ fn convert_bitmap_glyph_to_image(font: &Font, id: GlyphId) -> Option<(Image, f64 if raster.format != ttf_parser::RasterImageFormat::PNG { return None; } - let image = - Image::new(Bytes::from(raster.data).into(), RasterFormat::Png.into(), None) - .ok()?; + let image = Image::new( + Bytes::from(raster.data).into(), + RasterFormat::Png.into(), + &Default::default(), + ) + .ok()?; Some((image, raster.x as f64, raster.y as f64)) } diff --git a/tests/suite/visualize/image.typ b/tests/suite/visualize/image.typ index 8883627b0..1ff68589e 100644 --- a/tests/suite/visualize/image.typ +++ b/tests/suite/visualize/image.typ @@ -201,7 +201,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B ) --- image-png-but-pixmap-format --- -// Error: 1:2-4:2 source must be pixmap +// Error: 1:2-4:2 source must be a pixmap #image.decode( read("/assets/images/tiger.jpg", encoding: none), format: "rgba8",