diff --git a/crates/typst-pdf/src/krilla.rs b/crates/typst-pdf/src/krilla.rs index 479268a9d..6d6b5ddd4 100644 --- a/crates/typst-pdf/src/krilla.rs +++ b/crates/typst-pdf/src/krilla.rs @@ -1,8 +1,6 @@ -use crate::content_old::Builder; use crate::primitive::{PointExt, SizeExt, TransformExt}; use crate::{paint, AbsExt}; use bytemuck::TransparentWrapper; -use image::GenericImageView; use krilla::action::{Action, LinkAction}; use krilla::annotation::{LinkAnnotation, Target}; use krilla::destination::XyzDestination; @@ -13,10 +11,10 @@ use krilla::validation::Validator; use krilla::version::PdfVersion; use krilla::{PageSettings, SerializeSettings, SvgSettings}; use std::collections::HashMap; -use std::hash::{Hash, Hasher}; use std::ops::Range; use std::sync::Arc; use svg2pdf::usvg::Rect; +use typst_library::diag::{bail, SourceResult}; use typst_library::layout::{ Abs, Frame, FrameItem, GroupItem, PagedDocument, Point, Size, Transform, }; @@ -25,9 +23,10 @@ use typst_library::text::{Font, Glyph, TextItem}; use typst_library::visualize::{ FillRule, Geometry, Image, ImageKind, Paint, Path, PathItem, Shape, }; +use typst_syntax::Span; #[derive(Debug, Clone)] -struct State { +pub(crate) struct State { /// The full transform chain transform_chain: Transform, /// The transform of the current item. @@ -70,7 +69,6 @@ impl State { pub fn transforms(&self, size: Size) -> Transforms { Transforms { transform_chain_: self.transform_chain, - transform_: self.transform, container_transform_chain: self.container_transform_chain, container_size: self.size, size, @@ -113,8 +111,6 @@ impl FrameContext { pub(super) struct Transforms { /// The full transform chain. pub transform_chain_: Transform, - /// The transform of the current item. - pub transform_: Transform, /// The transform of first hard frame in the hierarchy. pub container_transform_chain: Transform, /// The size of the first hard frame in the hierarchy. @@ -155,18 +151,22 @@ impl krilla::font::Glyph for PdfGlyph { pub struct GlobalContext { fonts: HashMap, + // Note: In theory, the same image can have multiple spans + // if it appears in the document multiple times. We just store the + // first appearance, though. + image_spans: HashMap, } impl GlobalContext { pub fn new() -> Self { - Self { fonts: Default::default() } + Self { fonts: HashMap::new(), image_spans: HashMap::new() } } } // TODO: Change rustybuzz cluster behavior so it works with ActualText #[typst_macros::time(name = "write pdf")] -pub fn pdf(typst_document: &PagedDocument) -> Vec { +pub fn pdf(typst_document: &PagedDocument) -> SourceResult> { let settings = SerializeSettings { compress_content_streams: true, no_device_cs: true, @@ -196,7 +196,7 @@ pub fn pdf(typst_document: &PagedDocument) -> Vec { typst_page.fill_or_transparent(), &mut surface, &mut context, - ); + )?; surface.finish(); for annotation in fc.annotations { @@ -204,13 +204,7 @@ pub fn pdf(typst_document: &PagedDocument) -> Vec { } } - finish(document) -} - -#[typst_macros::time(name = "finish document")] -pub fn finish(document: krilla::Document) -> Vec { - // TODO: Don't unwrap - document.finish().unwrap() + Ok(document.finish().unwrap()) } pub fn process_frame( @@ -219,7 +213,7 @@ pub fn process_frame( fill: Option, surface: &mut Surface, gc: &mut GlobalContext, -) { +) -> SourceResult<()> { fc.push(); if frame.kind().is_hard() { @@ -229,18 +223,18 @@ pub fn process_frame( if let Some(fill) = fill { let shape = Geometry::Rect(frame.size()).filled(fill); - handle_shape(fc, &shape, surface, gc); + handle_shape(fc, &shape, surface, gc)?; } for (point, item) in frame.items() { fc.push(); fc.state_mut().transform(Transform::translate(point.x, point.y)); match item { - FrameItem::Group(g) => handle_group(fc, g, surface, gc), - FrameItem::Text(t) => handle_text(fc, t, surface, gc), - FrameItem::Shape(s, _) => handle_shape(fc, s, surface, gc), + FrameItem::Group(g) => handle_group(fc, g, surface, gc)?, + FrameItem::Text(t) => handle_text(fc, t, surface, gc)?, + FrameItem::Shape(s, _) => handle_shape(fc, s, surface, gc)?, FrameItem::Image(image, size, span) => { - handle_image(fc, image, *size, surface) + handle_image(gc, fc, image, *size, surface, *span)? } FrameItem::Link(d, s) => write_link(fc, d, *s), FrameItem::Tag(_) => {} @@ -250,6 +244,8 @@ pub fn process_frame( } fc.pop(); + + Ok(()) } /// Save a link for later writing in the annotations dictionary. @@ -304,7 +300,7 @@ pub fn handle_group( group: &GroupItem, surface: &mut Surface, context: &mut GlobalContext, -) { +) -> SourceResult<()> { fc.push(); fc.state_mut().transform(group.transform); @@ -322,13 +318,15 @@ pub fn handle_group( surface.push_clip_path(clip_path, &krilla::path::FillRule::NonZero); } - process_frame(fc, &group.frame, None, surface, context); + process_frame(fc, &group.frame, None, surface, context)?; if clip_path.is_some() { surface.pop(); } fc.pop(); + + Ok(()) } pub fn handle_text( @@ -336,7 +334,7 @@ pub fn handle_text( t: &TextItem, surface: &mut Surface, gc: &mut GlobalContext, -) { +) -> SourceResult<()> { let font = gc .fonts .entry(t.font.clone()) @@ -353,7 +351,7 @@ pub fn handle_text( true, surface, fc.state().transforms(Size::zero()), - ); + )?; let text = t.text.as_str(); let size = t.size; @@ -377,6 +375,8 @@ pub fn handle_text( .as_ref() .map(|s| paint::stroke(gc, s, true, surface, fc.state().transforms(Size::zero()))) { + let stroke = stroke?; + surface.stroke_glyphs( krilla::geom::Point::from_xy(0.0, 0.0), stroke, @@ -390,20 +390,31 @@ pub fn handle_text( } surface.pop(); + + Ok(()) } pub fn handle_image( + gc: &mut GlobalContext, fc: &mut FrameContext, image: &Image, size: Size, surface: &mut Surface, -) { + span: Span, +) -> SourceResult<()> { surface.push_transform(&fc.state().transform.as_krilla()); match image.kind() { ImageKind::Raster(raster) => { - // TODO: Don't unwrap - let image = crate::image::raster(raster.clone()).unwrap(); + let image = match crate::image::raster(raster.clone()) { + None => bail!(span, "failed to process image"), + Some(i) => i, + }; + + if gc.image_spans.contains_key(&image) { + gc.image_spans.insert(image.clone(), span); + } + surface.draw_image(image, size.as_krilla()); } ImageKind::Svg(svg) => { @@ -419,6 +430,8 @@ pub fn handle_image( } surface.pop(); + + Ok(()) } pub fn handle_shape( @@ -426,7 +439,7 @@ pub fn handle_shape( shape: &Shape, surface: &mut Surface, gc: &mut GlobalContext, -) { +) -> SourceResult<()> { let mut path_builder = PathBuilder::new(); match &shape.geometry { @@ -470,7 +483,7 @@ pub fn handle_shape( false, surface, fc.state().transforms(shape.geometry.bbox_size()), - ); + )?; surface.fill_path(&path, fill); } @@ -489,12 +502,14 @@ pub fn handle_shape( false, surface, fc.state().transforms(shape.geometry.bbox_size()), - ); + )?; surface.stroke_path(&path, stroke); } } surface.pop(); + + Ok(()) } pub fn convert_path(path: &Path, builder: &mut PathBuilder) { diff --git a/crates/typst-pdf/src/lib.rs b/crates/typst-pdf/src/lib.rs index 614d8236b..1e526be97 100644 --- a/crates/typst-pdf/src/lib.rs +++ b/crates/typst-pdf/src/lib.rs @@ -53,30 +53,30 @@ use crate::resources_old::{ /// Returns the raw bytes making up the PDF file. #[typst_macros::time(name = "pdf")] pub fn pdf(document: &PagedDocument, options: &PdfOptions) -> SourceResult> { - return Ok(krilla::pdf(document)); - PdfBuilder::new(document, options) - .phase(|builder| builder.run(traverse_pages))? - .phase(|builder| { - Ok(GlobalRefs { - color_functions: builder.run(alloc_color_functions_refs)?, - pages: builder.run(alloc_page_refs)?, - resources: builder.run(alloc_resources_refs)?, - }) - })? - .phase(|builder| { - Ok(References { - named_destinations: builder.run(write_named_destinations)?, - fonts: builder.run(write_fonts)?, - color_fonts: builder.run(write_color_fonts)?, - images: builder.run(write_images)?, - gradients: builder.run(write_gradients)?, - patterns: builder.run(write_patterns)?, - ext_gs: builder.run(write_graphic_states)?, - }) - })? - .phase(|builder| builder.run(write_page_tree))? - .phase(|builder| builder.run(write_resource_dictionaries))? - .export_with(write_catalog) + krilla::pdf(document) + // PdfBuilder::new(document, options) + // .phase(|builder| builder.run(traverse_pages))? + // .phase(|builder| { + // Ok(GlobalRefs { + // color_functions: builder.run(alloc_color_functions_refs)?, + // pages: builder.run(alloc_page_refs)?, + // resources: builder.run(alloc_resources_refs)?, + // }) + // })? + // .phase(|builder| { + // Ok(References { + // named_destinations: builder.run(write_named_destinations)?, + // fonts: builder.run(write_fonts)?, + // color_fonts: builder.run(write_color_fonts)?, + // images: builder.run(write_images)?, + // gradients: builder.run(write_gradients)?, + // patterns: builder.run(write_patterns)?, + // ext_gs: builder.run(write_graphic_states)?, + // }) + // })? + // .phase(|builder| builder.run(write_page_tree))? + // .phase(|builder| builder.run(write_resource_dictionaries))? + // .export_with(write_catalog) } /// Settings for PDF export. diff --git a/crates/typst-pdf/src/paint.rs b/crates/typst-pdf/src/paint.rs index f6c83ba3e..c4978b193 100644 --- a/crates/typst-pdf/src/paint.rs +++ b/crates/typst-pdf/src/paint.rs @@ -8,7 +8,8 @@ use krilla::page::{NumberingStyle, PageLabel}; use krilla::paint::SpreadMethod; use krilla::surface::Surface; use std::num::NonZeroUsize; -use typst_library::layout::{Abs, Angle, AngleUnit, Quadrant, Ratio, Size, Transform}; +use typst_library::diag::SourceResult; +use typst_library::layout::{Abs, Angle, Quadrant, Ratio, Transform}; use typst_library::model::Numbering; use typst_library::visualize::{ Color, ColorSpace, DashPattern, FillRule, FixedStroke, Gradient, Paint, Pattern, @@ -23,14 +24,14 @@ pub(crate) fn fill( on_text: bool, surface: &mut Surface, transforms: Transforms, -) -> krilla::path::Fill { - let (paint, opacity) = paint(gc, paint_, on_text, surface, transforms); +) -> SourceResult { + let (paint, opacity) = paint(gc, paint_, on_text, surface, transforms)?; - krilla::path::Fill { + Ok(krilla::path::Fill { paint, rule: fill_rule_.as_krilla(), opacity: NormalizedF32::new(opacity as f32 / 255.0).unwrap(), - } + }) } pub(crate) fn stroke( @@ -39,9 +40,10 @@ pub(crate) fn stroke( on_text: bool, surface: &mut Surface, transforms: Transforms, -) -> krilla::path::Stroke { - let (paint, opacity) = paint(fc, &stroke.paint, on_text, surface, transforms); - krilla::path::Stroke { +) -> SourceResult { + let (paint, opacity) = paint(fc, &stroke.paint, on_text, surface, transforms)?; + + Ok(krilla::path::Stroke { paint, width: stroke.thickness.to_f32(), miter_limit: stroke.miter_limit.get() as f32, @@ -49,7 +51,7 @@ pub(crate) fn stroke( line_cap: stroke.cap.as_krilla(), opacity: NormalizedF32::new(opacity as f32 / 255.0).unwrap(), dash: stroke.dash.as_ref().map(|d| dash(d)), - } + }) } fn dash(dash: &DashPattern) -> krilla::path::StrokeDash { @@ -74,13 +76,13 @@ fn paint( on_text: bool, surface: &mut Surface, transforms: Transforms, -) -> (krilla::paint::Paint, u8) { +) -> SourceResult<(krilla::paint::Paint, u8)> { match paint { Paint::Solid(c) => { let (c, alpha) = convert_color(c); - (c.into(), alpha) + Ok((c.into(), alpha)) } - Paint::Gradient(g) => convert_gradient(g, on_text, transforms), + Paint::Gradient(g) => Ok(convert_gradient(g, on_text, transforms)), Paint::Pattern(p) => convert_pattern(gc, p, on_text, surface, transforms), } } @@ -146,7 +148,7 @@ pub(crate) fn convert_pattern( on_text: bool, surface: &mut Surface, mut transforms: Transforms, -) -> (krilla::paint::Paint, u8) { +) -> SourceResult<(krilla::paint::Paint, u8)> { // Edge cases for strokes. if transforms.size.x.is_zero() { transforms.size.x = Abs::pt(1.0); @@ -169,7 +171,7 @@ pub(crate) fn convert_pattern( let mut stream_builder = surface.stream_builder(); let mut surface = stream_builder.surface(); let mut fc = FrameContext::new(pattern.frame().size()); - process_frame(&mut fc, pattern.frame(), None, &mut surface, gc); + process_frame(&mut fc, pattern.frame(), None, &mut surface, gc)?; surface.finish(); let stream = stream_builder.finish(); let pattern = krilla::paint::Pattern { @@ -179,7 +181,7 @@ pub(crate) fn convert_pattern( height: (pattern.size().y + pattern.spacing().y).to_pt() as _, }; - (pattern.into(), 255) + Ok((pattern.into(), 255)) } fn convert_gradient( @@ -234,7 +236,6 @@ fn convert_gradient( // If we have a hue index or are using Oklab, we will create several // stops in-between to make the gradient smoother without interpolation // issues with native color spaces. - let mut last_c = first.0; if gradient.space().hue_index().is_some() { for i in 0..=32 { let t = i as f64 / 32.0; @@ -243,7 +244,6 @@ fn convert_gradient( let c = gradient.sample(RatioOrAngle::Ratio(real_t)); add_single(&c, real_t); - last_c = c; } }