diff options
Diffstat (limited to 'crates/typst-library/src/visualize/image.rs')
| -rw-r--r-- | crates/typst-library/src/visualize/image.rs | 271 |
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, -} |
