refactor: merge Image::new and Image::with_fonts

This commit is contained in:
frozolotl 2024-12-13 16:30:13 +01:00
parent 259a029723
commit 4993bae782
6 changed files with 109 additions and 111 deletions

View File

@ -10,8 +10,8 @@ use typst_library::layout::{
use typst_library::loading::Readable; use typst_library::loading::Readable;
use typst_library::text::families; use typst_library::text::families;
use typst_library::visualize::{ use typst_library::visualize::{
Curve, Image, ImageElem, ImageFit, ImageFormat, ImageSource, RasterFormat, Curve, Image, ImageElem, ImageFit, ImageFormat, ImageOptions, ImageSource,
VectorFormat, RasterFormat, VectorFormat,
}; };
/// Layout the image. /// Layout the image.
@ -57,13 +57,15 @@ pub fn layout_image(
} }
// Construct the image itself. // Construct the image itself.
let image = Image::with_fonts( let image = Image::new(
source.clone(), source.clone(),
format, format,
elem.alt(styles), &ImageOptions {
engine.world, alt: elem.alt(styles),
&families(styles).map(|f| f.as_str()).collect::<Vec<_>>(), world: Some(engine.world),
elem.flatten_text(styles), families: &families(styles).map(|f| f.as_str()).collect::<Vec<_>>(),
flatten_text: elem.flatten_text(styles),
},
) )
.at(span)?; .at(span)?;

View File

@ -105,9 +105,12 @@ fn draw_raster_glyph(
upem: Abs, upem: Abs,
raster_image: ttf_parser::RasterGlyphImage, raster_image: ttf_parser::RasterGlyphImage,
) -> Option<()> { ) -> Option<()> {
let image = let image = Image::new(
Image::new(Bytes::from(raster_image.data).into(), RasterFormat::Png.into(), None) Bytes::from(raster_image.data).into(),
.ok()?; RasterFormat::Png.into(),
&Default::default(),
)
.ok()?;
// Apple Color emoji doesn't provide offset information (or at least // Apple Color emoji doesn't provide offset information (or at least
// not in a way ttf-parser understands), so we artificially shift their // 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 data = svg.end_document().into_bytes();
let image = let image = Image::new(
Image::new(Bytes::from(data).into(), VectorFormat::Svg.into(), None).ok()?; Bytes::from(data).into(),
VectorFormat::Svg.into(),
&Default::default(),
)
.ok()?;
let y_shift = Abs::pt(upem.to_pt() - y_max); let y_shift = Abs::pt(upem.to_pt() - y_max);
let position = Point::new(Abs::pt(x_min), y_shift); 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 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 position = Point::new(Abs::pt(left), Abs::pt(top) + upem);
let size = Size::new(Abs::pt(width), Abs::pt(height)); let size = Size::new(Abs::pt(width), Abs::pt(height));

View File

@ -8,6 +8,7 @@ pub use self::raster::{RasterFormat, RasterImage};
pub use self::svg::SvgImage; pub use self::svg::SvgImage;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::hash::Hash;
use std::sync::Arc; use std::sync::Arc;
use comemo::Tracked; use comemo::Tracked;
@ -209,17 +210,6 @@ struct Repr {
alt: Option<EcoString>, alt: Option<EcoString>,
} }
/// 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 { impl Image {
/// When scaling an image to it's natural size, we default to this DPI /// When scaling an image to it's natural size, we default to this DPI
/// if the image doesn't contain DPI metadata. /// if the image doesn't contain DPI metadata.
@ -234,7 +224,7 @@ impl Image {
pub fn new( pub fn new(
source: ImageSource, source: ImageSource,
format: ImageFormat, format: ImageFormat,
alt: Option<EcoString>, options: &ImageOptions,
) -> StrResult<Image> { ) -> StrResult<Image> {
let kind = match format { let kind = match format {
ImageFormat::Raster(format) => { ImageFormat::Raster(format) => {
@ -247,7 +237,7 @@ impl Image {
let ImageSource::Readable(readable) = source else { let ImageSource::Readable(readable) = source else {
bail!("expected readable source for the given format (str or bytes)"); 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) => { ImageFormat::Pixmap(format) => {
let ImageSource::Pixmap(source) = source else { let ImageSource::Pixmap(source) = source else {
@ -257,47 +247,7 @@ impl Image {
} }
}; };
Ok(Self(Arc::new(LazyHash::new(Repr { kind, alt })))) Ok(Self(Arc::new(LazyHash::new(Repr { kind, alt: options.alt.clone() }))))
}
/// 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<EcoString>,
world: Tracked<dyn World + '_>,
families: &[&str],
flatten_text: bool,
) -> StrResult<Image> {
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 }))))
} }
/// The format of the image. /// The format of the image.
@ -433,3 +383,40 @@ cast! {
v: VectorFormat => Self::Vector(v), v: VectorFormat => Self::Vector(v),
v: PixmapFormat => Self::Pixmap(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<EcoString>,
pub world: Option<Tracked<'a, dyn World + 'a>>,
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<H: std::hash::Hasher>(&self, state: &mut H) {
self.alt.hash(state);
self.families.hash(state);
self.flatten_text.hash(state);
}
}

View File

@ -14,6 +14,8 @@ use crate::text::{
}; };
use crate::World; use crate::World;
use super::ImageOptions;
/// A decoded SVG. /// A decoded SVG.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
pub struct SvgImage(Arc<Repr>); pub struct SvgImage(Arc<Repr>);
@ -28,51 +30,48 @@ struct Repr {
} }
impl SvgImage { impl SvgImage {
/// Decode an SVG image without fonts. /// Decode an SVG image.
#[comemo::memoize] #[comemo::memoize]
pub fn new(data: Bytes) -> StrResult<SvgImage> { pub fn new(data: Bytes, options: &ImageOptions) -> StrResult<SvgImage> {
let tree = let (tree, font_hash) = match options.world {
usvg::Tree::from_data(&data, &base_options()).map_err(format_usvg_error)?; Some(world) => {
Ok(Self(Arc::new(Repr { let book = world.book();
data, let resolver =
size: tree_size(&tree), Mutex::new(FontResolver::new(world, book, options.families));
font_hash: 0, let tree = usvg::Tree::from_data(
flatten_text: false, &data,
tree, &usvg::Options {
}))) font_resolver: usvg::FontResolver {
} select_font: Box::new(|font, db| {
resolver.lock().unwrap().select_font(font, db)
/// Decode an SVG image with access to fonts. }),
#[comemo::memoize] select_fallback: Box::new(|c, exclude_fonts, db| {
pub fn with_fonts( resolver.lock().unwrap().select_fallback(
data: Bytes, c,
world: Tracked<dyn World + '_>, exclude_fonts,
flatten_text: bool, db,
families: &[&str], )
) -> StrResult<SvgImage> { }),
let book = world.book(); },
let resolver = Mutex::new(FontResolver::new(world, book, families)); ..base_options()
let tree = usvg::Tree::from_data( },
&data, )
&usvg::Options { .map_err(format_usvg_error)?;
font_resolver: usvg::FontResolver { let font_hash = resolver.into_inner().unwrap().finish();
select_font: Box::new(|font, db| { (tree, font_hash)
resolver.lock().unwrap().select_font(font, db) }
}), None => {
select_fallback: Box::new(|c, exclude_fonts, db| { let tree = usvg::Tree::from_data(&data, &base_options())
resolver.lock().unwrap().select_fallback(c, exclude_fonts, db) .map_err(format_usvg_error)?;
}), let font_hash = 0;
}, (tree, font_hash)
..base_options() }
}, };
)
.map_err(format_usvg_error)?;
let font_hash = resolver.into_inner().unwrap().finish();
Ok(Self(Arc::new(Repr { Ok(Self(Arc::new(Repr {
data, data,
size: tree_size(&tree), size: tree_size(&tree),
font_hash, font_hash,
flatten_text, flatten_text: options.flatten_text,
tree, tree,
}))) })))
} }

View File

@ -244,9 +244,12 @@ fn convert_bitmap_glyph_to_image(font: &Font, id: GlyphId) -> Option<(Image, f64
if raster.format != ttf_parser::RasterImageFormat::PNG { if raster.format != ttf_parser::RasterImageFormat::PNG {
return None; return None;
} }
let image = let image = Image::new(
Image::new(Bytes::from(raster.data).into(), RasterFormat::Png.into(), None) Bytes::from(raster.data).into(),
.ok()?; RasterFormat::Png.into(),
&Default::default(),
)
.ok()?;
Some((image, raster.x as f64, raster.y as f64)) Some((image, raster.x as f64, raster.y as f64))
} }

View File

@ -201,7 +201,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
) )
--- image-png-but-pixmap-format --- --- 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( #image.decode(
read("/assets/images/tiger.jpg", encoding: none), read("/assets/images/tiger.jpg", encoding: none),
format: "rgba8", format: "rgba8",