mirror of
https://github.com/typst/typst
synced 2025-08-13 14:47:54 +08:00
Refactor
This commit is contained in:
parent
283fcbb71c
commit
3862102398
@ -10,8 +10,8 @@ use typst_library::layout::{
|
|||||||
use typst_library::loading::DataSource;
|
use typst_library::loading::DataSource;
|
||||||
use typst_library::text::families;
|
use typst_library::text::families;
|
||||||
use typst_library::visualize::{
|
use typst_library::visualize::{
|
||||||
Curve, Image, ImageElem, ImageFit, ImageFormat, ImageOptions, ImageSource,
|
Curve, Image, ImageElem, ImageFit, ImageFormat, ImageKind, ImageSource, PixmapImage,
|
||||||
RasterFormat, VectorFormat,
|
RasterFormat, RasterImage, SvgImage, VectorFormat,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Layout the image.
|
/// Layout the image.
|
||||||
@ -28,22 +28,16 @@ pub fn layout_image(
|
|||||||
// Take the format that was explicitly defined, or parse the extension,
|
// Take the format that was explicitly defined, or parse the extension,
|
||||||
// or try to detect the format.
|
// or try to detect the format.
|
||||||
let Derived { source, derived: data } = &elem.source;
|
let Derived { source, derived: data } = &elem.source;
|
||||||
let format = match (elem.format(styles), source) {
|
let format = match elem.format(styles) {
|
||||||
(Smart::Custom(v), _) => v,
|
Smart::Custom(v) => v,
|
||||||
(Smart::Auto, ImageSource::Readable(data)) => {
|
Smart::Auto => determine_format(source, data).at(span)?,
|
||||||
determine_format(elem.path().as_str(), data).at(span)?
|
|
||||||
}
|
|
||||||
(Smart::Auto, ImageSource::Pixmap(_)) => {
|
|
||||||
bail!(span, "pixmaps require an explicit image format to be given");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Warn the user if the image contains a foreign object. Not perfect
|
// 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.
|
// because the svg could also be encoded, but that's an edge case.
|
||||||
if let ImageSource::Readable(data) = source {
|
|
||||||
if format == ImageFormat::Vector(VectorFormat::Svg) {
|
if format == ImageFormat::Vector(VectorFormat::Svg) {
|
||||||
let has_foreign_object =
|
let has_foreign_object =
|
||||||
data.as_str().is_some_and(|s| s.contains("<foreignObject"));
|
data.as_str().is_ok_and(|s| s.contains("<foreignObject"));
|
||||||
|
|
||||||
if has_foreign_object {
|
if has_foreign_object {
|
||||||
engine.sink.warn(warning!(
|
engine.sink.warn(warning!(
|
||||||
@ -54,21 +48,31 @@ pub fn layout_image(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Construct the image itself.
|
// Construct the image itself.
|
||||||
let image = Image::new(
|
let kind = match (format, source) {
|
||||||
source.clone(),
|
(ImageFormat::Pixmap(format), ImageSource::Pixmap(source)) => {
|
||||||
format,
|
ImageKind::Pixmap(PixmapImage::new(source.clone(), format).at(span)?)
|
||||||
&ImageOptions {
|
}
|
||||||
alt: elem.alt(styles),
|
(ImageFormat::Raster(format), ImageSource::Data(_)) => {
|
||||||
world: Some(engine.world),
|
ImageKind::Raster(RasterImage::new(data.clone(), format).at(span)?)
|
||||||
families: &families(styles).map(|f| f.as_str()).collect::<Vec<_>>(),
|
}
|
||||||
flatten_text: elem.flatten_text(styles),
|
(ImageFormat::Vector(VectorFormat::Svg), ImageSource::Data(_)) => ImageKind::Svg(
|
||||||
scaling: elem.scaling(styles),
|
SvgImage::with_fonts(
|
||||||
},
|
data.clone(),
|
||||||
|
engine.world,
|
||||||
|
elem.flatten_text(styles),
|
||||||
|
&families(styles).map(|f| f.as_str()).collect::<Vec<_>>(),
|
||||||
)
|
)
|
||||||
.at(span)?;
|
.at(span)?,
|
||||||
|
),
|
||||||
|
(ImageFormat::Pixmap(_), _) => bail!(span, "source must be a pixmap"),
|
||||||
|
(ImageFormat::Raster(_) | ImageFormat::Vector(_), _) => {
|
||||||
|
bail!(span, "expected readable source for the given format (str or bytes)")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let image = Image::new(kind, elem.alt(styles), elem.scaling(styles));
|
||||||
|
|
||||||
// Determine the image's pixel aspect ratio.
|
// Determine the image's pixel aspect ratio.
|
||||||
let pxw = image.width();
|
let pxw = image.width();
|
||||||
@ -131,8 +135,9 @@ pub fn layout_image(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Try to determine the image format based on the data.
|
/// Try to determine the image format based on the data.
|
||||||
fn determine_format(source: &DataSource, data: &Bytes) -> StrResult<ImageFormat> {
|
fn determine_format(source: &ImageSource, data: &Bytes) -> StrResult<ImageFormat> {
|
||||||
if let DataSource::Path(path) = source {
|
match source {
|
||||||
|
ImageSource::Data(DataSource::Path(path)) => {
|
||||||
let ext = std::path::Path::new(path.as_str())
|
let ext = std::path::Path::new(path.as_str())
|
||||||
.extension()
|
.extension()
|
||||||
.and_then(OsStr::to_str)
|
.and_then(OsStr::to_str)
|
||||||
@ -147,6 +152,11 @@ fn determine_format(source: &DataSource, data: &Bytes) -> StrResult<ImageFormat>
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ImageSource::Data(DataSource::Bytes(_)) => {}
|
||||||
|
ImageSource::Pixmap(_) => {
|
||||||
|
bail!("pixmaps require an explicit image format to be given")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(ImageFormat::detect(data).ok_or("unknown image format")?)
|
Ok(ImageFormat::detect(data).ok_or("unknown image format")?)
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,11 @@ use typst_syntax::Span;
|
|||||||
use usvg::tiny_skia_path;
|
use usvg::tiny_skia_path;
|
||||||
use xmlwriter::XmlWriter;
|
use xmlwriter::XmlWriter;
|
||||||
|
|
||||||
use crate::foundations::Bytes;
|
use crate::foundations::{Bytes, Smart};
|
||||||
use crate::layout::{Abs, Frame, FrameItem, Point, Size};
|
use crate::layout::{Abs, Frame, FrameItem, Point, Size};
|
||||||
use crate::loading::Readable;
|
|
||||||
use crate::text::{Font, Glyph};
|
use crate::text::{Font, Glyph};
|
||||||
use crate::visualize::{
|
use crate::visualize::{
|
||||||
FixedStroke, Geometry, Image, ImageSource, RasterFormat, VectorFormat,
|
FixedStroke, Geometry, Image, RasterFormat, RasterImage, SvgImage,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Whether this glyph should be rendered via simple outlining instead of via
|
/// Whether this glyph should be rendered via simple outlining instead of via
|
||||||
@ -105,12 +104,9 @@ fn draw_raster_glyph(
|
|||||||
upem: Abs,
|
upem: Abs,
|
||||||
raster_image: ttf_parser::RasterGlyphImage,
|
raster_image: ttf_parser::RasterGlyphImage,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let image = Image::new(
|
let data = Bytes::new(raster_image.data.to_vec());
|
||||||
Bytes::new(raster_image.data).to_vec(),
|
let image =
|
||||||
RasterFormat::Png.into(),
|
Image::new(RasterImage::new(data, RasterFormat::Png).ok()?, None, Smart::Auto);
|
||||||
&Default::default(),
|
|
||||||
)
|
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
// Apple Color emoji doesn't provide offset information (or at least
|
// Apple Color emoji doesn't provide offset information (or at least
|
||||||
// not in a way ttf-parser understands), so we artificially shift their
|
// not in a way ttf-parser understands), so we artificially shift their
|
||||||
@ -181,14 +177,8 @@ fn draw_colr_glyph(
|
|||||||
ttf.paint_color_glyph(glyph_id, 0, RgbaColor::new(0, 0, 0, 255), &mut glyph_painter)?;
|
ttf.paint_color_glyph(glyph_id, 0, RgbaColor::new(0, 0, 0, 255), &mut glyph_painter)?;
|
||||||
svg.end_element();
|
svg.end_element();
|
||||||
|
|
||||||
let data = svg.end_document().into_bytes();
|
let data = Bytes::from_string(svg.end_document());
|
||||||
|
let image = Image::new(SvgImage::new(data).ok()?, None, Smart::Auto);
|
||||||
let image = Image::new(
|
|
||||||
Bytes::new(data).into(),
|
|
||||||
VectorFormat::Svg.into(),
|
|
||||||
&Default::default(),
|
|
||||||
)
|
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
let y_shift = Abs::pt(upem.to_pt() - y_max);
|
let y_shift = Abs::pt(upem.to_pt() - y_max);
|
||||||
let position = Point::new(Abs::pt(x_min), y_shift);
|
let position = Point::new(Abs::pt(x_min), y_shift);
|
||||||
@ -263,8 +253,8 @@ fn draw_svg_glyph(
|
|||||||
ty = -top,
|
ty = -top,
|
||||||
);
|
);
|
||||||
|
|
||||||
let source = ImageSource::Readable(Readable::Str(wrapper_svg.into()));
|
let data = Bytes::from_string(wrapper_svg);
|
||||||
let image = Image::new(source, VectorFormat::Svg.into(), &Default::default()).ok()?;
|
let image = Image::new(SvgImage::new(data).ok()?, None, Smart::Auto);
|
||||||
|
|
||||||
let position = Point::new(Abs::pt(left), Abs::pt(top) + upem);
|
let position = Point::new(Abs::pt(left), Abs::pt(top) + upem);
|
||||||
let size = Size::new(Abs::pt(width), Abs::pt(height));
|
let size = Size::new(Abs::pt(width), Abs::pt(height));
|
||||||
|
@ -4,6 +4,7 @@ mod pixmap;
|
|||||||
mod raster;
|
mod raster;
|
||||||
mod svg;
|
mod svg;
|
||||||
|
|
||||||
|
pub use self::pixmap::{PixmapFormat, PixmapImage, PixmapSource};
|
||||||
pub use self::raster::{RasterFormat, RasterImage};
|
pub use self::raster::{RasterFormat, RasterImage};
|
||||||
pub use self::svg::SvgImage;
|
pub use self::svg::SvgImage;
|
||||||
|
|
||||||
@ -13,15 +14,14 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use pixmap::{Pixmap, PixmapFormat, PixmapSource};
|
|
||||||
use typst_syntax::{Span, Spanned};
|
use typst_syntax::{Span, Spanned};
|
||||||
use typst_utils::LazyHash;
|
use typst_utils::LazyHash;
|
||||||
|
|
||||||
use crate::diag::{bail, SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, func, scope, AutoValue, Bytes, Cast, Content, Derived, Dict, NativeElement,
|
cast, elem, func, scope, Bytes, Cast, Content, Derived, NativeElement, Packed, Show,
|
||||||
Packed, Show, Smart, StyleChain, Value,
|
Smart, StyleChain,
|
||||||
};
|
};
|
||||||
use crate::layout::{BlockElem, Length, Rel, Sizing};
|
use crate::layout::{BlockElem, Length, Rel, Sizing};
|
||||||
use crate::loading::{DataSource, Load, Readable};
|
use crate::loading::{DataSource, Load, Readable};
|
||||||
@ -97,9 +97,9 @@ pub struct ImageElem {
|
|||||||
|
|
||||||
/// A hint to the viewer how it should scale the image.
|
/// A hint to the viewer how it should scale the image.
|
||||||
///
|
///
|
||||||
/// **Note:** This option may be ignored and results look different
|
/// _Note:_ This option may be ignored and results look different depending
|
||||||
/// depending on the format and viewer.
|
/// on the format and viewer.
|
||||||
pub scaling: ImageScaling,
|
pub scaling: Smart<ImageScaling>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[scope]
|
#[scope]
|
||||||
@ -145,10 +145,11 @@ impl ImageElem {
|
|||||||
flatten_text: Option<bool>,
|
flatten_text: Option<bool>,
|
||||||
/// How the image should be scaled by the viewer.
|
/// How the image should be scaled by the viewer.
|
||||||
#[named]
|
#[named]
|
||||||
scaling: Option<ImageScaling>,
|
scaling: Option<Smart<ImageScaling>>,
|
||||||
) -> StrResult<Content> {
|
) -> StrResult<Content> {
|
||||||
let bytes = data.into_bytes();
|
let bytes = data.into_bytes();
|
||||||
let source = Derived::new(ImageSource::DataSource(DataSource::Bytes(bytes.clone())), bytes);
|
let source =
|
||||||
|
Derived::new(ImageSource::Data(DataSource::Bytes(bytes.clone())), bytes);
|
||||||
let mut elem = ImageElem::new(source);
|
let mut elem = ImageElem::new(source);
|
||||||
if let Some(format) = format {
|
if let Some(format) = format {
|
||||||
elem.push_format(format);
|
elem.push_format(format);
|
||||||
@ -225,7 +226,7 @@ struct Repr {
|
|||||||
/// A text describing the image.
|
/// A text describing the image.
|
||||||
alt: Option<EcoString>,
|
alt: Option<EcoString>,
|
||||||
/// The scaling algorithm to use.
|
/// The scaling algorithm to use.
|
||||||
scaling: ImageScaling,
|
scaling: Smart<ImageScaling>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
@ -236,40 +237,24 @@ impl Image {
|
|||||||
/// Should always be the same as the default DPI used by usvg.
|
/// Should always be the same as the default DPI used by usvg.
|
||||||
pub const USVG_DEFAULT_DPI: f64 = 96.0;
|
pub const USVG_DEFAULT_DPI: f64 = 96.0;
|
||||||
|
|
||||||
/// Create an image from a source and a format.
|
/// Create an image from a kind.
|
||||||
#[comemo::memoize]
|
|
||||||
#[typst_macros::time(name = "load image")]
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
data: Derived<ImageSource, Bytes>,
|
kind: impl Into<ImageKind>,
|
||||||
format: ImageFormat,
|
alt: Option<EcoString>,
|
||||||
options: &ImageOptions,
|
scaling: Smart<ImageScaling>,
|
||||||
) -> StrResult<Image> {
|
) -> Self {
|
||||||
let kind = match format {
|
Self::new_impl(kind.into(), alt, scaling)
|
||||||
ImageFormat::Raster(format) => {
|
|
||||||
let ImageSource::DataSource(_) = data.source else {
|
|
||||||
bail!("expected non-pixmap source for the given format");
|
|
||||||
};
|
|
||||||
ImageKind::Raster(RasterImage::new(data.derived, format)?)
|
|
||||||
}
|
}
|
||||||
ImageFormat::Vector(VectorFormat::Svg) => {
|
|
||||||
let ImageSource::DataSource(_) = data.source else {
|
|
||||||
bail!("expected non-pixmap source for the given format");
|
|
||||||
};
|
|
||||||
ImageKind::Svg(SvgImage::new(data.derived, options)?)
|
|
||||||
}
|
|
||||||
ImageFormat::Pixmap(format) => {
|
|
||||||
let ImageSource::Pixmap(source) = data.source else {
|
|
||||||
bail!("source must be a pixmap");
|
|
||||||
};
|
|
||||||
ImageKind::Pixmap(Pixmap::new(source, format)?)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self(Arc::new(LazyHash::new(Repr {
|
/// The internal, non-generic implementation. This is memoized to reuse
|
||||||
kind,
|
/// the `Arc` and `LazyHash`.
|
||||||
alt: options.alt.clone(),
|
#[comemo::memoize]
|
||||||
scaling: options.scaling,
|
fn new_impl(
|
||||||
}))))
|
kind: ImageKind,
|
||||||
|
alt: Option<EcoString>,
|
||||||
|
scaling: Smart<ImageScaling>,
|
||||||
|
) -> Image {
|
||||||
|
Self(Arc::new(LazyHash::new(Repr { kind, alt, scaling })))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The format of the image.
|
/// The format of the image.
|
||||||
@ -314,7 +299,7 @@ impl Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The image scaling algorithm to use for this image.
|
/// The image scaling algorithm to use for this image.
|
||||||
pub fn scaling(&self) -> ImageScaling {
|
pub fn scaling(&self) -> Smart<ImageScaling> {
|
||||||
self.0.scaling
|
self.0.scaling
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,13 +324,13 @@ impl Debug for Image {
|
|||||||
/// Information specifying the source of an image's byte data.
|
/// Information specifying the source of an image's byte data.
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
pub enum ImageSource {
|
pub enum ImageSource {
|
||||||
DataSource(DataSource),
|
Data(DataSource),
|
||||||
Pixmap(Arc<PixmapSource>),
|
Pixmap(PixmapSource),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Bytes> for ImageSource {
|
impl From<Bytes> for ImageSource {
|
||||||
fn from(bytes: Bytes) -> Self {
|
fn from(bytes: Bytes) -> Self {
|
||||||
ImageSource::DataSource(DataSource::Bytes(bytes))
|
ImageSource::Data(DataSource::Bytes(bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,8 +339,8 @@ impl Load for Spanned<ImageSource> {
|
|||||||
|
|
||||||
fn load(&self, world: Tracked<dyn World + '_>) -> SourceResult<Self::Output> {
|
fn load(&self, world: Tracked<dyn World + '_>) -> SourceResult<Self::Output> {
|
||||||
match &self.v {
|
match &self.v {
|
||||||
ImageSource::DataSource(data_source) => Spanned::new(data_source, self.span).load(world),
|
ImageSource::Data(data) => Spanned::new(data, self.span).load(world),
|
||||||
ImageSource::Pixmap(pixmap_source) => Ok(pixmap_source.data.clone()),
|
ImageSource::Pixmap(pixmap) => Ok(pixmap.data.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,20 +348,40 @@ impl Load for Spanned<ImageSource> {
|
|||||||
cast! {
|
cast! {
|
||||||
ImageSource,
|
ImageSource,
|
||||||
self => match self {
|
self => match self {
|
||||||
Self::DataSource(data) => data.into_value(),
|
Self::Data(data) => data.into_value(),
|
||||||
Self::Pixmap(pixmap) => pixmap.into_value(),
|
Self::Pixmap(pixmap) => pixmap.into_value(),
|
||||||
},
|
},
|
||||||
data: DataSource => ImageSource::DataSource(data),
|
data: DataSource => Self::Data(data),
|
||||||
mut dict: Dict => {
|
pixmap: PixmapSource => Self::Pixmap(pixmap),
|
||||||
let source = ImageSource::Pixmap(Arc::new(PixmapSource {
|
}
|
||||||
data: dict.take("data")?.cast()?,
|
|
||||||
pixel_width: dict.take("pixel-width")?.cast()?,
|
/// A kind of image.
|
||||||
pixel_height: dict.take("pixel-height")?.cast()?,
|
#[derive(Clone, Hash)]
|
||||||
icc_profile: dict.take("icc-profile").ok().map(|value| value.cast()).transpose()?,
|
pub enum ImageKind {
|
||||||
}));
|
/// A raster image.
|
||||||
dict.finish(&["data", "pixel-width", "pixel-height", "icc-profile"])?;
|
Raster(RasterImage),
|
||||||
source
|
/// An SVG image.
|
||||||
},
|
Svg(SvgImage),
|
||||||
|
/// An image constructed from a pixmap.
|
||||||
|
Pixmap(PixmapImage),
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PixmapImage> for ImageKind {
|
||||||
|
fn from(image: PixmapImage) -> Self {
|
||||||
|
Self::Pixmap(image)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A raster or vector image format.
|
/// A raster or vector image format.
|
||||||
@ -444,66 +449,11 @@ cast! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The image scaling algorithm a viewer should use.
|
/// The image scaling algorithm a viewer should use.
|
||||||
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
||||||
pub enum ImageScaling {
|
pub enum ImageScaling {
|
||||||
/// Use the default scaling algorithm.
|
|
||||||
#[default]
|
|
||||||
Auto,
|
|
||||||
/// Scale photos with a smoothing algorithm such as bilinear interpolation.
|
/// Scale photos with a smoothing algorithm such as bilinear interpolation.
|
||||||
Smooth,
|
Smooth,
|
||||||
/// Scale with nearest neighbor or similar to preserve the pixelated look
|
/// Scale with nearest neighbor or similar to preserve the pixelated look
|
||||||
/// of the image.
|
/// of the image.
|
||||||
Pixelated,
|
Pixelated,
|
||||||
}
|
}
|
||||||
|
|
||||||
cast! {
|
|
||||||
ImageScaling,
|
|
||||||
self => match self {
|
|
||||||
ImageScaling::Auto => Value::Auto,
|
|
||||||
ImageScaling::Pixelated => "pixelated".into_value(),
|
|
||||||
ImageScaling::Smooth => "smooth".into_value(),
|
|
||||||
},
|
|
||||||
_: AutoValue => ImageScaling::Auto,
|
|
||||||
"pixelated" => ImageScaling::Pixelated,
|
|
||||||
"smooth" => ImageScaling::Smooth,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A kind of image.
|
|
||||||
#[derive(Hash)]
|
|
||||||
pub enum ImageKind {
|
|
||||||
/// A raster image.
|
|
||||||
Raster(RasterImage),
|
|
||||||
/// An SVG image.
|
|
||||||
Svg(SvgImage),
|
|
||||||
/// An image constructed from a pixmap.
|
|
||||||
Pixmap(Pixmap),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ImageOptions<'a> {
|
|
||||||
pub alt: Option<EcoString>,
|
|
||||||
pub scaling: ImageScaling,
|
|
||||||
pub world: Option<Tracked<'a, dyn World + 'a>>,
|
|
||||||
pub families: &'a [&'a str],
|
|
||||||
pub flatten_text: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ImageOptions<'_> {
|
|
||||||
fn default() -> Self {
|
|
||||||
ImageOptions {
|
|
||||||
alt: None,
|
|
||||||
scaling: ImageScaling::Auto,
|
|
||||||
world: None,
|
|
||||||
families: &[],
|
|
||||||
flatten_text: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for ImageOptions<'_> {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.alt.hash(state);
|
|
||||||
self.scaling.hash(state);
|
|
||||||
self.families.hash(state);
|
|
||||||
self.flatten_text.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -5,44 +5,22 @@ use image::{DynamicImage, ImageBuffer, Pixel};
|
|||||||
use crate::diag::{bail, StrResult};
|
use crate::diag::{bail, StrResult};
|
||||||
use crate::foundations::{cast, dict, Bytes, Cast, Dict};
|
use crate::foundations::{cast, dict, Bytes, Cast, Dict};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
||||||
pub struct PixmapSource {
|
|
||||||
pub data: Bytes,
|
|
||||||
pub pixel_width: u32,
|
|
||||||
pub pixel_height: u32,
|
|
||||||
pub icc_profile: Option<Bytes>,
|
|
||||||
}
|
|
||||||
|
|
||||||
cast! {
|
|
||||||
Arc<PixmapSource>,
|
|
||||||
self => dict!("data" => self.data.clone(), "pixel_width" => self.pixel_width, "pixel_height" => self.pixel_height, "icc_profile" => self.icc_profile.clone()).into_value(),
|
|
||||||
mut dict: Dict => {
|
|
||||||
let source = Arc::new(PixmapSource {
|
|
||||||
data: dict.take("data")?.cast()?,
|
|
||||||
pixel_width: dict.take("pixel-width")?.cast()?,
|
|
||||||
pixel_height: dict.take("pixel-height")?.cast()?,
|
|
||||||
icc_profile: dict.take("icc-profile").ok().map(|value| value.cast()).transpose()?,
|
|
||||||
});
|
|
||||||
dict.finish(&["data", "pixel-width", "pixel-height", "icc-profile"])?;
|
|
||||||
source
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A raster image based on a flat pixmap.
|
/// A raster image based on a flat pixmap.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub struct Pixmap(Arc<Repr>);
|
pub struct PixmapImage(Arc<Repr>);
|
||||||
|
|
||||||
/// The internal representation.
|
/// The internal representation.
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
struct Repr {
|
struct Repr {
|
||||||
source: Arc<PixmapSource>,
|
source: PixmapSource,
|
||||||
format: PixmapFormat,
|
format: PixmapFormat,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pixmap {
|
impl PixmapImage {
|
||||||
/// Build a new [`Pixmap`] from a flat, uncompressed byte sequence.
|
/// Build a new [`Pixmap`] from a flat, uncompressed byte sequence.
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
pub fn new(source: Arc<PixmapSource>, format: PixmapFormat) -> StrResult<Pixmap> {
|
#[typst_macros::time(name = "load pixmap")]
|
||||||
|
pub fn new(source: PixmapSource, format: PixmapFormat) -> StrResult<PixmapImage> {
|
||||||
if source.pixel_width == 0 || source.pixel_height == 0 {
|
if source.pixel_width == 0 || source.pixel_height == 0 {
|
||||||
bail!("zero-sized images are not allowed");
|
bail!("zero-sized images are not allowed");
|
||||||
}
|
}
|
||||||
@ -53,6 +31,7 @@ impl Pixmap {
|
|||||||
PixmapFormat::Luma8 => 1,
|
PixmapFormat::Luma8 => 1,
|
||||||
PixmapFormat::Lumaa8 => 2,
|
PixmapFormat::Lumaa8 => 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(expected_size) = source
|
let Some(expected_size) = source
|
||||||
.pixel_width
|
.pixel_width
|
||||||
.checked_mul(source.pixel_height)
|
.checked_mul(source.pixel_height)
|
||||||
@ -60,6 +39,7 @@ impl Pixmap {
|
|||||||
else {
|
else {
|
||||||
bail!("provided pixel dimensions are too large");
|
bail!("provided pixel dimensions are too large");
|
||||||
};
|
};
|
||||||
|
|
||||||
if expected_size as usize != source.data.len() {
|
if expected_size as usize != source.data.len() {
|
||||||
bail!("provided pixel dimensions and pixmap data do not match");
|
bail!("provided pixel dimensions and pixmap data do not match");
|
||||||
}
|
}
|
||||||
@ -83,14 +63,14 @@ impl Pixmap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The raw data encoded in the given format.
|
/// The raw data encoded in the given format.
|
||||||
pub fn data(&self) -> &[u8] {
|
pub fn data(&self) -> &Bytes {
|
||||||
self.0.source.data.as_slice()
|
&self.0.source.data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transform the image data into an [`DynamicImage`].
|
/// Transform the image data into a [`DynamicImage`].
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
pub fn to_image(&self) -> Arc<DynamicImage> {
|
pub fn to_image(&self) -> Arc<DynamicImage> {
|
||||||
// TODO optimize by returning a `View` if possible?
|
// TODO: Optimize by returning a `View` if possible?
|
||||||
fn decode<P: Pixel<Subpixel = u8>>(
|
fn decode<P: Pixel<Subpixel = u8>>(
|
||||||
source: &PixmapSource,
|
source: &PixmapSource,
|
||||||
) -> ImageBuffer<P, Vec<u8>> {
|
) -> ImageBuffer<P, Vec<u8>> {
|
||||||
@ -118,13 +98,42 @@ impl Pixmap {
|
|||||||
/// Determines how the given image is interpreted and encoded.
|
/// Determines how the given image is interpreted and encoded.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
||||||
pub enum PixmapFormat {
|
pub enum PixmapFormat {
|
||||||
/// The red, green, and blue channels are each eight bit integers.
|
/// Red, green, and blue channels, one byte per channel.
|
||||||
/// There is no alpha channel.
|
/// No alpha channel.
|
||||||
Rgb8,
|
Rgb8,
|
||||||
/// The red, green, blue, and alpha channels are each eight bit integers.
|
/// Red, green, blue, and alpha channels, one byte per channel.
|
||||||
Rgba8,
|
Rgba8,
|
||||||
/// A single eight bit channel representing brightness.
|
/// A single byte channel representing brightness.
|
||||||
Luma8,
|
Luma8,
|
||||||
/// One byte of brightness, another for alpha.
|
/// Brightness and alpha, one byte per channel.
|
||||||
Lumaa8,
|
Lumaa8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Raw pixmap data and relevant metadata.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
|
pub struct PixmapSource {
|
||||||
|
pub data: Bytes,
|
||||||
|
pub pixel_width: u32,
|
||||||
|
pub pixel_height: u32,
|
||||||
|
pub icc_profile: Option<Bytes>,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast! {
|
||||||
|
PixmapSource,
|
||||||
|
self => dict! {
|
||||||
|
"data" => self.data.clone(),
|
||||||
|
"pixel-width" => self.pixel_width,
|
||||||
|
"pixel-height" => self.pixel_height,
|
||||||
|
"icc-profile" => self.icc_profile.clone()
|
||||||
|
}.into_value(),
|
||||||
|
mut dict: Dict => {
|
||||||
|
let source = PixmapSource {
|
||||||
|
data: dict.take("data")?.cast()?,
|
||||||
|
pixel_width: dict.take("pixel-width")?.cast()?,
|
||||||
|
pixel_height: dict.take("pixel-height")?.cast()?,
|
||||||
|
icc_profile: dict.take("icc-profile").ok().map(|v| v.cast()).transpose()?,
|
||||||
|
};
|
||||||
|
dict.finish(&["data", "pixel-width", "pixel-height", "icc-profile"])?;
|
||||||
|
source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -28,6 +28,7 @@ struct Repr {
|
|||||||
impl RasterImage {
|
impl RasterImage {
|
||||||
/// Decode a raster image.
|
/// Decode a raster image.
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
|
#[typst_macros::time(name = "load raster image")]
|
||||||
pub fn new(data: Bytes, format: RasterFormat) -> StrResult<RasterImage> {
|
pub fn new(data: Bytes, format: RasterFormat) -> StrResult<RasterImage> {
|
||||||
fn decode_with<T: ImageDecoder>(
|
fn decode_with<T: ImageDecoder>(
|
||||||
decoder: ImageResult<T>,
|
decoder: ImageResult<T>,
|
||||||
|
@ -14,8 +14,6 @@ use crate::text::{
|
|||||||
};
|
};
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
use super::ImageOptions;
|
|
||||||
|
|
||||||
/// A decoded SVG.
|
/// A decoded SVG.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub struct SvgImage(Arc<Repr>);
|
pub struct SvgImage(Arc<Repr>);
|
||||||
@ -30,14 +28,32 @@ struct Repr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SvgImage {
|
impl SvgImage {
|
||||||
/// Decode an SVG image.
|
/// Decode an SVG image without fonts.
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
pub fn new(data: Bytes, options: &ImageOptions) -> StrResult<SvgImage> {
|
#[typst_macros::time(name = "load svg")]
|
||||||
let (tree, font_hash) = match options.world {
|
pub fn new(data: Bytes) -> StrResult<SvgImage> {
|
||||||
Some(world) => {
|
let tree =
|
||||||
|
usvg::Tree::from_data(&data, &base_options()).map_err(format_usvg_error)?;
|
||||||
|
Ok(Self(Arc::new(Repr {
|
||||||
|
data,
|
||||||
|
size: tree_size(&tree),
|
||||||
|
font_hash: 0,
|
||||||
|
flatten_text: false,
|
||||||
|
tree,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode an SVG image with access to fonts.
|
||||||
|
#[comemo::memoize]
|
||||||
|
#[typst_macros::time(name = "load svg")]
|
||||||
|
pub fn with_fonts(
|
||||||
|
data: Bytes,
|
||||||
|
world: Tracked<dyn World + '_>,
|
||||||
|
flatten_text: bool,
|
||||||
|
families: &[&str],
|
||||||
|
) -> StrResult<SvgImage> {
|
||||||
let book = world.book();
|
let book = world.book();
|
||||||
let resolver =
|
let resolver = Mutex::new(FontResolver::new(world, book, families));
|
||||||
Mutex::new(FontResolver::new(world, book, options.families));
|
|
||||||
let tree = usvg::Tree::from_data(
|
let tree = usvg::Tree::from_data(
|
||||||
&data,
|
&data,
|
||||||
&usvg::Options {
|
&usvg::Options {
|
||||||
@ -46,11 +62,7 @@ impl SvgImage {
|
|||||||
resolver.lock().unwrap().select_font(font, db)
|
resolver.lock().unwrap().select_font(font, db)
|
||||||
}),
|
}),
|
||||||
select_fallback: Box::new(|c, exclude_fonts, db| {
|
select_fallback: Box::new(|c, exclude_fonts, db| {
|
||||||
resolver.lock().unwrap().select_fallback(
|
resolver.lock().unwrap().select_fallback(c, exclude_fonts, db)
|
||||||
c,
|
|
||||||
exclude_fonts,
|
|
||||||
db,
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
..base_options()
|
..base_options()
|
||||||
@ -58,20 +70,11 @@ impl SvgImage {
|
|||||||
)
|
)
|
||||||
.map_err(format_usvg_error)?;
|
.map_err(format_usvg_error)?;
|
||||||
let font_hash = resolver.into_inner().unwrap().finish();
|
let font_hash = resolver.into_inner().unwrap().finish();
|
||||||
(tree, font_hash)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let tree = usvg::Tree::from_data(&data, &base_options())
|
|
||||||
.map_err(format_usvg_error)?;
|
|
||||||
let font_hash = 0;
|
|
||||||
(tree, font_hash)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(Self(Arc::new(Repr {
|
Ok(Self(Arc::new(Repr {
|
||||||
data,
|
data,
|
||||||
size: tree_size(&tree),
|
size: tree_size(&tree),
|
||||||
font_hash,
|
font_hash,
|
||||||
flatten_text: options.flatten_text,
|
flatten_text,
|
||||||
tree,
|
tree,
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ use ecow::eco_format;
|
|||||||
use image::{DynamicImage, GenericImageView, Rgba};
|
use image::{DynamicImage, GenericImageView, Rgba};
|
||||||
use pdf_writer::{Chunk, Filter, Finish, Ref};
|
use pdf_writer::{Chunk, Filter, Finish, Ref};
|
||||||
use typst_library::diag::{At, SourceResult, StrResult};
|
use typst_library::diag::{At, SourceResult, StrResult};
|
||||||
|
use typst_library::foundations::Smart;
|
||||||
use typst_library::visualize::{
|
use typst_library::visualize::{
|
||||||
ColorSpace, Image, ImageKind, ImageScaling, RasterFormat, SvgImage,
|
ColorSpace, Image, ImageKind, ImageScaling, RasterFormat, SvgImage,
|
||||||
};
|
};
|
||||||
@ -136,7 +137,7 @@ pub fn deferred_image(
|
|||||||
|
|
||||||
// PDF/A does not appear to allow interpolation[^1].
|
// PDF/A does not appear to allow interpolation[^1].
|
||||||
// [^1]: https://github.com/typst/typst/issues/2942
|
// [^1]: https://github.com/typst/typst/issues/2942
|
||||||
let interpolate = image.scaling() == ImageScaling::Smooth && !pdfa;
|
let interpolate = image.scaling() == Smart::Custom(ImageScaling::Smooth) && !pdfa;
|
||||||
|
|
||||||
let deferred = Deferred::new(move || match image.kind() {
|
let deferred = Deferred::new(move || match image.kind() {
|
||||||
ImageKind::Raster(raster) => {
|
ImageKind::Raster(raster) => {
|
||||||
|
@ -3,6 +3,7 @@ use std::sync::Arc;
|
|||||||
use image::imageops::FilterType;
|
use image::imageops::FilterType;
|
||||||
use image::{GenericImageView, Rgba};
|
use image::{GenericImageView, Rgba};
|
||||||
use tiny_skia as sk;
|
use tiny_skia as sk;
|
||||||
|
use typst_library::foundations::Smart;
|
||||||
use typst_library::layout::Size;
|
use typst_library::layout::Size;
|
||||||
use typst_library::visualize::{Image, ImageKind, ImageScaling};
|
use typst_library::visualize::{Image, ImageKind, ImageScaling};
|
||||||
|
|
||||||
@ -81,7 +82,7 @@ fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> {
|
|||||||
/// Scale a rastered image to a given size and return texture.
|
/// Scale a rastered image to a given size and return texture.
|
||||||
fn scale_image(
|
fn scale_image(
|
||||||
image: &image::DynamicImage,
|
image: &image::DynamicImage,
|
||||||
scaling: ImageScaling,
|
scaling: Smart<ImageScaling>,
|
||||||
w: u32,
|
w: u32,
|
||||||
h: u32,
|
h: u32,
|
||||||
) -> Option<Arc<sk::Pixmap>> {
|
) -> Option<Arc<sk::Pixmap>> {
|
||||||
@ -93,9 +94,10 @@ fn scale_image(
|
|||||||
} else {
|
} else {
|
||||||
let upscale = w > image.width();
|
let upscale = w > image.width();
|
||||||
let filter = match scaling {
|
let filter = match scaling {
|
||||||
ImageScaling::Auto if upscale => FilterType::CatmullRom,
|
Smart::Auto | Smart::Custom(ImageScaling::Smooth) if upscale => {
|
||||||
ImageScaling::Smooth if upscale => FilterType::CatmullRom,
|
FilterType::CatmullRom
|
||||||
ImageScaling::Pixelated => FilterType::Nearest,
|
}
|
||||||
|
Smart::Custom(ImageScaling::Pixelated) => FilterType::Nearest,
|
||||||
_ => FilterType::Lanczos3, // downscale
|
_ => FilterType::Lanczos3, // downscale
|
||||||
};
|
};
|
||||||
buf = image.resize_exact(w, h, filter);
|
buf = image.resize_exact(w, h, filter);
|
||||||
|
@ -4,6 +4,7 @@ use base64::Engine;
|
|||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use image::error::UnsupportedError;
|
use image::error::UnsupportedError;
|
||||||
use image::{codecs::png::PngEncoder, ImageEncoder};
|
use image::{codecs::png::PngEncoder, ImageEncoder};
|
||||||
|
use typst_library::foundations::Smart;
|
||||||
use typst_library::layout::{Abs, Axes};
|
use typst_library::layout::{Abs, Axes};
|
||||||
use typst_library::visualize::{
|
use typst_library::visualize::{
|
||||||
Image, ImageFormat, ImageKind, ImageScaling, RasterFormat, VectorFormat,
|
Image, ImageFormat, ImageKind, ImageScaling, RasterFormat, VectorFormat,
|
||||||
@ -21,13 +22,13 @@ impl SVGRenderer {
|
|||||||
self.xml.write_attribute("height", &size.y.to_pt());
|
self.xml.write_attribute("height", &size.y.to_pt());
|
||||||
self.xml.write_attribute("preserveAspectRatio", "none");
|
self.xml.write_attribute("preserveAspectRatio", "none");
|
||||||
match image.scaling() {
|
match image.scaling() {
|
||||||
ImageScaling::Auto => {}
|
Smart::Auto => {}
|
||||||
ImageScaling::Smooth => {
|
Smart::Custom(ImageScaling::Smooth) => {
|
||||||
// This is still experimental and not implemented in all major browsers[^1].
|
// This is still experimental and not implemented in all major browsers[^1].
|
||||||
// [^1]: https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering#browser_compatibility
|
// [^1]: https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering#browser_compatibility
|
||||||
self.xml.write_attribute("style", "image-rendering: smooth")
|
self.xml.write_attribute("style", "image-rendering: smooth")
|
||||||
}
|
}
|
||||||
ImageScaling::Pixelated => {
|
Smart::Custom(ImageScaling::Pixelated) => {
|
||||||
self.xml.write_attribute("style", "image-rendering: pixelated")
|
self.xml.write_attribute("style", "image-rendering: pixelated")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@ use std::io::Read;
|
|||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use ttf_parser::GlyphId;
|
use ttf_parser::GlyphId;
|
||||||
use typst_library::foundations::Bytes;
|
use typst_library::foundations::{Bytes, Smart};
|
||||||
use typst_library::layout::{Abs, Point, Ratio, Size, Transform};
|
use typst_library::layout::{Abs, Point, Ratio, Size, Transform};
|
||||||
use typst_library::text::{Font, TextItem};
|
use typst_library::text::{Font, TextItem};
|
||||||
use typst_library::visualize::{FillRule, Image, Paint, RasterFormat, RelativeTo};
|
use typst_library::visualize::{
|
||||||
|
FillRule, Image, Paint, RasterFormat, RasterImage, RelativeTo,
|
||||||
|
};
|
||||||
use typst_utils::hash128;
|
use typst_utils::hash128;
|
||||||
|
|
||||||
use crate::{SVGRenderer, State, SvgMatrix, SvgPathBuilder};
|
use crate::{SVGRenderer, State, SvgMatrix, SvgPathBuilder};
|
||||||
@ -245,11 +247,10 @@ fn convert_bitmap_glyph_to_image(font: &Font, id: GlyphId) -> Option<(Image, f64
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let image = Image::new(
|
let image = Image::new(
|
||||||
Bytes::new(raster.data.to_vec()).into(),
|
RasterImage::new(Bytes::new(raster.data.to_vec()), RasterFormat::Png).ok()?,
|
||||||
RasterFormat::Png.into(),
|
None,
|
||||||
&Default::default(),
|
Smart::Auto,
|
||||||
)
|
);
|
||||||
.ok()?;
|
|
||||||
Some((image, raster.x as f64, raster.y as f64))
|
Some((image, raster.x as f64, raster.y as f64))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
|||||||
)
|
)
|
||||||
|
|
||||||
--- image-pixmap-rgb8 ---
|
--- image-pixmap-rgb8 ---
|
||||||
#image.decode(
|
#image(
|
||||||
(
|
(
|
||||||
data: bytes((
|
data: bytes((
|
||||||
0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
|
0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
|
||||||
@ -81,7 +81,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
|||||||
)
|
)
|
||||||
|
|
||||||
--- image-pixmap-rgba8 ---
|
--- image-pixmap-rgba8 ---
|
||||||
#image.decode(
|
#image(
|
||||||
(
|
(
|
||||||
data: bytes((
|
data: bytes((
|
||||||
0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF,
|
0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF,
|
||||||
@ -96,7 +96,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
|||||||
)
|
)
|
||||||
|
|
||||||
--- image-pixmap-luma8 ---
|
--- image-pixmap-luma8 ---
|
||||||
#image.decode(
|
#image(
|
||||||
(
|
(
|
||||||
data: bytes(range(16).map(x => x * 16)),
|
data: bytes(range(16).map(x => x * 16)),
|
||||||
pixel-width: 4,
|
pixel-width: 4,
|
||||||
@ -107,7 +107,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
|||||||
)
|
)
|
||||||
|
|
||||||
--- image-pixmap-lumaa8 ---
|
--- image-pixmap-lumaa8 ---
|
||||||
#image.decode(
|
#image(
|
||||||
(
|
(
|
||||||
data: bytes(range(16).map(x => (0x80, x * 16)).flatten()),
|
data: bytes(range(16).map(x => (0x80, x * 16)).flatten()),
|
||||||
pixel-width: 4,
|
pixel-width: 4,
|
||||||
@ -118,7 +118,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
|||||||
)
|
)
|
||||||
|
|
||||||
--- image-scaling-methods ---
|
--- image-scaling-methods ---
|
||||||
#let img(scaling) = image.decode(
|
#let img(scaling) = image(
|
||||||
(
|
(
|
||||||
data: bytes((
|
data: bytes((
|
||||||
0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
|
0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
|
||||||
@ -180,7 +180,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
|||||||
|
|
||||||
--- image-pixmap-empty ---
|
--- image-pixmap-empty ---
|
||||||
// Error: 1:2-8:2 zero-sized images are not allowed
|
// Error: 1:2-8:2 zero-sized images are not allowed
|
||||||
#image.decode(
|
#image(
|
||||||
(
|
(
|
||||||
data: bytes(()),
|
data: bytes(()),
|
||||||
pixel-width: 0,
|
pixel-width: 0,
|
||||||
@ -191,7 +191,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
|||||||
|
|
||||||
--- image-pixmap-invalid-size ---
|
--- image-pixmap-invalid-size ---
|
||||||
// Error: 1:2-8:2 provided pixel dimensions and pixmap data do not match
|
// Error: 1:2-8:2 provided pixel dimensions and pixmap data do not match
|
||||||
#image.decode(
|
#image(
|
||||||
(
|
(
|
||||||
data: bytes((0x00, 0x00, 0x00)),
|
data: bytes((0x00, 0x00, 0x00)),
|
||||||
pixel-width: 16,
|
pixel-width: 16,
|
||||||
@ -202,7 +202,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
|||||||
|
|
||||||
--- image-pixmap-unknown-attribute ---
|
--- image-pixmap-unknown-attribute ---
|
||||||
// Error: 2:3-7:4 unexpected key "stowaway", valid keys are "data", "pixel-width", "pixel-height", and "icc-profile"
|
// Error: 2:3-7:4 unexpected key "stowaway", valid keys are "data", "pixel-width", "pixel-height", and "icc-profile"
|
||||||
#image.decode(
|
#image(
|
||||||
(
|
(
|
||||||
data: bytes((0x00, 0x00, 0x00)),
|
data: bytes((0x00, 0x00, 0x00)),
|
||||||
pixel-width: 1,
|
pixel-width: 1,
|
||||||
@ -214,7 +214,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
|||||||
|
|
||||||
--- image-pixmap-but-png-format ---
|
--- image-pixmap-but-png-format ---
|
||||||
// Error: 1:2-8:2 expected readable source for the given format (str or bytes)
|
// Error: 1:2-8:2 expected readable source for the given format (str or bytes)
|
||||||
#image.decode(
|
#image(
|
||||||
(
|
(
|
||||||
data: bytes((0x00, 0x00, 0x00)),
|
data: bytes((0x00, 0x00, 0x00)),
|
||||||
pixel-width: 1,
|
pixel-width: 1,
|
||||||
@ -225,7 +225,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
|||||||
|
|
||||||
--- image-png-but-pixmap-format ---
|
--- image-png-but-pixmap-format ---
|
||||||
// Error: 1:2-4:2 source must be a pixmap
|
// Error: 1:2-4:2 source must be a pixmap
|
||||||
#image.decode(
|
#image(
|
||||||
read("/assets/images/tiger.jpg", encoding: none),
|
read("/assets/images/tiger.jpg", encoding: none),
|
||||||
format: "rgba8",
|
format: "rgba8",
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user