diff --git a/crates/typst-library/src/visualize/image/raster.rs b/crates/typst-library/src/visualize/image/raster.rs index 677127e00..c4293ff9d 100644 --- a/crates/typst-library/src/visualize/image/raster.rs +++ b/crates/typst-library/src/visualize/image/raster.rs @@ -62,7 +62,14 @@ impl RasterImage { // Extract pixel density. let dpi = determine_dpi(&data, exif.as_ref()); - Ok(Self(Arc::new(Repr { data, format, is_rotated, dynamic: Arc::new(dynamic), icc, dpi }))) + Ok(Self(Arc::new(Repr { + data, + format, + is_rotated, + dynamic: Arc::new(dynamic), + icc, + dpi, + }))) } /// The raw image data. diff --git a/crates/typst-pdf/src/image.rs b/crates/typst-pdf/src/image.rs index 0484920b0..086d969d1 100644 --- a/crates/typst-pdf/src/image.rs +++ b/crates/typst-pdf/src/image.rs @@ -111,7 +111,7 @@ pub(crate) fn raster(raster: RasterImage) -> Option { RasterFormat::Jpg => { if !raster.is_rotated() { krilla::image::Image::from_jpeg(Arc::new(raster.data().clone())) - } else { + } else { // Can't embed original JPEG data if it needed to be rotated. krilla::image::Image::from_custom(PdfImage::new(raster)) } diff --git a/crates/typst-pdf/src/image_old.rs b/crates/typst-pdf/src/image_old.rs index 76b86bed2..a3b447c2d 100644 --- a/crates/typst-pdf/src/image_old.rs +++ b/crates/typst-pdf/src/image_old.rs @@ -1,13 +1,9 @@ -use std::collections::HashMap; -use std::io::Cursor; - use ecow::eco_format; -use image::{DynamicImage, GenericImageView, Rgba}; +use image::{GenericImageView, Rgba}; use pdf_writer::{Chunk, Filter, Finish, Ref}; +use std::collections::HashMap; use typst_library::diag::{At, SourceResult, StrResult}; -use typst_library::visualize::{ - ColorSpace, Image, ImageKind, RasterFormat, RasterImage, SvgImage, -}; +use typst_library::visualize::{ColorSpace, Image, ImageKind, RasterImage, SvgImage}; use typst_utils::Deferred; use crate::{color_old, deflate, PdfChunk, WithGlobalRefs}; diff --git a/crates/typst-pdf/src/krilla.rs b/crates/typst-pdf/src/krilla.rs index d0ebf863a..479268a9d 100644 --- a/crates/typst-pdf/src/krilla.rs +++ b/crates/typst-pdf/src/krilla.rs @@ -1,3 +1,4 @@ +use crate::content_old::Builder; use crate::primitive::{PointExt, SizeExt, TransformExt}; use crate::{paint, AbsExt}; use bytemuck::TransparentWrapper; @@ -16,7 +17,9 @@ use std::hash::{Hash, Hasher}; use std::ops::Range; use std::sync::Arc; use svg2pdf::usvg::Rect; -use typst_library::layout::{Abs, Frame, FrameItem, GroupItem, PagedDocument, Point, Ratio, Size, Transform}; +use typst_library::layout::{ + Abs, Frame, FrameItem, GroupItem, PagedDocument, Point, Size, Transform, +}; use typst_library::model::Destination; use typst_library::text::{Font, Glyph, TextItem}; use typst_library::visualize::{ @@ -37,7 +40,11 @@ struct State { impl State { /// Creates a new, clean state for a given `size`. - fn new(size: Size, transform_chain: Transform, container_transform_chain: Transform) -> Self { + fn new( + size: Size, + transform_chain: Transform, + container_transform_chain: Transform, + ) -> Self { Self { transform_chain, transform: Transform::identity(), @@ -73,15 +80,15 @@ impl State { pub(crate) struct FrameContext { states: Vec, + annotations: Vec, } impl FrameContext { pub fn new(size: Size) -> Self { - Self { states: vec![State::new(size, Transform::identity(), Transform::identity())] } - } - - pub fn derive_new(&self, size: Size) -> Self { - Self { states: vec![State::new(size, self.state().transform_chain, self.state().container_transform_chain)] } + Self { + states: vec![State::new(size, Transform::identity(), Transform::identity())], + annotations: vec![], + } } pub fn push(&mut self) { @@ -148,12 +155,11 @@ impl krilla::font::Glyph for PdfGlyph { pub struct GlobalContext { fonts: HashMap, - annotations: Vec, } impl GlobalContext { pub fn new() -> Self { - Self { fonts: Default::default(), annotations: vec![] } + Self { fonts: Default::default() } } } @@ -193,8 +199,7 @@ pub fn pdf(typst_document: &PagedDocument) -> Vec { ); surface.finish(); - let annotations = std::mem::take(&mut context.annotations); - for annotation in annotations { + for annotation in fc.annotations { page.add_annotation(annotation); } } @@ -237,7 +242,7 @@ pub fn process_frame( FrameItem::Image(image, size, span) => { handle_image(fc, image, *size, surface) } - FrameItem::Link(d, s) => {} + FrameItem::Link(d, s) => write_link(fc, d, *s), FrameItem::Tag(_) => {} } @@ -247,6 +252,53 @@ pub fn process_frame( fc.pop(); } +/// Save a link for later writing in the annotations dictionary. +fn write_link(fc: &mut FrameContext, dest: &Destination, size: Size) { + let mut min_x = Abs::inf(); + let mut min_y = Abs::inf(); + let mut max_x = -Abs::inf(); + let mut max_y = -Abs::inf(); + + let pos = Point::zero(); + + // Compute the bounding box of the transformed link. + for point in [ + pos, + pos + Point::with_x(size.x), + pos + Point::with_y(size.y), + pos + size.to_point(), + ] { + let t = point.transform(fc.state().transform); + min_x.set_min(t.x); + min_y.set_min(t.y); + max_x.set_max(t.x); + max_y.set_max(t.y); + } + + let x1 = min_x.to_f32(); + let x2 = max_x.to_f32(); + let y1 = min_y.to_f32(); + let y2 = max_y.to_f32(); + + let rect = Rect::from_ltrb(x1, y1, x2, y2).unwrap(); + + let target = match dest { + Destination::Url(u) => { + Target::Action(Action::Link(LinkAction::new(u.to_string()))) + } + Destination::Position(p) => { + // TODO: Ignore non-exported destinations + Target::Destination(krilla::destination::Destination::Xyz( + XyzDestination::new(p.page.get() - 1, p.point.as_krilla()), + )) + } + // TODO: Implement + Destination::Location(_) => return, + }; + + fc.annotations.push(LinkAnnotation::new(rect, target).into()); +} + pub fn handle_group( fc: &mut FrameContext, group: &GroupItem, @@ -390,8 +442,10 @@ pub fn handle_shape( // Typst supports them, so we apply a transform if needed // Because this operation is expensive according to tiny-skia's // docs, we prefer to not apply it if not needed - let transform = krilla::geom::Transform::from_scale(w.signum(), h.signum()); - Rect::from_xywh(0.0, 0.0, w.abs(), h.abs()).and_then(|rect| rect.transform(transform)) + let transform = + krilla::geom::Transform::from_scale(w.signum(), h.signum()); + Rect::from_xywh(0.0, 0.0, w.abs(), h.abs()) + .and_then(|rect| rect.transform(transform)) } else { Rect::from_xywh(0.0, 0.0, w, h) }; @@ -460,50 +514,3 @@ pub fn convert_path(path: &Path, builder: &mut PathBuilder) { } } } - -// fn handle_link( -// pos: typst_library::layout::Point, -// dest: &Destination, -// size: typst_library::layout::Size, -// ctx: &mut GlobalContext, -// surface: &mut Surface, -// ) { -// let mut min_x = Abs::inf(); -// let mut min_y = Abs::inf(); -// let mut max_x = -Abs::inf(); -// let mut max_y = -Abs::inf(); -// -// // Compute the bounding box of the transformed link. -// for point in [ -// pos, -// pos + Point::with_x(size.x), -// pos + Point::with_y(size.y), -// pos + size.to_point(), -// ] { -// let t = point.transform(ctx.cur_transform); -// min_x.set_min(t.x); -// min_y.set_min(t.y); -// max_x.set_max(t.x); -// max_y.set_max(t.y); -// } -// -// let x1 = min_x.to_f32(); -// let x2 = max_x.to_f32(); -// let y1 = min_y.to_f32(); -// let y2 = max_y.to_f32(); -// let rect = krilla::geom::Rect::from_ltrb(x1, y1, x2, y2).unwrap(); -// -// let target = match dest { -// Destination::Url(u) => { -// Target::Action(Action::Link(LinkAction::new(u.to_string()))) -// } -// Destination::Position(p) => { -// Target::Destination(krilla::destination::Destination::Xyz( -// XyzDestination::new(p.page.get() - 1, p.point.as_krilla()), -// )) -// } -// Destination::Location(_) => return, -// }; -// -// ctx.annotations.push(LinkAnnotation::new(rect, target).into()); -// } diff --git a/crates/typst-pdf/src/paint.rs b/crates/typst-pdf/src/paint.rs index 9d30d3551..f6c83ba3e 100644 --- a/crates/typst-pdf/src/paint.rs +++ b/crates/typst-pdf/src/paint.rs @@ -158,8 +158,13 @@ pub(crate) fn convert_pattern( let transform = match pattern.unwrap_relative(on_text) { RelativeTo::Self_ => Transform::identity(), - RelativeTo::Parent => transforms.transform_chain_.invert().unwrap().pre_concat(transforms.container_transform_chain) - }.as_krilla(); + RelativeTo::Parent => transforms + .transform_chain_ + .invert() + .unwrap() + .pre_concat(transforms.container_transform_chain), + } + .as_krilla(); let mut stream_builder = surface.stream_builder(); let mut surface = stream_builder.surface();