diff --git a/Cargo.toml b/Cargo.toml index e960226fe..ee94e2a30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ fxhash = "0.2.1" image = { version = "0.23", default-features = false, features = ["png", "jpeg"] } itertools = "0.10" miniz_oxide = "0.4" -pdf-writer = "0.3.2" +pdf-writer = { git = "https://github.com/typst/pdf-writer", rev = "a750b66" } rand = "0.8" rustybuzz = "0.4" serde = { version = "1", features = ["derive", "rc"] } diff --git a/src/export/pdf.rs b/src/export/pdf.rs index f1c6135af..b762060e6 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -6,10 +6,10 @@ use std::hash::Hash; use std::rc::Rc; use image::{DynamicImage, GenericImageView, ImageFormat, ImageResult, Rgba}; -use pdf_writer::{ - ActionType, AnnotationType, CidFontType, ColorSpace, Content, Filter, FontFlags, - Name, PdfWriter, Rect, Ref, Str, SystemInfo, UnicodeCmap, +use pdf_writer::types::{ + ActionType, AnnotationType, CidFontType, ColorSpace, FontFlags, SystemInfo, }; +use pdf_writer::{Content, Filter, Finish, Name, PdfWriter, Rect, Ref, Str, UnicodeCmap}; use ttf_parser::{name_id, GlyphId, Tag}; use super::subset; @@ -70,11 +70,8 @@ impl<'a> PdfExporter<'a> { } } - let mut writer = PdfWriter::new(1, 7); - writer.set_indent(2); - Self { - writer, + writer: PdfWriter::new(), refs: Refs::new(frames.len(), font_map.len(), image_map.len(), alpha_masks), frames, fonts: &ctx.fonts, @@ -108,7 +105,7 @@ impl<'a> PdfExporter<'a> { fonts.pair(Name(name.as_bytes()), refs.type0_font); } - drop(fonts); + fonts.finish(); let mut images = resources.x_objects(); for (id, im) in self.refs.images().zip(self.image_map.pdf_indices()) { @@ -116,9 +113,9 @@ impl<'a> PdfExporter<'a> { images.pair(Name(name.as_bytes()), id); } - drop(images); - drop(resources); - drop(pages); + images.finish(); + resources.finish(); + pages.finish(); // The page objects (non-root nodes in the page tree). for ((page_id, content_id), page) in @@ -150,7 +147,7 @@ impl<'a> PdfExporter<'a> { } } - drop(annotations); + annotations.finish(); page_writer.contents(content_id); } } @@ -169,8 +166,24 @@ impl<'a> PdfExporter<'a> { let mut face_id = None; let mut size = Length::zero(); let mut fill: Option = None; + let mut in_text_state = false; for (pos, element) in page.elements() { + // Make sure the content stream is in the correct state. + match element { + Element::Text(_) if !in_text_state => { + content.begin_text(); + in_text_state = true; + } + + Element::Geometry(..) | Element::Image(..) if in_text_state => { + content.end_text(); + in_text_state = false; + } + + _ => {} + } + let x = pos.x.to_pt() as f32; let y = (page.size.h - pos.y).to_pt() as f32; @@ -181,8 +194,6 @@ impl<'a> PdfExporter<'a> { fill = Some(text.fill); } - let mut text_writer = content.text(); - // Then, also check if we need to issue a font switching // action. if face_id != Some(text.face_id) || text.size != size { @@ -190,15 +201,16 @@ impl<'a> PdfExporter<'a> { size = text.size; let name = format!("F{}", self.font_map.map(text.face_id)); - text_writer.font(Name(name.as_bytes()), size.to_pt() as f32); + content.set_font(Name(name.as_bytes()), size.to_pt() as f32); } let face = self.fonts.get(text.face_id); // Position the text. - text_writer.matrix(1.0, 0.0, 0.0, 1.0, x, y); + content.set_text_matrix([1.0, 0.0, 0.0, 1.0, x, y]); - let mut positioned = text_writer.show_positioned(); + let mut positioned = content.show_positioned(); + let mut items = positioned.items(); let mut adjustment = Em::zero(); let mut encoded = vec![]; @@ -208,11 +220,11 @@ impl<'a> PdfExporter<'a> { if !adjustment.is_zero() { if !encoded.is_empty() { - positioned.show(Str(&encoded)); + items.show(Str(&encoded)); encoded.clear(); } - positioned.adjust(-adjustment.to_pdf()); + items.adjust(-adjustment.to_pdf()); adjustment = Em::zero(); } @@ -227,7 +239,7 @@ impl<'a> PdfExporter<'a> { } if !encoded.is_empty() { - positioned.show(Str(&encoded)); + items.show(Str(&encoded)); } } @@ -240,24 +252,27 @@ impl<'a> PdfExporter<'a> { let h = h.to_pt() as f32; if w > 0.0 && h > 0.0 { write_fill(&mut content, paint); - content.rect(x, y - h, w, h, false, true); + content.rect(x, y - h, w, h); + content.fill_nonzero(); } } Geometry::Ellipse(size) => { let path = geom::Path::ellipse(size); write_fill(&mut content, paint); - write_path(&mut content, x, y, &path, false, true); + write_path(&mut content, x, y, &path); } Geometry::Line(target, thickness) => { write_stroke(&mut content, paint, thickness.to_pt() as f32); - content.path(true, false).move_to(x, y).line_to( + content.move_to(x, y); + content.line_to( x + target.x.to_pt() as f32, y - target.y.to_pt() as f32, ); + content.stroke(); } Geometry::Path(ref path) => { write_fill(&mut content, paint); - write_path(&mut content, x, y, path, false, true) + write_path(&mut content, x, y, path) } } @@ -270,7 +285,7 @@ impl<'a> PdfExporter<'a> { let h = h.to_pt() as f32; content.save_state(); - content.matrix(w, 0.0, 0.0, h, x, y - h); + content.concat_matrix([w, 0.0, 0.0, h, x, y - h]); content.x_object(Name(name.as_bytes())); content.restore_state(); } @@ -279,6 +294,10 @@ impl<'a> PdfExporter<'a> { } } + if in_text_state { + content.end_text(); + } + self.writer .stream(id, &deflate(&content.finish())) .filter(Filter::FlateDecode); @@ -432,7 +451,7 @@ impl<'a> PdfExporter<'a> { let (alpha_data, alpha_filter) = encode_alpha(img); let mask_id = self.refs.alpha_mask(masks_seen); image.s_mask(mask_id); - drop(image); + image.finish(); let mut mask = self.writer.image(mask_id, &alpha_data); mask.filter(alpha_filter); @@ -459,39 +478,31 @@ impl<'a> PdfExporter<'a> { /// Write a fill change into a content stream. fn write_fill(content: &mut Content, fill: Paint) { let Paint::Color(Color::Rgba(c)) = fill; - content.fill_rgb(c.r as f32 / 255.0, c.g as f32 / 255.0, c.b as f32 / 255.0); + content.set_fill_rgb(c.r as f32 / 255.0, c.g as f32 / 255.0, c.b as f32 / 255.0); } /// Write a stroke change into a content stream. fn write_stroke(content: &mut Content, stroke: Paint, thickness: f32) { match stroke { Paint::Color(Color::Rgba(c)) => { - content.stroke_rgb( + content.set_stroke_rgb( c.r as f32 / 255.0, c.g as f32 / 255.0, c.b as f32 / 255.0, ); } } - content.line_width(thickness); + content.set_line_width(thickness); } /// Write a path into a content stream. -fn write_path( - content: &mut Content, - x: f32, - y: f32, - path: &geom::Path, - stroke: bool, - fill: bool, -) { +fn write_path(content: &mut Content, x: f32, y: f32, path: &geom::Path) { let f = |length: Length| length.to_pt() as f32; - let mut builder = content.path(stroke, fill); for elem in &path.0 { match elem { - geom::PathElement::MoveTo(p) => builder.move_to(x + f(p.x), y + f(p.y)), - geom::PathElement::LineTo(p) => builder.line_to(x + f(p.x), y + f(p.y)), - geom::PathElement::CubicTo(p1, p2, p3) => builder.cubic_to( + geom::PathElement::MoveTo(p) => content.move_to(x + f(p.x), y + f(p.y)), + geom::PathElement::LineTo(p) => content.line_to(x + f(p.x), y + f(p.y)), + geom::PathElement::CubicTo(p1, p2, p3) => content.cubic_to( x + f(p1.x), y + f(p1.y), x + f(p2.x), @@ -499,9 +510,10 @@ fn write_path( x + f(p3.x), y + f(p3.y), ), - geom::PathElement::ClosePath => builder.close_path(), + geom::PathElement::ClosePath => content.close_path(), }; } + content.fill_nonzero(); } /// The compression level for the deflating.