mirror of
https://github.com/typst/typst
synced 2025-08-13 22:57:56 +08:00
refactor: merge Image::new and Image::with_fonts
This commit is contained in:
parent
259a029723
commit
4993bae782
@ -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)?;
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user