This commit is contained in:
Laurenz Stampfl 2024-12-15 20:10:26 +01:00
parent 761c8cf15e
commit 17abfb3a76
3 changed files with 89 additions and 74 deletions

View File

@ -1,8 +1,6 @@
use crate::content_old::Builder;
use crate::primitive::{PointExt, SizeExt, TransformExt}; use crate::primitive::{PointExt, SizeExt, TransformExt};
use crate::{paint, AbsExt}; use crate::{paint, AbsExt};
use bytemuck::TransparentWrapper; use bytemuck::TransparentWrapper;
use image::GenericImageView;
use krilla::action::{Action, LinkAction}; use krilla::action::{Action, LinkAction};
use krilla::annotation::{LinkAnnotation, Target}; use krilla::annotation::{LinkAnnotation, Target};
use krilla::destination::XyzDestination; use krilla::destination::XyzDestination;
@ -13,10 +11,10 @@ use krilla::validation::Validator;
use krilla::version::PdfVersion; use krilla::version::PdfVersion;
use krilla::{PageSettings, SerializeSettings, SvgSettings}; use krilla::{PageSettings, SerializeSettings, SvgSettings};
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::ops::Range; use std::ops::Range;
use std::sync::Arc; use std::sync::Arc;
use svg2pdf::usvg::Rect; use svg2pdf::usvg::Rect;
use typst_library::diag::{bail, SourceResult};
use typst_library::layout::{ use typst_library::layout::{
Abs, Frame, FrameItem, GroupItem, PagedDocument, Point, Size, Transform, Abs, Frame, FrameItem, GroupItem, PagedDocument, Point, Size, Transform,
}; };
@ -25,9 +23,10 @@ use typst_library::text::{Font, Glyph, TextItem};
use typst_library::visualize::{ use typst_library::visualize::{
FillRule, Geometry, Image, ImageKind, Paint, Path, PathItem, Shape, FillRule, Geometry, Image, ImageKind, Paint, Path, PathItem, Shape,
}; };
use typst_syntax::Span;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct State { pub(crate) struct State {
/// The full transform chain /// The full transform chain
transform_chain: Transform, transform_chain: Transform,
/// The transform of the current item. /// The transform of the current item.
@ -70,7 +69,6 @@ impl State {
pub fn transforms(&self, size: Size) -> Transforms { pub fn transforms(&self, size: Size) -> Transforms {
Transforms { Transforms {
transform_chain_: self.transform_chain, transform_chain_: self.transform_chain,
transform_: self.transform,
container_transform_chain: self.container_transform_chain, container_transform_chain: self.container_transform_chain,
container_size: self.size, container_size: self.size,
size, size,
@ -113,8 +111,6 @@ impl FrameContext {
pub(super) struct Transforms { pub(super) struct Transforms {
/// The full transform chain. /// The full transform chain.
pub transform_chain_: Transform, pub transform_chain_: Transform,
/// The transform of the current item.
pub transform_: Transform,
/// The transform of first hard frame in the hierarchy. /// The transform of first hard frame in the hierarchy.
pub container_transform_chain: Transform, pub container_transform_chain: Transform,
/// The size of the first hard frame in the hierarchy. /// The size of the first hard frame in the hierarchy.
@ -155,18 +151,22 @@ impl krilla::font::Glyph for PdfGlyph {
pub struct GlobalContext { pub struct GlobalContext {
fonts: HashMap<Font, krilla::font::Font>, fonts: HashMap<Font, krilla::font::Font>,
// 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<krilla::image::Image, Span>,
} }
impl GlobalContext { impl GlobalContext {
pub fn new() -> Self { 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 // TODO: Change rustybuzz cluster behavior so it works with ActualText
#[typst_macros::time(name = "write pdf")] #[typst_macros::time(name = "write pdf")]
pub fn pdf(typst_document: &PagedDocument) -> Vec<u8> { pub fn pdf(typst_document: &PagedDocument) -> SourceResult<Vec<u8>> {
let settings = SerializeSettings { let settings = SerializeSettings {
compress_content_streams: true, compress_content_streams: true,
no_device_cs: true, no_device_cs: true,
@ -196,7 +196,7 @@ pub fn pdf(typst_document: &PagedDocument) -> Vec<u8> {
typst_page.fill_or_transparent(), typst_page.fill_or_transparent(),
&mut surface, &mut surface,
&mut context, &mut context,
); )?;
surface.finish(); surface.finish();
for annotation in fc.annotations { for annotation in fc.annotations {
@ -204,13 +204,7 @@ pub fn pdf(typst_document: &PagedDocument) -> Vec<u8> {
} }
} }
finish(document) Ok(document.finish().unwrap())
}
#[typst_macros::time(name = "finish document")]
pub fn finish(document: krilla::Document) -> Vec<u8> {
// TODO: Don't unwrap
document.finish().unwrap()
} }
pub fn process_frame( pub fn process_frame(
@ -219,7 +213,7 @@ pub fn process_frame(
fill: Option<Paint>, fill: Option<Paint>,
surface: &mut Surface, surface: &mut Surface,
gc: &mut GlobalContext, gc: &mut GlobalContext,
) { ) -> SourceResult<()> {
fc.push(); fc.push();
if frame.kind().is_hard() { if frame.kind().is_hard() {
@ -229,18 +223,18 @@ pub fn process_frame(
if let Some(fill) = fill { if let Some(fill) = fill {
let shape = Geometry::Rect(frame.size()).filled(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() { for (point, item) in frame.items() {
fc.push(); fc.push();
fc.state_mut().transform(Transform::translate(point.x, point.y)); fc.state_mut().transform(Transform::translate(point.x, point.y));
match item { match item {
FrameItem::Group(g) => handle_group(fc, g, surface, gc), FrameItem::Group(g) => handle_group(fc, g, surface, gc)?,
FrameItem::Text(t) => handle_text(fc, t, surface, gc), FrameItem::Text(t) => handle_text(fc, t, surface, gc)?,
FrameItem::Shape(s, _) => handle_shape(fc, s, surface, gc), FrameItem::Shape(s, _) => handle_shape(fc, s, surface, gc)?,
FrameItem::Image(image, size, span) => { 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::Link(d, s) => write_link(fc, d, *s),
FrameItem::Tag(_) => {} FrameItem::Tag(_) => {}
@ -250,6 +244,8 @@ pub fn process_frame(
} }
fc.pop(); fc.pop();
Ok(())
} }
/// Save a link for later writing in the annotations dictionary. /// Save a link for later writing in the annotations dictionary.
@ -304,7 +300,7 @@ pub fn handle_group(
group: &GroupItem, group: &GroupItem,
surface: &mut Surface, surface: &mut Surface,
context: &mut GlobalContext, context: &mut GlobalContext,
) { ) -> SourceResult<()> {
fc.push(); fc.push();
fc.state_mut().transform(group.transform); fc.state_mut().transform(group.transform);
@ -322,13 +318,15 @@ pub fn handle_group(
surface.push_clip_path(clip_path, &krilla::path::FillRule::NonZero); 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() { if clip_path.is_some() {
surface.pop(); surface.pop();
} }
fc.pop(); fc.pop();
Ok(())
} }
pub fn handle_text( pub fn handle_text(
@ -336,7 +334,7 @@ pub fn handle_text(
t: &TextItem, t: &TextItem,
surface: &mut Surface, surface: &mut Surface,
gc: &mut GlobalContext, gc: &mut GlobalContext,
) { ) -> SourceResult<()> {
let font = gc let font = gc
.fonts .fonts
.entry(t.font.clone()) .entry(t.font.clone())
@ -353,7 +351,7 @@ pub fn handle_text(
true, true,
surface, surface,
fc.state().transforms(Size::zero()), fc.state().transforms(Size::zero()),
); )?;
let text = t.text.as_str(); let text = t.text.as_str();
let size = t.size; let size = t.size;
@ -377,6 +375,8 @@ pub fn handle_text(
.as_ref() .as_ref()
.map(|s| paint::stroke(gc, s, true, surface, fc.state().transforms(Size::zero()))) .map(|s| paint::stroke(gc, s, true, surface, fc.state().transforms(Size::zero())))
{ {
let stroke = stroke?;
surface.stroke_glyphs( surface.stroke_glyphs(
krilla::geom::Point::from_xy(0.0, 0.0), krilla::geom::Point::from_xy(0.0, 0.0),
stroke, stroke,
@ -390,20 +390,31 @@ pub fn handle_text(
} }
surface.pop(); surface.pop();
Ok(())
} }
pub fn handle_image( pub fn handle_image(
gc: &mut GlobalContext,
fc: &mut FrameContext, fc: &mut FrameContext,
image: &Image, image: &Image,
size: Size, size: Size,
surface: &mut Surface, surface: &mut Surface,
) { span: Span,
) -> SourceResult<()> {
surface.push_transform(&fc.state().transform.as_krilla()); surface.push_transform(&fc.state().transform.as_krilla());
match image.kind() { match image.kind() {
ImageKind::Raster(raster) => { ImageKind::Raster(raster) => {
// TODO: Don't unwrap let image = match crate::image::raster(raster.clone()) {
let image = crate::image::raster(raster.clone()).unwrap(); 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()); surface.draw_image(image, size.as_krilla());
} }
ImageKind::Svg(svg) => { ImageKind::Svg(svg) => {
@ -419,6 +430,8 @@ pub fn handle_image(
} }
surface.pop(); surface.pop();
Ok(())
} }
pub fn handle_shape( pub fn handle_shape(
@ -426,7 +439,7 @@ pub fn handle_shape(
shape: &Shape, shape: &Shape,
surface: &mut Surface, surface: &mut Surface,
gc: &mut GlobalContext, gc: &mut GlobalContext,
) { ) -> SourceResult<()> {
let mut path_builder = PathBuilder::new(); let mut path_builder = PathBuilder::new();
match &shape.geometry { match &shape.geometry {
@ -470,7 +483,7 @@ pub fn handle_shape(
false, false,
surface, surface,
fc.state().transforms(shape.geometry.bbox_size()), fc.state().transforms(shape.geometry.bbox_size()),
); )?;
surface.fill_path(&path, fill); surface.fill_path(&path, fill);
} }
@ -489,12 +502,14 @@ pub fn handle_shape(
false, false,
surface, surface,
fc.state().transforms(shape.geometry.bbox_size()), fc.state().transforms(shape.geometry.bbox_size()),
); )?;
surface.stroke_path(&path, stroke); surface.stroke_path(&path, stroke);
} }
} }
surface.pop(); surface.pop();
Ok(())
} }
pub fn convert_path(path: &Path, builder: &mut PathBuilder) { pub fn convert_path(path: &Path, builder: &mut PathBuilder) {

View File

@ -53,30 +53,30 @@ use crate::resources_old::{
/// Returns the raw bytes making up the PDF file. /// Returns the raw bytes making up the PDF file.
#[typst_macros::time(name = "pdf")] #[typst_macros::time(name = "pdf")]
pub fn pdf(document: &PagedDocument, options: &PdfOptions) -> SourceResult<Vec<u8>> { pub fn pdf(document: &PagedDocument, options: &PdfOptions) -> SourceResult<Vec<u8>> {
return Ok(krilla::pdf(document)); krilla::pdf(document)
PdfBuilder::new(document, options) // PdfBuilder::new(document, options)
.phase(|builder| builder.run(traverse_pages))? // .phase(|builder| builder.run(traverse_pages))?
.phase(|builder| { // .phase(|builder| {
Ok(GlobalRefs { // Ok(GlobalRefs {
color_functions: builder.run(alloc_color_functions_refs)?, // color_functions: builder.run(alloc_color_functions_refs)?,
pages: builder.run(alloc_page_refs)?, // pages: builder.run(alloc_page_refs)?,
resources: builder.run(alloc_resources_refs)?, // resources: builder.run(alloc_resources_refs)?,
}) // })
})? // })?
.phase(|builder| { // .phase(|builder| {
Ok(References { // Ok(References {
named_destinations: builder.run(write_named_destinations)?, // named_destinations: builder.run(write_named_destinations)?,
fonts: builder.run(write_fonts)?, // fonts: builder.run(write_fonts)?,
color_fonts: builder.run(write_color_fonts)?, // color_fonts: builder.run(write_color_fonts)?,
images: builder.run(write_images)?, // images: builder.run(write_images)?,
gradients: builder.run(write_gradients)?, // gradients: builder.run(write_gradients)?,
patterns: builder.run(write_patterns)?, // patterns: builder.run(write_patterns)?,
ext_gs: builder.run(write_graphic_states)?, // ext_gs: builder.run(write_graphic_states)?,
}) // })
})? // })?
.phase(|builder| builder.run(write_page_tree))? // .phase(|builder| builder.run(write_page_tree))?
.phase(|builder| builder.run(write_resource_dictionaries))? // .phase(|builder| builder.run(write_resource_dictionaries))?
.export_with(write_catalog) // .export_with(write_catalog)
} }
/// Settings for PDF export. /// Settings for PDF export.

View File

@ -8,7 +8,8 @@ use krilla::page::{NumberingStyle, PageLabel};
use krilla::paint::SpreadMethod; use krilla::paint::SpreadMethod;
use krilla::surface::Surface; use krilla::surface::Surface;
use std::num::NonZeroUsize; 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::model::Numbering;
use typst_library::visualize::{ use typst_library::visualize::{
Color, ColorSpace, DashPattern, FillRule, FixedStroke, Gradient, Paint, Pattern, Color, ColorSpace, DashPattern, FillRule, FixedStroke, Gradient, Paint, Pattern,
@ -23,14 +24,14 @@ pub(crate) fn fill(
on_text: bool, on_text: bool,
surface: &mut Surface, surface: &mut Surface,
transforms: Transforms, transforms: Transforms,
) -> krilla::path::Fill { ) -> SourceResult<krilla::path::Fill> {
let (paint, opacity) = paint(gc, paint_, on_text, surface, transforms); let (paint, opacity) = paint(gc, paint_, on_text, surface, transforms)?;
krilla::path::Fill { Ok(krilla::path::Fill {
paint, paint,
rule: fill_rule_.as_krilla(), rule: fill_rule_.as_krilla(),
opacity: NormalizedF32::new(opacity as f32 / 255.0).unwrap(), opacity: NormalizedF32::new(opacity as f32 / 255.0).unwrap(),
} })
} }
pub(crate) fn stroke( pub(crate) fn stroke(
@ -39,9 +40,10 @@ pub(crate) fn stroke(
on_text: bool, on_text: bool,
surface: &mut Surface, surface: &mut Surface,
transforms: Transforms, transforms: Transforms,
) -> krilla::path::Stroke { ) -> SourceResult<krilla::path::Stroke> {
let (paint, opacity) = paint(fc, &stroke.paint, on_text, surface, transforms); let (paint, opacity) = paint(fc, &stroke.paint, on_text, surface, transforms)?;
krilla::path::Stroke {
Ok(krilla::path::Stroke {
paint, paint,
width: stroke.thickness.to_f32(), width: stroke.thickness.to_f32(),
miter_limit: stroke.miter_limit.get() as f32, miter_limit: stroke.miter_limit.get() as f32,
@ -49,7 +51,7 @@ pub(crate) fn stroke(
line_cap: stroke.cap.as_krilla(), line_cap: stroke.cap.as_krilla(),
opacity: NormalizedF32::new(opacity as f32 / 255.0).unwrap(), opacity: NormalizedF32::new(opacity as f32 / 255.0).unwrap(),
dash: stroke.dash.as_ref().map(|d| dash(d)), dash: stroke.dash.as_ref().map(|d| dash(d)),
} })
} }
fn dash(dash: &DashPattern<Abs, Abs>) -> krilla::path::StrokeDash { fn dash(dash: &DashPattern<Abs, Abs>) -> krilla::path::StrokeDash {
@ -74,13 +76,13 @@ fn paint(
on_text: bool, on_text: bool,
surface: &mut Surface, surface: &mut Surface,
transforms: Transforms, transforms: Transforms,
) -> (krilla::paint::Paint, u8) { ) -> SourceResult<(krilla::paint::Paint, u8)> {
match paint { match paint {
Paint::Solid(c) => { Paint::Solid(c) => {
let (c, alpha) = convert_color(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), Paint::Pattern(p) => convert_pattern(gc, p, on_text, surface, transforms),
} }
} }
@ -146,7 +148,7 @@ pub(crate) fn convert_pattern(
on_text: bool, on_text: bool,
surface: &mut Surface, surface: &mut Surface,
mut transforms: Transforms, mut transforms: Transforms,
) -> (krilla::paint::Paint, u8) { ) -> SourceResult<(krilla::paint::Paint, u8)> {
// Edge cases for strokes. // Edge cases for strokes.
if transforms.size.x.is_zero() { if transforms.size.x.is_zero() {
transforms.size.x = Abs::pt(1.0); 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 stream_builder = surface.stream_builder();
let mut surface = stream_builder.surface(); let mut surface = stream_builder.surface();
let mut fc = FrameContext::new(pattern.frame().size()); 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(); surface.finish();
let stream = stream_builder.finish(); let stream = stream_builder.finish();
let pattern = krilla::paint::Pattern { let pattern = krilla::paint::Pattern {
@ -179,7 +181,7 @@ pub(crate) fn convert_pattern(
height: (pattern.size().y + pattern.spacing().y).to_pt() as _, height: (pattern.size().y + pattern.spacing().y).to_pt() as _,
}; };
(pattern.into(), 255) Ok((pattern.into(), 255))
} }
fn convert_gradient( fn convert_gradient(
@ -234,7 +236,6 @@ fn convert_gradient(
// If we have a hue index or are using Oklab, we will create several // If we have a hue index or are using Oklab, we will create several
// stops in-between to make the gradient smoother without interpolation // stops in-between to make the gradient smoother without interpolation
// issues with native color spaces. // issues with native color spaces.
let mut last_c = first.0;
if gradient.space().hue_index().is_some() { if gradient.space().hue_index().is_some() {
for i in 0..=32 { for i in 0..=32 {
let t = i as f64 / 32.0; let t = i as f64 / 32.0;
@ -243,7 +244,6 @@ fn convert_gradient(
let c = gradient.sample(RatioOrAngle::Ratio(real_t)); let c = gradient.sample(RatioOrAngle::Ratio(real_t));
add_single(&c, real_t); add_single(&c, real_t);
last_c = c;
} }
} }