Merge a46f1c7250d27321247ac01f6e5016255b11ff22 into b790c6d59ceaf7a809cc24b60c1f1509807470e2

This commit is contained in:
Laurenz Stampfl 2025-07-18 23:57:19 +09:00 committed by GitHub
commit bf045ce8d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 301 additions and 29 deletions

130
Cargo.lock generated
View File

@ -181,9 +181,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.8.0" version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -214,9 +214,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.21.0" version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
dependencies = [ dependencies = [
"bytemuck_derive", "bytemuck_derive",
] ]
@ -415,6 +415,15 @@ name = "codex"
version = "0.1.1" version = "0.1.1"
source = "git+https://github.com/typst/codex?rev=9ac86f9#9ac86f96af5b89fce555e6bba8b6d1ac7b44ef00" source = "git+https://github.com/typst/codex?rev=9ac86f9#9ac86f96af5b89fce555e6bba8b6d1ac7b44ef00"
[[package]]
name = "color"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ae467d04a8a8aea5d9a49018a6ade2e4221d92968e8ce55a48c0b1164e5f698"
dependencies = [
"bytemuck",
]
[[package]] [[package]]
name = "color-print" name = "color-print"
version = "0.3.7" version = "0.3.7"
@ -964,6 +973,67 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "hayro-font"
version = "0.1.0"
dependencies = [
"log",
"phf",
]
[[package]]
name = "hayro-interpret"
version = "0.1.0"
dependencies = [
"bitflags 2.9.1",
"hayro-font",
"hayro-syntax",
"kurbo",
"log",
"peniko",
"phf",
"qcms",
"skrifa",
"smallvec",
"yoke 0.8.0",
]
[[package]]
name = "hayro-render"
version = "0.1.0"
dependencies = [
"bytemuck",
"hayro-interpret",
"hayro-syntax",
"image",
"kurbo",
"peniko",
"rustc-hash",
"smallvec",
]
[[package]]
name = "hayro-syntax"
version = "0.0.1"
dependencies = [
"flate2",
"kurbo",
"log",
"rustc-hash",
"smallvec",
"zune-jpeg",
]
[[package]]
name = "hayro-write"
version = "0.1.0"
dependencies = [
"flate2",
"hayro-syntax",
"log",
"pdf-writer",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@ -1271,7 +1341,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"inotify-sys", "inotify-sys",
"libc", "libc",
] ]
@ -1367,7 +1437,6 @@ dependencies = [
[[package]] [[package]]
name = "krilla" name = "krilla"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/LaurenzV/krilla?rev=20c14fe#20c14fefee5002566b3d6668b338bbe2168784e7"
dependencies = [ dependencies = [
"base64", "base64",
"bumpalo", "bumpalo",
@ -1376,6 +1445,7 @@ dependencies = [
"float-cmp 0.10.0", "float-cmp 0.10.0",
"fxhash", "fxhash",
"gif", "gif",
"hayro-write",
"image-webp", "image-webp",
"imagesize", "imagesize",
"once_cell", "once_cell",
@ -1385,6 +1455,7 @@ dependencies = [
"rustybuzz", "rustybuzz",
"siphasher", "siphasher",
"skrifa", "skrifa",
"smallvec",
"subsetter", "subsetter",
"tiny-skia-path", "tiny-skia-path",
"xmp-writer", "xmp-writer",
@ -1395,7 +1466,6 @@ dependencies = [
[[package]] [[package]]
name = "krilla-svg" name = "krilla-svg"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/LaurenzV/krilla?rev=20c14fe#20c14fefee5002566b3d6668b338bbe2168784e7"
dependencies = [ dependencies = [
"flate2", "flate2",
"fontdb", "fontdb",
@ -1408,9 +1478,9 @@ dependencies = [
[[package]] [[package]]
name = "kurbo" name = "kurbo"
version = "0.11.1" version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" checksum = "1077d333efea6170d9ccb96d3c3026f300ca0773da4938cc4c811daa6df68b0c"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"smallvec", "smallvec",
@ -1462,7 +1532,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"libc", "libc",
"redox_syscall", "redox_syscall",
] ]
@ -1628,7 +1698,7 @@ version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"filetime", "filetime",
"fsevent-sys", "fsevent-sys",
"inotify", "inotify",
@ -1710,7 +1780,7 @@ version = "0.10.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"cfg-if", "cfg-if",
"foreign-types", "foreign-types",
"libc", "libc",
@ -1847,12 +1917,24 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ea27c5015ab81753fc61e49f8cde74999346605ee148bb20008ef3d3150e0dc" checksum = "3ea27c5015ab81753fc61e49f8cde74999346605ee148bb20008ef3d3150e0dc"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"itoa", "itoa",
"memchr", "memchr",
"ryu", "ryu",
] ]
[[package]]
name = "peniko"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f9529efd019889b2a205193c14ffb6e2839b54ed9d2720674f10f4b04d87ac9"
dependencies = [
"bytemuck",
"color",
"kurbo",
"smallvec",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@ -2005,7 +2087,7 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"getopts", "getopts",
"memchr", "memchr",
"unicase", "unicase",
@ -2118,7 +2200,7 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
] ]
[[package]] [[package]]
@ -2221,7 +2303,7 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@ -2240,7 +2322,7 @@ version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"bytemuck", "bytemuck",
"core_maths", "core_maths",
"log", "log",
@ -2288,7 +2370,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"core-foundation", "core-foundation",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@ -2451,9 +2533,9 @@ dependencies = [
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.13.2" version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]] [[package]]
name = "spin" name = "spin"
@ -3063,7 +3145,7 @@ name = "typst-library"
version = "0.13.1" version = "0.13.1"
dependencies = [ dependencies = [
"az", "az",
"bitflags 2.8.0", "bitflags 2.9.1",
"bumpalo", "bumpalo",
"chinese-number", "chinese-number",
"ciborium", "ciborium",
@ -3075,6 +3157,7 @@ dependencies = [
"fontdb", "fontdb",
"glidesort", "glidesort",
"hayagriva", "hayagriva",
"hayro-syntax",
"icu_properties", "icu_properties",
"icu_provider", "icu_provider",
"icu_provider_blob", "icu_provider_blob",
@ -3173,6 +3256,7 @@ version = "0.13.1"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"comemo", "comemo",
"hayro-render",
"image", "image",
"pixglyph", "pixglyph",
"resvg", "resvg",
@ -3589,7 +3673,7 @@ version = "0.221.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9845c470a2e10b61dd42c385839cdd6496363ed63b5c9e420b5488b77bd22083" checksum = "9845c470a2e10b61dd42c385839cdd6496363ed63b5c9e420b5488b77bd22083"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
"indexmap 2.7.1", "indexmap 2.7.1",
] ]
@ -3724,7 +3808,7 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.1",
] ]
[[package]] [[package]]

View File

@ -61,6 +61,8 @@ fontdb = { version = "0.23", default-features = false }
fs_extra = "1.3" fs_extra = "1.3"
glidesort = "0.1.2" glidesort = "0.1.2"
hayagriva = "0.8.1" hayagriva = "0.8.1"
hayro-syntax = { path = "../hayro/hayro-syntax" }
hayro-render = { path = "../hayro/hayro-render" }
heck = "0.5" heck = "0.5"
hypher = "0.1.4" hypher = "0.1.4"
icu_properties = { version = "1.4", features = ["serde"] } icu_properties = { version = "1.4", features = ["serde"] }
@ -73,8 +75,8 @@ image = { version = "0.25.5", default-features = false, features = ["png", "jpeg
indexmap = { version = "2", features = ["serde"] } indexmap = { version = "2", features = ["serde"] }
infer = { version = "0.19.0", default-features = false } infer = { version = "0.19.0", default-features = false }
kamadak-exif = "0.6" kamadak-exif = "0.6"
krilla = { git = "https://github.com/LaurenzV/krilla", rev = "20c14fe", default-features = false, features = ["raster-images", "comemo", "rayon"] } krilla = { path = "../krilla/crates/krilla", default-features = false, features = ["raster-images", "comemo", "rayon", "pdf"] }
krilla-svg = { git = "https://github.com/LaurenzV/krilla", rev = "20c14fe" } krilla-svg = { path = "../krilla/crates/krilla-svg" }
kurbo = "0.11" kurbo = "0.11"
libfuzzer-sys = "0.4" libfuzzer-sys = "0.4"
lipsum = "0.9" lipsum = "0.9"

View File

@ -471,6 +471,7 @@ display_possible_values!(DiagnosticFormat);
#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)] #[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)]
pub enum Feature { pub enum Feature {
Html, Html,
PdfEmbedding,
} }
display_possible_values!(Feature); display_possible_values!(Feature);

View File

@ -117,6 +117,7 @@ impl SystemWorld {
.iter() .iter()
.map(|&feature| match feature { .map(|&feature| match feature {
Feature::Html => typst::Feature::Html, Feature::Html => typst::Feature::Html,
Feature::PdfEmbedding => typst::Feature::PdfEmbedding,
}) })
.collect(); .collect();

View File

@ -31,6 +31,7 @@ flate2 = { workspace = true }
fontdb = { workspace = true } fontdb = { workspace = true }
glidesort = { workspace = true } glidesort = { workspace = true }
hayagriva = { workspace = true } hayagriva = { workspace = true }
hayro-syntax = { workspace = true }
icu_properties = { workspace = true } icu_properties = { workspace = true }
icu_provider = { workspace = true } icu_provider = { workspace = true }
icu_provider_blob = { workspace = true } icu_provider_blob = { workspace = true }

View File

@ -237,6 +237,7 @@ impl FromIterator<Feature> for Features {
#[non_exhaustive] #[non_exhaustive]
pub enum Feature { pub enum Feature {
Html, Html,
PdfEmbedding,
} }
/// A group of related standard library definitions. /// A group of related standard library definitions.

View File

@ -1,5 +1,6 @@
//! Image handling. //! Image handling.
mod pdf;
mod raster; mod raster;
mod svg; mod svg;
@ -26,6 +27,7 @@ use crate::layout::{Length, Rel, Sizing};
use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable}; use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable};
use crate::model::Figurable; use crate::model::Figurable;
use crate::text::{families, LocalName}; use crate::text::{families, LocalName};
use crate::visualize::image::pdf::{PdfDocument, PdfImage};
/// A raster or vector graphic. /// A raster or vector graphic.
/// ///
@ -126,6 +128,11 @@ pub struct ImageElem {
/// A text describing the image. /// A text describing the image.
pub alt: Option<EcoString>, pub alt: Option<EcoString>,
/// The page number that should be embedded as an image. This attribute only has an effect
/// for PDF files.
#[default(1)]
pub page: usize,
/// How the image should adjust itself to a given area (the area is defined /// How the image should adjust itself to a given area (the area is defined
/// by the `width` and `height` fields). Note that `fit` doesn't visually /// by the `width` and `height` fields). Note that `fit` doesn't visually
/// change anything if the area's aspect ratio is the same as the image's /// change anything if the area's aspect ratio is the same as the image's
@ -261,6 +268,15 @@ impl Packed<ImageElem> {
) )
.within(loaded)?, .within(loaded)?,
), ),
ImageFormat::Vector(VectorFormat::Pdf) => {
let document = PdfDocument::new(loaded.data.clone()).within(loaded)?;
// The user provides the page number staring from 1, further down the pipeline they page
// numbers are 0-based.
let pdf_image =
PdfImage::new(document, self.page.get(styles) - 1).within(loaded)?;
ImageKind::Pdf(pdf_image)
}
}; };
Ok(Image::new(kind, self.alt.get_cloned(styles), self.scaling.get(styles))) Ok(Image::new(kind, self.alt.get_cloned(styles), self.scaling.get(styles)))
@ -286,6 +302,7 @@ impl Packed<ImageElem> {
"jpg" | "jpeg" => return Ok(ExchangeFormat::Jpg.into()), "jpg" | "jpeg" => return Ok(ExchangeFormat::Jpg.into()),
"gif" => return Ok(ExchangeFormat::Gif.into()), "gif" => return Ok(ExchangeFormat::Gif.into()),
"svg" | "svgz" => return Ok(VectorFormat::Svg.into()), "svg" | "svgz" => return Ok(VectorFormat::Svg.into()),
"pdf" => return Ok(VectorFormat::Pdf.into()),
"webp" => return Ok(ExchangeFormat::Webp.into()), "webp" => return Ok(ExchangeFormat::Webp.into()),
_ => {} _ => {}
} }
@ -373,6 +390,7 @@ impl Image {
match &self.0.kind { match &self.0.kind {
ImageKind::Raster(raster) => raster.format().into(), ImageKind::Raster(raster) => raster.format().into(),
ImageKind::Svg(_) => VectorFormat::Svg.into(), ImageKind::Svg(_) => VectorFormat::Svg.into(),
ImageKind::Pdf(_) => VectorFormat::Pdf.into(),
} }
} }
@ -381,6 +399,7 @@ impl Image {
match &self.0.kind { match &self.0.kind {
ImageKind::Raster(raster) => raster.width() as f64, ImageKind::Raster(raster) => raster.width() as f64,
ImageKind::Svg(svg) => svg.width(), ImageKind::Svg(svg) => svg.width(),
ImageKind::Pdf(pdf) => pdf.width() as f64,
} }
} }
@ -389,6 +408,7 @@ impl Image {
match &self.0.kind { match &self.0.kind {
ImageKind::Raster(raster) => raster.height() as f64, ImageKind::Raster(raster) => raster.height() as f64,
ImageKind::Svg(svg) => svg.height(), ImageKind::Svg(svg) => svg.height(),
ImageKind::Pdf(pdf) => pdf.height() as f64,
} }
} }
@ -397,6 +417,7 @@ impl Image {
match &self.0.kind { match &self.0.kind {
ImageKind::Raster(raster) => raster.dpi(), ImageKind::Raster(raster) => raster.dpi(),
ImageKind::Svg(_) => Some(Image::USVG_DEFAULT_DPI), ImageKind::Svg(_) => Some(Image::USVG_DEFAULT_DPI),
ImageKind::Pdf(_) => Some(Image::DEFAULT_DPI),
} }
} }
@ -435,6 +456,8 @@ pub enum ImageKind {
Raster(RasterImage), Raster(RasterImage),
/// An SVG image. /// An SVG image.
Svg(SvgImage), Svg(SvgImage),
/// A PDF image.
Pdf(PdfImage),
} }
impl From<RasterImage> for ImageKind { impl From<RasterImage> for ImageKind {
@ -469,10 +492,20 @@ impl ImageFormat {
return Some(Self::Vector(VectorFormat::Svg)); return Some(Self::Vector(VectorFormat::Svg));
} }
if is_pdf(data) {
return Some(Self::Vector(VectorFormat::Pdf));
}
None None
} }
} }
/// Checks whether the data looks like a PDF file.
fn is_pdf(data: &[u8]) -> bool {
let head = &data[..data.len().min(2048)];
memchr::memmem::find(head, b"%PDF-").is_some()
}
/// Checks whether the data looks like an SVG or a compressed SVG. /// Checks whether the data looks like an SVG or a compressed SVG.
fn is_svg(data: &[u8]) -> bool { fn is_svg(data: &[u8]) -> bool {
// Check for the gzip magic bytes. This check is perhaps a bit too // Check for the gzip magic bytes. This check is perhaps a bit too
@ -493,6 +526,8 @@ fn is_svg(data: &[u8]) -> bool {
pub enum VectorFormat { pub enum VectorFormat {
/// The vector graphics format of the web. /// The vector graphics format of the web.
Svg, Svg,
/// The PDF graphics format.
Pdf,
} }
impl<R> From<R> for ImageFormat impl<R> From<R> for ImageFormat

View File

@ -0,0 +1,96 @@
use crate::diag::LoadResult;
use crate::foundations::Bytes;
use hayro_syntax::pdf::Pdf;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use hayro_syntax::document::page::Page;
#[derive(Clone)]
struct DocumentRepr {
pdf: Arc<Pdf>,
data: Bytes,
page_sizes: Vec<(f32, f32)>,
}
impl Hash for DocumentRepr {
fn hash<H: Hasher>(&self, state: &mut H) {
self.data.hash(state);
}
}
/// A PDF document.
#[derive(Clone, Hash)]
pub struct PdfDocument(Arc<DocumentRepr>);
impl PdfDocument {
/// Load a PDF document.
#[comemo::memoize]
#[typst_macros::time(name = "load pdf document")]
pub fn new(data: Bytes) -> LoadResult<PdfDocument> {
// TODO: Remove unwraps
let pdf = Arc::new(Pdf::new(Arc::new(data.clone())).unwrap());
let pages = pdf.pages().unwrap();
let page_sizes = pages.get().iter().map(|p| p.render_dimensions()).collect();
Ok(Self(Arc::new(DocumentRepr { data, pdf, page_sizes })))
}
}
struct ImageRepr {
pub document: PdfDocument,
pub page_index: usize,
pub width: f32,
pub height: f32,
}
impl Hash for ImageRepr {
fn hash<H: Hasher>(&self, state: &mut H) {
self.document.hash(state);
self.page_index.hash(state);
}
}
/// A page of a PDF file.
#[derive(Clone, Hash)]
pub struct PdfImage(Arc<ImageRepr>);
impl PdfImage {
#[comemo::memoize]
pub fn new(document: PdfDocument, page: usize) -> LoadResult<PdfImage> {
// TODO: Don't allow loading if pdf-embedding feature is disabled.
// TODO: Remove Unwrap
let dimensions = *(&document.0).page_sizes.get(page).unwrap();
Ok(Self(Arc::new(ImageRepr {
document,
page_index: page,
width: dimensions.0,
height: dimensions.1,
})))
}
pub fn with_page<F, R>(&self, f: F) -> R
where
F: FnOnce(&Page) -> R,
{
let pages = self.0.document.0.pdf.pages().unwrap();
f(&pages.get().get(self.0.page_index).unwrap())
}
pub fn width(&self) -> f32 {
self.0.width
}
pub fn height(&self) -> f32 {
self.0.height
}
pub fn data(&self) -> &Bytes {
&self.0.document.0.data
}
pub fn page_index(&self) -> usize {
self.0.page_index
}
}

View File

@ -363,6 +363,11 @@ fn finish(
hint: "convert the image to 8 bit instead" hint: "convert the image to 8 bit instead"
) )
} }
KrillaError::Pdf(_, e, loc) => {
// TODO: Better errors
let span = to_span(loc);
bail!(span, "failed to process PDF");
}
}, },
} }
} }
@ -576,6 +581,17 @@ fn convert_error(
"{prefix} missing document date"; "{prefix} missing document date";
hint: "set the date of the document" hint: "set the date of the document"
), ),
ValidationError::DuplicateTagId(_, loc) => error!(
to_span(*loc),
"{prefix} duplicate tag id";
hint: "please report this as a bug"
),
ValidationError::UnknownTagId(_, loc) => error!(
to_span(*loc),
"{prefix} unknown tag id";
hint: "please report this as a bug"
),
ValidationError::EmbeddedPDF(loc) => error!(to_span(*loc), "TODO"),
} }
} }

View File

@ -3,6 +3,7 @@ use std::sync::{Arc, OnceLock};
use image::{DynamicImage, EncodableLayout, GenericImageView, Rgba}; use image::{DynamicImage, EncodableLayout, GenericImageView, Rgba};
use krilla::image::{BitsPerComponent, CustomImage, ImageColorspace}; use krilla::image::{BitsPerComponent, CustomImage, ImageColorspace};
use krilla::pdf::PdfDocument;
use krilla::surface::Surface; use krilla::surface::Surface;
use krilla_svg::{SurfaceExt, SvgSettings}; use krilla_svg::{SurfaceExt, SvgSettings};
use typst_library::diag::{bail, SourceResult}; use typst_library::diag::{bail, SourceResult};
@ -60,6 +61,15 @@ pub(crate) fn handle_image(
SvgSettings { embed_text: true, ..Default::default() }, SvgSettings { embed_text: true, ..Default::default() },
); );
} }
ImageKind::Pdf(pdf) => {
let pdf_data: Arc<dyn AsRef<[u8]> + Send + Sync> =
Arc::new(pdf.data().clone());
surface.draw_pdf_page(
&PdfDocument::new(pdf_data.into()).unwrap(),
size.to_krilla(),
pdf.page_index(),
)
}
} }
if image.alt().is_some() { if image.alt().is_some() {

View File

@ -18,6 +18,7 @@ typst-macros = { workspace = true }
typst-timing = { workspace = true } typst-timing = { workspace = true }
bytemuck = { workspace = true } bytemuck = { workspace = true }
comemo = { workspace = true } comemo = { workspace = true }
hayro-render = { workspace = true }
image = { workspace = true } image = { workspace = true }
pixglyph = { workspace = true } pixglyph = { workspace = true }
resvg = { workspace = true } resvg = { workspace = true }

View File

@ -1,8 +1,9 @@
use std::sync::Arc; use std::sync::Arc;
use hayro_render::{InterpreterSettings, RenderSettings};
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 tiny_skia::IntSize;
use typst_library::foundations::Smart; 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};
@ -59,9 +60,9 @@ pub fn render_image(
/// Prepare a texture for an image at a scaled size. /// Prepare a texture for an image at a scaled size.
#[comemo::memoize] #[comemo::memoize]
fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> { fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> {
let mut texture = sk::Pixmap::new(w, h)?; let texture = match image.kind() {
match image.kind() {
ImageKind::Raster(raster) => { ImageKind::Raster(raster) => {
let mut texture = sk::Pixmap::new(w, h)?;
let w = texture.width(); let w = texture.width();
let h = texture.height(); let h = texture.height();
@ -85,15 +86,37 @@ fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> {
let Rgba([r, g, b, a]) = src; let Rgba([r, g, b, a]) = src;
*dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply(); *dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply();
} }
texture
} }
ImageKind::Svg(svg) => { ImageKind::Svg(svg) => {
let mut texture = sk::Pixmap::new(w, h)?;
let tree = svg.tree(); let tree = svg.tree();
let ts = tiny_skia::Transform::from_scale( let ts = tiny_skia::Transform::from_scale(
w as f32 / tree.size().width(), w as f32 / tree.size().width(),
h as f32 / tree.size().height(), h as f32 / tree.size().height(),
); );
resvg::render(tree, ts, &mut texture.as_mut()); resvg::render(tree, ts, &mut texture.as_mut());
texture
} }
} ImageKind::Pdf(pdf) => {
let hayro_pix = pdf.with_page(|page| {
// TODO: Configure so that PDF standard fonts can be resolved
let interpreter_settings = InterpreterSettings::default();
let render_settings = RenderSettings {
x_scale: w as f32 / pdf.width(),
y_scale: h as f32 / pdf.height(),
width: Some(w as u16),
height: Some(h as u16)
};
hayro_render::render(page, &interpreter_settings, &render_settings)
});
sk::Pixmap::from_vec(hayro_pix.data_as_u8_slice().to_vec(), IntSize::from_wh(w, h)?)?
},
};
Some(Arc::new(texture)) Some(Arc::new(texture))
} }

View File

@ -66,6 +66,7 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
}), }),
}, },
ImageKind::Svg(svg) => ("svg+xml", svg.data()), ImageKind::Svg(svg) => ("svg+xml", svg.data()),
ImageKind::Pdf(_) => todo!(),
}; };
let mut url = eco_format!("data:image/{format};base64,"); let mut url = eco_format!("data:image/{format};base64,");