summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/visualize
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/visualize')
-rw-r--r--crates/typst-library/src/visualize/image/mod.rs82
1 files changed, 80 insertions, 2 deletions
diff --git a/crates/typst-library/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs
index 95021b81..f1fa6381 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::{
};
pub use self::svg::SvgImage;
+use std::ffi::OsStr;
use std::fmt::{self, Debug, Formatter};
use std::sync::Arc;
@@ -15,14 +16,16 @@ use ecow::EcoString;
use typst_syntax::{Span, Spanned};
use typst_utils::LazyHash;
-use crate::diag::StrResult;
+use crate::diag::{warning, At, LoadedWithin, SourceResult, StrResult};
+use crate::engine::Engine;
use crate::foundations::{
cast, elem, func, scope, Bytes, Cast, Content, Derived, NativeElement, Packed, Smart,
+ StyleChain,
};
use crate::layout::{Length, Rel, Sizing};
use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable};
use crate::model::Figurable;
-use crate::text::LocalName;
+use crate::text::{families, LocalName};
/// A raster or vector graphic.
///
@@ -217,6 +220,81 @@ impl ImageElem {
}
}
+impl Packed<ImageElem> {
+ /// Decodes the image.
+ pub fn decode(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Image> {
+ let span = self.span();
+ let loaded = &self.source.derived;
+ let format = self.determine_format(styles).at(span)?;
+
+ // Warn the user if the image contains a foreign object. Not perfect
+ // because the svg could also be encoded, but that's an edge case.
+ if format == ImageFormat::Vector(VectorFormat::Svg) {
+ let has_foreign_object =
+ memchr::memmem::find(&loaded.data, b"<foreignObject").is_some();
+
+ if has_foreign_object {
+ engine.sink.warn(warning!(
+ span,
+ "image contains foreign object";
+ hint: "SVG images with foreign objects might render incorrectly in typst";
+ hint: "see https://github.com/typst/typst/issues/1421 for more information"
+ ));
+ }
+ }
+
+ // Construct the image itself.
+ let kind = match format {
+ ImageFormat::Raster(format) => ImageKind::Raster(
+ RasterImage::new(
+ loaded.data.clone(),
+ format,
+ self.icc.get_ref(styles).as_ref().map(|icc| icc.derived.clone()),
+ )
+ .at(span)?,
+ ),
+ ImageFormat::Vector(VectorFormat::Svg) => ImageKind::Svg(
+ SvgImage::with_fonts(
+ loaded.data.clone(),
+ engine.world,
+ &families(styles).map(|f| f.as_str()).collect::<Vec<_>>(),
+ )
+ .within(loaded)?,
+ ),
+ };
+
+ Ok(Image::new(kind, self.alt.get_cloned(styles), self.scaling.get(styles)))
+ }
+
+ /// Tries to determine the image format based on the format that was
+ /// explicitly defined, or else the extension, or else the data.
+ fn determine_format(&self, styles: StyleChain) -> StrResult<ImageFormat> {
+ if let Smart::Custom(v) = self.format.get(styles) {
+ return Ok(v);
+ };
+
+ let Derived { source, derived: loaded } = &self.source;
+ if let DataSource::Path(path) = source {
+ let ext = std::path::Path::new(path.as_str())
+ .extension()
+ .and_then(OsStr::to_str)
+ .unwrap_or_default()
+ .to_lowercase();
+
+ match ext.as_str() {
+ "png" => return Ok(ExchangeFormat::Png.into()),
+ "jpg" | "jpeg" => return Ok(ExchangeFormat::Jpg.into()),
+ "gif" => return Ok(ExchangeFormat::Gif.into()),
+ "svg" | "svgz" => return Ok(VectorFormat::Svg.into()),
+ "webp" => return Ok(ExchangeFormat::Webp.into()),
+ _ => {}
+ }
+ }
+
+ Ok(ImageFormat::detect(&loaded.data).ok_or("unknown image format")?)
+ }
+}
+
impl LocalName for Packed<ImageElem> {
const KEY: &'static str = "figure";
}