mod raster;
mod svg;
pub use self::raster::{
ExchangeFormat, PixelEncoding, PixelFormat, RasterFormat, RasterImage,
};
pub use self::svg::SvgImage;
use std::fmt::{self, Debug, Formatter};
use std::sync::Arc;
use ecow::EcoString;
use typst_syntax::{Span, Spanned};
use typst_utils::LazyHash;
use crate::diag::{SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, func, scope, Bytes, Cast, Content, Derived, NativeElement, Packed, Show,
Smart, StyleChain,
};
use crate::layout::{BlockElem, Length, Rel, Sizing};
use crate::loading::{DataSource, Load, Readable};
use crate::model::Figurable;
use crate::text::LocalName;
#[elem(scope, Show, LocalName, Figurable)]
pub struct ImageElem {
#[required]
#[parse(
let source = args.expect::<Spanned<DataSource>>("source")?;
let data = source.load(engine.world)?;
Derived::new(source.v, data)
)]
pub source: Derived<DataSource, Bytes>,
pub format: Smart<ImageFormat>,
pub width: Smart<Rel<Length>>,
pub height: Sizing,
pub alt: Option<EcoString>,
#[default(ImageFit::Cover)]
pub fit: ImageFit,
pub scaling: Smart<ImageScaling>,
#[parse(match args.named::<Spanned<Smart<DataSource>>>("icc")? {
Some(Spanned { v: Smart::Custom(source), span }) => Some(Smart::Custom({
let data = Spanned::new(&source, span).load(engine.world)?;
Derived::new(source, data)
})),
Some(Spanned { v: Smart::Auto, .. }) => Some(Smart::Auto),
None => None,
})]
#[borrowed]
pub icc: Smart<Derived<DataSource, Bytes>>,
}
#[scope]
#[allow(clippy::too_many_arguments)]
impl ImageElem {
#[func(title = "Decode Image")]
#[deprecated = "`image.decode` is deprecated, directly pass bytes to `image` instead"]
pub fn decode(
span: Span,
data: Readable,
#[named]
format: Option<Smart<ImageFormat>>,
#[named]
width: Option<Smart<Rel<Length>>>,
#[named]
height: Option<Sizing>,
#[named]
alt: Option<Option<EcoString>>,
#[named]
fit: Option<ImageFit>,
#[named]
scaling: Option<Smart<ImageScaling>>,
) -> StrResult<Content> {
let bytes = data.into_bytes();
let source = Derived::new(DataSource::Bytes(bytes.clone()), bytes);
let mut elem = ImageElem::new(source);
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);
}
if let Some(scaling) = scaling {
elem.push_scaling(scaling);
}
Ok(elem.pack().spanned(span))
}
}
impl Show for Packed<ImageElem> {
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_image)
.with_width(self.width(styles))
.with_height(self.height(styles))
.pack()
.spanned(self.span()))
}
}
impl LocalName for Packed<ImageElem> {
const KEY: &'static str = "figure";
}
impl Figurable for Packed<ImageElem> {}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum ImageFit {
Cover,
Contain,
Stretch,
}
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct Image(Arc<LazyHash<Repr>>);
#[derive(Hash)]
struct Repr {
kind: ImageKind,
alt: Option<EcoString>,
scaling: Smart<ImageScaling>,
}
impl Image {
pub const DEFAULT_DPI: f64 = 72.0;
pub const USVG_DEFAULT_DPI: f64 = 96.0;
pub fn new(
kind: impl Into<ImageKind>,
alt: Option<EcoString>,
scaling: Smart<ImageScaling>,
) -> Self {
Self::new_impl(kind.into(), alt, scaling)
}
pub fn plain(kind: impl Into<ImageKind>) -> Self {
Self::new(kind, None, Smart::Auto)
}
#[comemo::memoize]
fn new_impl(
kind: ImageKind,
alt: Option<EcoString>,
scaling: Smart<ImageScaling>,
) -> Image {
Self(Arc::new(LazyHash::new(Repr { kind, alt, scaling })))
}
pub fn format(&self) -> ImageFormat {
match &self.0.kind {
ImageKind::Raster(raster) => raster.format().into(),
ImageKind::Svg(_) => VectorFormat::Svg.into(),
}
}
pub fn width(&self) -> f64 {
match &self.0.kind {
ImageKind::Raster(raster) => raster.width() as f64,
ImageKind::Svg(svg) => svg.width(),
}
}
pub fn height(&self) -> f64 {
match &self.0.kind {
ImageKind::Raster(raster) => raster.height() as f64,
ImageKind::Svg(svg) => svg.height(),
}
}
pub fn dpi(&self) -> Option<f64> {
match &self.0.kind {
ImageKind::Raster(raster) => raster.dpi(),
ImageKind::Svg(_) => Some(Image::USVG_DEFAULT_DPI),
}
}
pub fn alt(&self) -> Option<&str> {
self.0.alt.as_deref()
}
pub fn scaling(&self) -> Smart<ImageScaling> {
self.0.scaling
}
pub fn kind(&self) -> &ImageKind {
&self.0.kind
}
}
impl Debug for Image {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Image")
.field("format", &self.format())
.field("width", &self.width())
.field("height", &self.height())
.field("alt", &self.alt())
.field("scaling", &self.scaling())
.finish()
}
}
#[derive(Clone, Hash)]
pub enum ImageKind {
Raster(RasterImage),
Svg(SvgImage),
}
impl From<RasterImage> for ImageKind {
fn from(image: RasterImage) -> Self {
Self::Raster(image)
}
}
impl From<SvgImage> for ImageKind {
fn from(image: SvgImage) -> Self {
Self::Svg(image)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ImageFormat {
Raster(RasterFormat),
Vector(VectorFormat),
}
impl ImageFormat {
pub fn detect(data: &[u8]) -> Option<Self> {
if let Some(format) = ExchangeFormat::detect(data) {
return Some(Self::Raster(RasterFormat::Exchange(format)));
}
if is_svg(data) {
return Some(Self::Vector(VectorFormat::Svg));
}
None
}
}
fn is_svg(data: &[u8]) -> bool {
if data.starts_with(&[0x1f, 0x8b]) {
return true;
}
let head = &data[..data.len().min(2048)];
memchr::memmem::find(head, b"http://www.w3.org/2000/svg").is_some()
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum VectorFormat {
Svg,
}
impl<R> From<R> for ImageFormat
where
R: Into<RasterFormat>,
{
fn from(format: R) -> Self {
Self::Raster(format.into())
}
}
impl From<VectorFormat> for ImageFormat {
fn from(format: VectorFormat) -> Self {
Self::Vector(format)
}
}
cast! {
ImageFormat,
self => match self {
Self::Raster(v) => v.into_value(),
Self::Vector(v) => v.into_value(),
},
v: RasterFormat => Self::Raster(v),
v: VectorFormat => Self::Vector(v),
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum ImageScaling {
Smooth,
Pixelated,
}