summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/visualize/image.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/visualize/image.rs')
-rw-r--r--crates/typst-library/src/visualize/image.rs271
1 files changed, 0 insertions, 271 deletions
diff --git a/crates/typst-library/src/visualize/image.rs b/crates/typst-library/src/visualize/image.rs
deleted file mode 100644
index 0996ae7c..00000000
--- a/crates/typst-library/src/visualize/image.rs
+++ /dev/null
@@ -1,271 +0,0 @@
-use std::ffi::OsStr;
-
-use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
-use typst::util::option_eq;
-
-use crate::compute::Readable;
-use crate::meta::Figurable;
-use crate::prelude::*;
-use crate::text::families;
-
-/// A raster or vector graphic.
-///
-/// Supported formats are PNG, JPEG, GIF and SVG.
-///
-/// _Note:_ Work on SVG export is ongoing and there might be visual inaccuracies
-/// in the resulting PDF. Make sure to double-check embedded SVG images. If you
-/// have an issue, also feel free to report it on [GitHub][gh-svg].
-///
-/// # Example
-/// ```example
-/// #figure(
-/// image("molecular.jpg", width: 80%),
-/// caption: [
-/// A step in the molecular testing
-/// pipeline of our lab.
-/// ],
-/// )
-/// ```
-///
-/// [gh-svg]: https://github.com/typst/typst/issues?q=is%3Aopen+is%3Aissue+label%3Asvg
-#[elem(scope, Layout, LocalName, Figurable)]
-pub struct ImageElem {
- /// Path to an image file.
- #[required]
- #[parse(
- let Spanned { v: path, span } =
- args.expect::<Spanned<EcoString>>("path to image file")?;
- let id = vm.resolve_path(&path).at(span)?;
- let data = vm.world().file(id).at(span)?;
- path
- )]
- #[borrowed]
- pub path: EcoString,
-
- /// The raw file data.
- #[internal]
- #[required]
- #[parse(Readable::Bytes(data))]
- pub data: Readable,
-
- /// The image's format. Detected automatically by default.
- pub format: Smart<ImageFormat>,
-
- /// The width of the image.
- pub width: Smart<Rel<Length>>,
-
- /// The height of the image.
- pub height: Smart<Rel<Length>>,
-
- /// A text describing the image.
- pub alt: Option<EcoString>,
-
- /// How the image should adjust itself to a given area.
- #[default(ImageFit::Cover)]
- pub fit: ImageFit,
-}
-
-#[scope]
-impl ImageElem {
- /// Decode a raster or vector graphic from bytes or a string.
- ///
- /// ```example
- /// #let original = read("diagram.svg")
- /// #let changed = original.replace(
- /// "#2B80FF", // blue
- /// green.to-hex(),
- /// )
- ///
- /// #image.decode(original)
- /// #image.decode(changed)
- /// ```
- #[func(title = "Decode Image")]
- pub fn decode(
- /// The data to decode as an image. Can be a string for SVGs.
- data: Readable,
- /// The image's format. Detected automatically by default.
- #[named]
- format: Option<Smart<ImageFormat>>,
- /// The width of the image.
- #[named]
- width: Option<Smart<Rel<Length>>>,
- /// The height of the image.
- #[named]
- height: Option<Smart<Rel<Length>>>,
- /// A text describing the image.
- #[named]
- alt: Option<Option<EcoString>>,
- /// How the image should adjust itself to a given area.
- #[named]
- fit: Option<ImageFit>,
- ) -> StrResult<Content> {
- let mut elem = ImageElem::new(EcoString::new(), data);
- if let Some(format) = format {
- elem.push_format(format);
- }
- if let Some(width) = width {
- elem.push_width(width);
- }
- if let Some(height) = height {
- elem.push_height(height);
- }
- if let Some(alt) = alt {
- elem.push_alt(alt);
- }
- if let Some(fit) = fit {
- elem.push_fit(fit);
- }
- Ok(elem.pack())
- }
-}
-
-impl Layout for ImageElem {
- #[tracing::instrument(name = "ImageElem::layout", skip_all)]
- fn layout(
- &self,
- vt: &mut Vt,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- // Take the format that was explicitly defined, or parse the extension,
- // or try to detect the format.
- let data = self.data();
- let format = match self.format(styles) {
- Smart::Custom(v) => v,
- Smart::Auto => {
- let ext = std::path::Path::new(self.path().as_str())
- .extension()
- .and_then(OsStr::to_str)
- .unwrap_or_default()
- .to_lowercase();
-
- match ext.as_str() {
- "png" => ImageFormat::Raster(RasterFormat::Png),
- "jpg" | "jpeg" => ImageFormat::Raster(RasterFormat::Jpg),
- "gif" => ImageFormat::Raster(RasterFormat::Gif),
- "svg" | "svgz" => ImageFormat::Vector(VectorFormat::Svg),
- _ => match &data {
- Readable::Str(_) => ImageFormat::Vector(VectorFormat::Svg),
- Readable::Bytes(bytes) => match RasterFormat::detect(bytes) {
- Some(f) => ImageFormat::Raster(f),
- None => bail!(self.span(), "unknown image format"),
- },
- },
- }
- }
- };
-
- let image = Image::with_fonts(
- data.clone().into(),
- format,
- self.alt(styles),
- vt.world,
- &families(styles).map(|s| s.into()).collect::<Vec<_>>(),
- )
- .at(self.span())?;
-
- let sizing = Axes::new(self.width(styles), self.height(styles));
- let region = sizing
- .zip_map(regions.base(), |s, r| s.map(|v| v.resolve(styles).relative_to(r)))
- .unwrap_or(regions.base());
-
- let expand = sizing.as_ref().map(Smart::is_custom) | regions.expand;
- let region_ratio = region.x / region.y;
-
- // Find out whether the image is wider or taller than the target size.
- let pxw = image.width() as f64;
- let pxh = image.height() as f64;
- let px_ratio = pxw / pxh;
- let wide = px_ratio > region_ratio;
-
- // The space into which the image will be placed according to its fit.
- let target = if expand.x && expand.y {
- region
- } else if expand.x || (!expand.y && wide && region.x.is_finite()) {
- Size::new(region.x, region.y.min(region.x.safe_div(px_ratio)))
- } else if region.y.is_finite() {
- Size::new(region.x.min(region.y * px_ratio), region.y)
- } else {
- Size::new(Abs::pt(pxw), Abs::pt(pxh))
- };
-
- // Compute the actual size of the fitted image.
- let fit = self.fit(styles);
- let fitted = match fit {
- ImageFit::Cover | ImageFit::Contain => {
- if wide == (fit == ImageFit::Contain) {
- Size::new(target.x, target.x / px_ratio)
- } else {
- Size::new(target.y * px_ratio, target.y)
- }
- }
- ImageFit::Stretch => target,
- };
-
- // First, place the image in a frame of exactly its size and then resize
- // the frame to the target size, center aligning the image in the
- // process.
- let mut frame = Frame::soft(fitted);
- frame.push(Point::zero(), FrameItem::Image(image, fitted, self.span()));
- frame.resize(target, Axes::splat(FixedAlign::Center));
-
- // Create a clipping group if only part of the image should be visible.
- if fit == ImageFit::Cover && !target.fits(fitted) {
- frame.clip(Path::rect(frame.size()));
- }
-
- // Apply metadata.
- frame.meta(styles, false);
-
- Ok(Fragment::frame(frame))
- }
-}
-
-impl LocalName for ImageElem {
- fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
- match lang {
- Lang::ALBANIAN => "Figurë",
- Lang::ARABIC => "شكل",
- Lang::BOKMÅL => "Figur",
- Lang::CHINESE if option_eq(region, "TW") => "圖",
- Lang::CHINESE => "图",
- Lang::CZECH => "Obrázek",
- Lang::DANISH => "Figur",
- Lang::DUTCH => "Figuur",
- Lang::FILIPINO => "Pigura",
- Lang::FINNISH => "Kuva",
- Lang::FRENCH => "Fig.",
- Lang::GERMAN => "Abbildung",
- Lang::GREEK => "Σχήμα",
- Lang::HUNGARIAN => "Ábra",
- Lang::ITALIAN => "Figura",
- Lang::NYNORSK => "Figur",
- Lang::POLISH => "Rysunek",
- Lang::PORTUGUESE => "Figura",
- Lang::ROMANIAN => "Figura",
- Lang::RUSSIAN => "Рис.",
- Lang::SLOVENIAN => "Slika",
- Lang::SPANISH => "Figura",
- Lang::SWEDISH => "Figur",
- Lang::TURKISH => "Şekil",
- Lang::UKRAINIAN => "Рисунок",
- Lang::VIETNAMESE => "Hình",
- Lang::JAPANESE => "図",
- Lang::ENGLISH | _ => "Figure",
- }
- }
-}
-
-impl Figurable for ImageElem {}
-
-/// How an image should adjust itself to a given area.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
-pub enum ImageFit {
- /// The image should completely cover the area. This is the default.
- Cover,
- /// The image should be fully contained in the area.
- Contain,
- /// The image should be stretched so that it exactly fills the area, even if
- /// this means that the image will be distorted.
- Stretch,
-}