use typst_library::diag::SourceResult; use typst_library::engine::Engine; use typst_library::foundations::{Packed, StyleChain}; use typst_library::introspection::Locator; use typst_library::layout::{ Abs, Axes, FixedAlignment, Frame, FrameItem, Point, Region, Size, }; use typst_library::visualize::{Curve, Image, ImageElem, ImageFit}; /// Layout the image. #[typst_macros::time(span = elem.span())] pub fn layout_image( elem: &Packed, engine: &mut Engine, _: Locator, styles: StyleChain, region: Region, ) -> SourceResult { let image = elem.decode(engine, styles)?; // Determine the image's pixel aspect ratio. let pxw = image.width(); let pxh = image.height(); let px_ratio = pxw / pxh; // Determine the region's aspect ratio. let region_ratio = region.size.x / region.size.y; // Find out whether the image is wider or taller than the region. let wide = px_ratio > region_ratio; // The space into which the image will be placed according to its fit. let target = if region.expand.x && region.expand.y { // If both width and height are forced, take them. region.size } else if region.expand.x { // If just width is forced, take it. Size::new(region.size.x, region.size.y.min(region.size.x / px_ratio)) } else if region.expand.y { // If just height is forced, take it. Size::new(region.size.x.min(region.size.y * px_ratio), region.size.y) } else { // If neither is forced, take the natural image size at the image's // DPI bounded by the available space. // // Division by DPI is fine since it's guaranteed to be positive. let dpi = image.dpi().unwrap_or(Image::DEFAULT_DPI); let natural = Axes::new(pxw, pxh).map(|v| Abs::inches(v / dpi)); Size::new( natural.x.min(region.size.x).min(region.size.y * px_ratio), natural.y.min(region.size.y).min(region.size.x / px_ratio), ) }; // Compute the actual size of the fitted image. let fit = elem.fit.get(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, elem.span())); frame.resize(target, Axes::splat(FixedAlignment::Center)); // Create a clipping group if only part of the image should be visible. if fit == ImageFit::Cover && !target.fits(fitted) { frame.clip(Curve::rect(frame.size())); } Ok(frame) }