A bit of tidying

This commit is contained in:
Laurenz 2025-07-22 12:59:49 +02:00
parent f9595e747d
commit c80d8b07c7
11 changed files with 124 additions and 207 deletions

6
Cargo.lock generated
View File

@ -1424,7 +1424,7 @@ dependencies = [
[[package]] [[package]]
name = "krilla" name = "krilla"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/LaurenzV/krilla/?rev=37b9a00#37b9a00bfac87ed0b347b7cf8e9d37a6f68fcccd" source = "git+https://github.com/LaurenzV/krilla?rev=37b9a00#37b9a00bfac87ed0b347b7cf8e9d37a6f68fcccd"
dependencies = [ dependencies = [
"base64", "base64",
"bumpalo", "bumpalo",
@ -1454,7 +1454,7 @@ 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=37b9a00#37b9a00bfac87ed0b347b7cf8e9d37a6f68fcccd" source = "git+https://github.com/LaurenzV/krilla?rev=37b9a00#37b9a00bfac87ed0b347b7cf8e9d37a6f68fcccd"
dependencies = [ dependencies = [
"flate2", "flate2",
"fontdb", "fontdb",
@ -3237,6 +3237,7 @@ dependencies = [
"resvg", "resvg",
"tiny-skia", "tiny-skia",
"ttf-parser", "ttf-parser",
"typst-assets",
"typst-library", "typst-library",
"typst-macros", "typst-macros",
"typst-timing", "typst-timing",
@ -3253,6 +3254,7 @@ dependencies = [
"hayro", "hayro",
"image", "image",
"ttf-parser", "ttf-parser",
"typst-assets",
"typst-library", "typst-library",
"typst-macros", "typst-macros",
"typst-timing", "typst-timing",

View File

@ -74,8 +74,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 = "37b9a00", default-features = false, features = ["raster-images", "comemo", "rayon", "pdf"] } krilla = { git = "https://github.com/LaurenzV/krilla", rev = "37b9a00", default-features = false, features = ["raster-images", "comemo", "rayon", "pdf"] }
krilla-svg = { git = "https://github.com/LaurenzV/krilla/", rev = "37b9a00"} krilla-svg = { git = "https://github.com/LaurenzV/krilla", rev = "37b9a00"}
kurbo = "0.11" kurbo = "0.11"
libfuzzer-sys = "0.4" libfuzzer-sys = "0.4"
lipsum = "0.9" lipsum = "0.9"

View File

@ -12,12 +12,13 @@ pub use self::svg::SvgImage;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::num::NonZeroUsize;
use std::sync::Arc; use std::sync::Arc;
use ecow::EcoString; use ecow::EcoString;
use hayro_syntax::LoadPdfError; use hayro_syntax::LoadPdfError;
use typst_syntax::{Span, Spanned}; use typst_syntax::{Span, Spanned};
use typst_utils::LazyHash; use typst_utils::{LazyHash, NonZeroExt};
use crate::diag::{At, LoadedWithin, SourceResult, StrResult, bail, warning}; use crate::diag::{At, LoadedWithin, SourceResult, StrResult, bail, warning};
use crate::engine::Engine; use crate::engine::Engine;
@ -129,10 +130,10 @@ 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 /// The page number that should be embedded as an image. This attribute only
/// for PDF files. /// has an effect for PDF files.
#[default(1)] #[default(NonZeroUsize::ONE)]
pub page: usize, pub page: NonZeroUsize,
/// 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
@ -291,28 +292,18 @@ impl Packed<ImageElem> {
}, },
}; };
let page_num = self.page.get(styles); // The user provides the page number start from 1, but further
// down the pipeline, page numbers are 0-based.
if page_num == 0 { let page_num = self.page.get(styles).get();
bail!(
span,
"{page_num} is not a valid page number";
hint: "page numbers for PDF start at 1"
)
};
// The user provides the page number start from 1, but further down the pipeline,
// page numbers are 0-based.
let page_idx = page_num - 1; let page_idx = page_num - 1;
let num_pages = document.len(); let num_pages = document.num_pages();
let Some(pdf_image) = PdfImage::new(document, page_idx) else { let Some(pdf_image) = PdfImage::new(document, page_idx) else {
let pages = if num_pages == 1 { "page" } else { "pages" }; let s = if num_pages == 1 { "" } else { "s" };
bail!( bail!(
span, span,
"page {page_num} doesn't exist"; "page {page_num} does not exist";
hint: "the document only has {num_pages} {pages}" hint: "the document only has {num_pages} page{s}"
); );
}; };
@ -567,7 +558,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. /// High-fidelity document and graphics format, with focus on exact
/// reproduction in print.
Pdf, Pdf,
} }

View File

@ -6,10 +6,34 @@ use hayro_syntax::{LoadPdfError, Pdf};
use crate::foundations::Bytes; use crate::foundations::Bytes;
/// A PDF document.
#[derive(Clone, Hash)]
pub struct PdfDocument(Arc<DocumentRepr>);
/// The internal representation of a `PdfDocument`.
struct DocumentRepr { struct DocumentRepr {
pdf: Arc<Pdf>, pdf: Arc<Pdf>,
data: Bytes, data: Bytes,
standard_fonts: Arc<StandardFonts>, }
impl PdfDocument {
/// Loads a PDF document.
#[comemo::memoize]
#[typst_macros::time(name = "load pdf document")]
pub fn new(data: Bytes) -> Result<PdfDocument, LoadPdfError> {
let pdf = Arc::new(Pdf::new(Arc::new(data.clone()))?);
Ok(Self(Arc::new(DocumentRepr { data, pdf })))
}
/// Returns the underlying PDF document.
pub fn pdf(&self) -> &Arc<Pdf> {
&self.0.pdf
}
/// Return the number of pages in the PDF.
pub fn num_pages(&self) -> usize {
self.0.pdf.pages().len()
}
} }
impl Hash for DocumentRepr { impl Hash for DocumentRepr {
@ -18,27 +42,11 @@ impl Hash for DocumentRepr {
} }
} }
/// A PDF document. /// A specific page of a PDF acting as an image.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
pub struct PdfDocument(Arc<DocumentRepr>); pub struct PdfImage(Arc<ImageRepr>);
impl PdfDocument {
/// Load a PDF document.
#[comemo::memoize]
#[typst_macros::time(name = "load pdf document")]
pub fn new(data: Bytes) -> Result<PdfDocument, LoadPdfError> {
let pdf = Arc::new(Pdf::new(Arc::new(data.clone()))?);
let standard_fonts = get_standard_fonts();
Ok(Self(Arc::new(DocumentRepr { data, pdf, standard_fonts })))
}
/// Return the number of pages in the PDF.
pub fn len(&self) -> usize {
self.0.pdf.pages().len()
}
}
/// The internal representation of a `PdfImage`.
struct ImageRepr { struct ImageRepr {
document: PdfDocument, document: PdfDocument,
page_index: usize, page_index: usize,
@ -46,41 +54,24 @@ struct ImageRepr {
height: f32, height: f32,
} }
impl Hash for ImageRepr {
fn hash<H: Hasher>(&self, state: &mut H) {
self.document.hash(state);
self.page_index.hash(state);
}
}
/// A specific page of a PDF acting as an image.
#[derive(Clone, Hash)]
pub struct PdfImage(Arc<ImageRepr>);
impl PdfImage { impl PdfImage {
/// Create a new PDF image. /// Creates a new PDF image.
/// ///
/// Returns `None` if the page index is not valid. /// Returns `None` if the page index is not valid.
#[comemo::memoize] #[comemo::memoize]
pub fn new(document: PdfDocument, page: usize) -> Option<PdfImage> { pub fn new(document: PdfDocument, page_index: usize) -> Option<PdfImage> {
let dimensions = document.0.pdf.pages().get(page)?.render_dimensions(); let (width, height) = document.0.pdf.pages().get(page_index)?.render_dimensions();
Some(Self(Arc::new(ImageRepr { document, page_index, width, height })))
}
Some(Self(Arc::new(ImageRepr { /// Returns the underlying Typst PDF document.
document, pub fn document(&self) -> &PdfDocument {
page_index: page, &self.0.document
width: dimensions.0,
height: dimensions.1,
})))
} }
/// Returns the PDF page of the image. /// Returns the PDF page of the image.
pub fn page(&self) -> &Page { pub fn page(&self) -> &Page {
&self.pdf().pages()[self.0.page_index] &self.document().pdf().pages()[self.0.page_index]
}
/// Returns the underlying PDF document.
pub fn pdf(&self) -> &Arc<Pdf> {
&self.0.document.0.pdf
} }
/// Returns the width of the image. /// Returns the width of the image.
@ -88,11 +79,6 @@ impl PdfImage {
self.0.width self.0.width
} }
/// Returns the embedded standard fonts of the image.
pub fn standard_fonts(&self) -> &Arc<StandardFonts> {
&self.0.document.0.standard_fonts
}
/// Returns the height of the image. /// Returns the height of the image.
pub fn height(&self) -> f32 { pub fn height(&self) -> f32 {
self.0.height self.0.height
@ -102,64 +88,11 @@ impl PdfImage {
pub fn page_index(&self) -> usize { pub fn page_index(&self) -> usize {
self.0.page_index self.0.page_index
} }
}
/// Returns the underlying Typst PDF document. impl Hash for ImageRepr {
pub fn document(&self) -> &PdfDocument { fn hash<H: Hasher>(&self, state: &mut H) {
&self.0.document self.document.hash(state);
self.page_index.hash(state);
} }
} }
#[comemo::memoize]
fn get_standard_fonts() -> Arc<StandardFonts> {
let helvetica = VariantFont {
normal: Bytes::new(typst_assets::pdf::SANS),
bold: Bytes::new(typst_assets::pdf::SANS_BOLD),
italic: Bytes::new(typst_assets::pdf::SANS_ITALIC),
bold_italic: Bytes::new(typst_assets::pdf::SANS_BOLD_ITALIC),
};
let courier = VariantFont {
normal: Bytes::new(typst_assets::pdf::FIXED),
bold: Bytes::new(typst_assets::pdf::FIXED_BOLD),
italic: Bytes::new(typst_assets::pdf::FIXED_ITALIC),
bold_italic: Bytes::new(typst_assets::pdf::FIXED_BOLD_ITALIC),
};
let times = VariantFont {
normal: Bytes::new(typst_assets::pdf::SERIF),
bold: Bytes::new(typst_assets::pdf::SERIF_BOLD),
italic: Bytes::new(typst_assets::pdf::SERIF_ITALIC),
bold_italic: Bytes::new(typst_assets::pdf::SERIF_BOLD_ITALIC),
};
let symbol = Bytes::new(typst_assets::pdf::SYMBOL);
let zapf_dingbats = Bytes::new(typst_assets::pdf::DING_BATS);
Arc::new(StandardFonts { helvetica, courier, times, symbol, zapf_dingbats })
}
/// A PDF font with multiple variants.
pub struct VariantFont {
/// The normal variant.
pub normal: Bytes,
/// The bold variant.
pub bold: Bytes,
/// The italic variant.
pub italic: Bytes,
/// The bold-italic variant.
pub bold_italic: Bytes,
}
/// A structure holding the raw data of all PDF standard fonts.
pub struct StandardFonts {
/// The data for the `Helvetica` font family.
pub helvetica: VariantFont,
/// The data for the `Courier` font family.
pub courier: VariantFont,
/// The data for the `Times` font family.
pub times: VariantFont,
/// The data for the `Symbol` font family.
pub symbol: Bytes,
/// The data for the `Zapf Dingbats` font family.
pub zapf_dingbats: Bytes,
}

View File

@ -368,17 +368,20 @@ fn finish(
let span = to_span(loc); let span = to_span(loc);
match e { match e {
// We already validated in `typst-library` that the page index is valid. // We already validated in `typst-library` that the page index is valid.
PdfError::InvalidPage(_) => unreachable!(), PdfError::InvalidPage(_) => bail!(
span,
"invalid page number for PDF file";
hint: "please report this as a bug"
),
PdfError::VersionMismatch(v) => { PdfError::VersionMismatch(v) => {
let pdf_ver = v.as_str(); let pdf_ver = v.as_str();
let config_ver = configuration.version(); let config_ver = configuration.version();
let cur_ver = config_ver.as_str(); let cur_ver = config_ver.as_str();
bail!(span, bail!(span,
"the version of the PDF file is too high"; "the version of the PDF is too high";
hint: "the current export target is {cur_ver}, while the PDF has version {pdf_ver}"; hint: "the current export target is {cur_ver}, while the PDF has version {pdf_ver}";
hint: "raise the export target to {pdf_ver} or higher"; hint: "raise the export target to {pdf_ver} or higher";
hint: "preprocess the PDF to convert it to a lower version" hint: "or preprocess the PDF to convert it to a lower version"
); );
} }
} }
@ -387,14 +390,14 @@ fn finish(
let span = to_span(loc); let span = to_span(loc);
bail!(span, bail!(span,
"duplicate tag id"; "duplicate tag id";
hint: "this is a bug in typst, please report it" hint: "please report this as a bug"
); );
} }
KrillaError::UnknownTagId(_, loc) => { KrillaError::UnknownTagId(_, loc) => {
let span = to_span(loc); let span = to_span(loc);
bail!(span, bail!(span,
"unknown tag id"; "unknown tag id";
hint: "this is a bug in typst, please report it" hint: "please report this as a bug"
); );
} }
}, },
@ -614,7 +617,7 @@ fn convert_error(
error!( error!(
to_span(*loc), to_span(*loc),
"embedding PDFs is currently not supported in this export mode"; "embedding PDFs is currently not supported in this export mode";
hint: "try converting the PDF to SVG before embedding it" hint: "try converting the PDF to an SVG before embedding it"
) )
} }
} }

View File

@ -206,7 +206,7 @@ fn convert_raster(
#[comemo::memoize] #[comemo::memoize]
fn convert_pdf(pdf: &PdfImage) -> PdfDocument { fn convert_pdf(pdf: &PdfImage) -> PdfDocument {
PdfDocument::new(pdf.pdf().clone()) PdfDocument::new(pdf.document().pdf().clone())
} }
fn exif_transform(image: &RasterImage, size: Size) -> (Transform, Size) { fn exif_transform(image: &RasterImage, size: Size) -> (Transform, Size) {

View File

@ -13,6 +13,7 @@ keywords = { workspace = true }
readme = { workspace = true } readme = { workspace = true }
[dependencies] [dependencies]
typst-assets = { workspace = true }
typst-library = { workspace = true } typst-library = { workspace = true }
typst-macros = { workspace = true } typst-macros = { workspace = true }
typst-timing = { workspace = true } typst-timing = { workspace = true }

View File

@ -97,7 +97,6 @@ fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> {
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 texture
} }
ImageKind::Pdf(pdf) => build_pdf_texture(pdf, w, h)?, ImageKind::Pdf(pdf) => build_pdf_texture(pdf, w, h)?,
@ -108,29 +107,24 @@ fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> {
// Keep this in sync with `typst-svg`! // Keep this in sync with `typst-svg`!
fn build_pdf_texture(pdf: &PdfImage, w: u32, h: u32) -> Option<sk::Pixmap> { fn build_pdf_texture(pdf: &PdfImage, w: u32, h: u32) -> Option<sk::Pixmap> {
let sf = pdf.standard_fonts().clone();
let select_standard_font = move |font: StandardFont| -> Option<(FontData, u32)> { let select_standard_font = move |font: StandardFont| -> Option<(FontData, u32)> {
let bytes = match font { let bytes = match font {
StandardFont::Helvetica => sf.helvetica.normal.clone(), StandardFont::Helvetica => typst_assets::pdf::SANS,
StandardFont::HelveticaBold => sf.helvetica.bold.clone(), StandardFont::HelveticaBold => typst_assets::pdf::SANS_BOLD,
StandardFont::HelveticaOblique => sf.helvetica.italic.clone(), StandardFont::HelveticaOblique => typst_assets::pdf::SANS_ITALIC,
StandardFont::HelveticaBoldOblique => sf.helvetica.bold_italic.clone(), StandardFont::HelveticaBoldOblique => typst_assets::pdf::SANS_BOLD_ITALIC,
StandardFont::Courier => sf.courier.normal.clone(), StandardFont::Courier => typst_assets::pdf::FIXED,
StandardFont::CourierBold => sf.courier.bold.clone(), StandardFont::CourierBold => typst_assets::pdf::FIXED_BOLD,
StandardFont::CourierOblique => sf.courier.italic.clone(), StandardFont::CourierOblique => typst_assets::pdf::FIXED_ITALIC,
StandardFont::CourierBoldOblique => sf.courier.bold_italic.clone(), StandardFont::CourierBoldOblique => typst_assets::pdf::FIXED_BOLD_ITALIC,
StandardFont::TimesRoman => sf.times.normal.clone(), StandardFont::TimesRoman => typst_assets::pdf::SERIF,
StandardFont::TimesBold => sf.times.bold.clone(), StandardFont::TimesBold => typst_assets::pdf::SERIF_BOLD,
StandardFont::TimesItalic => sf.times.italic.clone(), StandardFont::TimesItalic => typst_assets::pdf::SERIF_ITALIC,
StandardFont::TimesBoldItalic => sf.times.bold_italic.clone(), StandardFont::TimesBoldItalic => typst_assets::pdf::SERIF_BOLD_ITALIC,
StandardFont::ZapfDingBats => sf.zapf_dingbats.clone(), StandardFont::ZapfDingBats => typst_assets::pdf::DING_BATS,
StandardFont::Symbol => sf.symbol.clone(), StandardFont::Symbol => typst_assets::pdf::SYMBOL,
}; };
Some((Arc::new(bytes), 0))
let font_data: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(bytes.clone());
Some((font_data, 0))
}; };
let interpreter_settings = InterpreterSettings { let interpreter_settings = InterpreterSettings {
@ -140,7 +134,6 @@ fn build_pdf_texture(pdf: &PdfImage, w: u32, h: u32) -> Option<sk::Pixmap> {
}), }),
warning_sink: Arc::new(|_| {}), warning_sink: Arc::new(|_| {}),
}; };
let page = pdf.page();
let render_settings = RenderSettings { let render_settings = RenderSettings {
x_scale: w as f32 / pdf.width(), x_scale: w as f32 / pdf.width(),
@ -149,7 +142,7 @@ fn build_pdf_texture(pdf: &PdfImage, w: u32, h: u32) -> Option<sk::Pixmap> {
height: Some(h as u16), height: Some(h as u16),
}; };
let hayro_pix = hayro::render(page, &interpreter_settings, &render_settings); let hayro_pix = hayro::render(pdf.page(), &interpreter_settings, &render_settings);
sk::Pixmap::from_vec(hayro_pix.take_u8(), IntSize::from_wh(w, h)?) sk::Pixmap::from_vec(hayro_pix.take_u8(), IntSize::from_wh(w, h)?)
} }

View File

@ -13,6 +13,7 @@ keywords = { workspace = true }
readme = { workspace = true } readme = { workspace = true }
[dependencies] [dependencies]
typst-assets = { workspace = true }
typst-library = { workspace = true } typst-library = { workspace = true }
typst-macros = { workspace = true } typst-macros = { workspace = true }
typst-timing = { workspace = true } typst-timing = { workspace = true }

View File

@ -1,4 +1,3 @@
use std::borrow::Cow;
use std::sync::Arc; use std::sync::Arc;
use base64::Engine; use base64::Engine;
@ -48,7 +47,7 @@ pub fn convert_image_scaling(scaling: Smart<ImageScaling>) -> Option<&'static st
#[comemo::memoize] #[comemo::memoize]
pub fn convert_image_to_base64_url(image: &Image) -> EcoString { pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
let mut buf; let mut buf;
let (format, data): (&str, Cow<[u8]>) = match image.kind() { let (format, data): (&str, &[u8]) = match image.kind() {
ImageKind::Raster(raster) => match raster.format() { ImageKind::Raster(raster) => match raster.format() {
RasterFormat::Exchange(format) => ( RasterFormat::Exchange(format) => (
match format { match format {
@ -57,7 +56,7 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
ExchangeFormat::Gif => "gif", ExchangeFormat::Gif => "gif",
ExchangeFormat::Webp => "webp", ExchangeFormat::Webp => "webp",
}, },
Cow::Borrowed(raster.data()), raster.data(),
), ),
RasterFormat::Pixel(_) => ("png", { RasterFormat::Pixel(_) => ("png", {
buf = vec![]; buf = vec![];
@ -66,14 +65,15 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
encoder.set_icc_profile(icc_profile.to_vec()).ok(); encoder.set_icc_profile(icc_profile.to_vec()).ok();
} }
raster.dynamic().write_with_encoder(encoder).unwrap(); raster.dynamic().write_with_encoder(encoder).unwrap();
Cow::Borrowed(buf.as_slice()) buf.as_slice()
}), }),
}, },
ImageKind::Svg(svg) => ("svg+xml", Cow::Borrowed(svg.data())), ImageKind::Svg(svg) => ("svg+xml", svg.data()),
ImageKind::Pdf(pdf) => { ImageKind::Pdf(pdf) => {
// To make sure the image isn't pixelated, we always scale up so the lowest // To make sure the image isn't pixelated, we always scale up so the
// dimension has at least 1000 pixels. However, we only scale up as much so that the // lowest dimension has at least 1000 pixels. However, we only scale
// largest dimension doesn't exceed 3000 pixels. // up as much so that the largest dimension doesn't exceed 3000
// pixels.
const MIN_RES: f32 = 1000.0; const MIN_RES: f32 = 1000.0;
const MAX_RES: f32 = 3000.0; const MAX_RES: f32 = 3000.0;
@ -81,13 +81,12 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
let w_scale = (MIN_RES / base_width).max(MAX_RES / base_width); let w_scale = (MIN_RES / base_width).max(MAX_RES / base_width);
let base_height = pdf.height(); let base_height = pdf.height();
let h_scale = (MIN_RES / base_height).min(MAX_RES / base_height); let h_scale = (MIN_RES / base_height).min(MAX_RES / base_height);
let total_scale = w_scale.min(h_scale); let total_scale = w_scale.min(h_scale);
let width = (base_width * total_scale).ceil() as u32; let width = (base_width * total_scale).ceil() as u32;
let height = (base_height * total_scale).ceil() as u32; let height = (base_height * total_scale).ceil() as u32;
("png", Cow::Owned(pdf_to_png(pdf, width, height))) buf = pdf_to_png(pdf, width, height);
("png", buf.as_slice())
} }
}; };
@ -98,31 +97,25 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
} }
// Keep this in sync with `typst-png`! // Keep this in sync with `typst-png`!
#[comemo::memoize]
fn pdf_to_png(pdf: &PdfImage, w: u32, h: u32) -> Vec<u8> { fn pdf_to_png(pdf: &PdfImage, w: u32, h: u32) -> Vec<u8> {
let sf = pdf.standard_fonts().clone();
let select_standard_font = move |font: StandardFont| -> Option<(FontData, u32)> { let select_standard_font = move |font: StandardFont| -> Option<(FontData, u32)> {
let bytes = match font { let bytes = match font {
StandardFont::Helvetica => sf.helvetica.normal.clone(), StandardFont::Helvetica => typst_assets::pdf::SANS,
StandardFont::HelveticaBold => sf.helvetica.bold.clone(), StandardFont::HelveticaBold => typst_assets::pdf::SANS_BOLD,
StandardFont::HelveticaOblique => sf.helvetica.italic.clone(), StandardFont::HelveticaOblique => typst_assets::pdf::SANS_ITALIC,
StandardFont::HelveticaBoldOblique => sf.helvetica.bold_italic.clone(), StandardFont::HelveticaBoldOblique => typst_assets::pdf::SANS_BOLD_ITALIC,
StandardFont::Courier => sf.courier.normal.clone(), StandardFont::Courier => typst_assets::pdf::FIXED,
StandardFont::CourierBold => sf.courier.bold.clone(), StandardFont::CourierBold => typst_assets::pdf::FIXED_BOLD,
StandardFont::CourierOblique => sf.courier.italic.clone(), StandardFont::CourierOblique => typst_assets::pdf::FIXED_ITALIC,
StandardFont::CourierBoldOblique => sf.courier.bold_italic.clone(), StandardFont::CourierBoldOblique => typst_assets::pdf::FIXED_BOLD_ITALIC,
StandardFont::TimesRoman => sf.times.normal.clone(), StandardFont::TimesRoman => typst_assets::pdf::SERIF,
StandardFont::TimesBold => sf.times.bold.clone(), StandardFont::TimesBold => typst_assets::pdf::SERIF_BOLD,
StandardFont::TimesItalic => sf.times.italic.clone(), StandardFont::TimesItalic => typst_assets::pdf::SERIF_ITALIC,
StandardFont::TimesBoldItalic => sf.times.bold_italic.clone(), StandardFont::TimesBoldItalic => typst_assets::pdf::SERIF_BOLD_ITALIC,
StandardFont::ZapfDingBats => sf.zapf_dingbats.clone(), StandardFont::ZapfDingBats => typst_assets::pdf::DING_BATS,
StandardFont::Symbol => sf.symbol.clone(), StandardFont::Symbol => typst_assets::pdf::SYMBOL,
}; };
Some((Arc::new(bytes), 0))
let font_data: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(bytes.clone());
Some((font_data, 0))
}; };
let interpreter_settings = InterpreterSettings { let interpreter_settings = InterpreterSettings {
@ -132,7 +125,6 @@ fn pdf_to_png(pdf: &PdfImage, w: u32, h: u32) -> Vec<u8> {
}), }),
warning_sink: Arc::new(|_| {}), warning_sink: Arc::new(|_| {}),
}; };
let page = pdf.page();
let render_settings = RenderSettings { let render_settings = RenderSettings {
x_scale: w as f32 / pdf.width(), x_scale: w as f32 / pdf.width(),
@ -141,7 +133,7 @@ fn pdf_to_png(pdf: &PdfImage, w: u32, h: u32) -> Vec<u8> {
height: Some(h as u16), height: Some(h as u16),
}; };
let hayro_pix = hayro::render(page, &interpreter_settings, &render_settings); let hayro_pix = hayro::render(pdf.page(), &interpreter_settings, &render_settings);
hayro_pix.take_png() hayro_pix.take_png()
} }

View File

@ -293,12 +293,12 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
--- image-pdf --- --- image-pdf ---
#image("/assets/images/matplotlib.pdf") #image("/assets/images/matplotlib.pdf")
--- image-pdf-invalid-page ---
// Error: 2-49 page 2 doesn't exist
// Hint: 2-49 the document only has 1 page
#image("/assets/images/matplotlib.pdf", page: 2)
--- image-pdf-multiple-pages --- --- image-pdf-multiple-pages ---
#image("/assets/images/diagrams.pdf", page: 1) #image("/assets/images/diagrams.pdf", page: 1)
#image("/assets/images/diagrams.pdf", page: 3) #image("/assets/images/diagrams.pdf", page: 3)
#image("/assets/images/diagrams.pdf", page: 2) #image("/assets/images/diagrams.pdf", page: 2)
--- image-pdf-invalid-page ---
// Error: 2-49 page 2 does not exist
// Hint: 2-49 the document only has 1 page
#image("/assets/images/matplotlib.pdf", page: 2)