diff options
Diffstat (limited to 'src/export/pdf.rs')
| -rw-r--r-- | src/export/pdf.rs | 463 |
1 files changed, 267 insertions, 196 deletions
diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 24944246..05f73e52 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -14,7 +14,7 @@ use ttf_parser::{name_id, GlyphId, Tag}; use super::subset; use crate::font::{find_name, FaceId, FontStore}; -use crate::frame::{Element, Frame, Geometry}; +use crate::frame::{Element, Frame, Geometry, Text}; use crate::geom::{self, Color, Em, Length, Paint, Size}; use crate::image::{Image, ImageId, ImageStore}; use crate::Context; @@ -36,16 +36,16 @@ struct PdfExporter<'a> { frames: &'a [Rc<Frame>], fonts: &'a FontStore, images: &'a ImageStore, - glyphs: HashMap<FaceId, HashSet<u16>>, font_map: Remapper<FaceId>, image_map: Remapper<ImageId>, + glyphs: HashMap<FaceId, HashSet<u16>>, } impl<'a> PdfExporter<'a> { fn new(ctx: &'a Context, frames: &'a [Rc<Frame>]) -> Self { - let mut glyphs = HashMap::<FaceId, HashSet<u16>>::new(); let mut font_map = Remapper::new(); let mut image_map = Remapper::new(); + let mut glyphs = HashMap::<FaceId, HashSet<u16>>::new(); let mut alpha_masks = 0; for frame in frames { @@ -56,7 +56,6 @@ impl<'a> PdfExporter<'a> { let set = glyphs.entry(text.face_id).or_default(); set.extend(text.glyphs.iter().map(|g| g.id)); } - Element::Geometry(_, _) => {} Element::Image(id, _) => { let img = ctx.images.get(id); if img.buf.color().has_alpha() { @@ -64,7 +63,7 @@ impl<'a> PdfExporter<'a> { } image_map.insert(id); } - Element::Link(_, _) => {} + _ => {} } } } @@ -120,8 +119,8 @@ impl<'a> PdfExporter<'a> { for ((page_id, content_id), page) in self.refs.pages().zip(self.refs.contents()).zip(self.frames) { - let w = page.size.w.to_pt() as f32; - let h = page.size.h.to_pt() as f32; + let w = page.size.w.to_f32(); + let h = page.size.h.to_f32(); let mut page_writer = self.writer.page(page_id); page_writer @@ -131,10 +130,10 @@ impl<'a> PdfExporter<'a> { let mut annotations = page_writer.annotations(); for (pos, element) in page.elements() { if let Element::Link(href, size) = element { - let x = pos.x.to_pt() as f32; - let y = (page.size.h - pos.y).to_pt() as f32; - let w = size.w.to_pt() as f32; - let h = size.h.to_pt() as f32; + let x = pos.x.to_f32(); + let y = (page.size.h - pos.y).to_f32(); + let w = size.w.to_f32(); + let h = size.h.to_f32(); annotations .push() @@ -158,148 +157,9 @@ impl<'a> PdfExporter<'a> { } fn write_page(&mut self, id: Ref, page: &'a Frame) { - let mut content = Content::new(); - - // We only write font switching actions when the used face changes. To - // do that, we need to remember the active face. - let mut face_id = None; - let mut size = Length::zero(); - let mut fill: Option<Paint> = 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; - - match *element { - Element::Text(ref text) => { - if fill != Some(text.fill) { - write_fill(&mut content, text.fill); - fill = Some(text.fill); - } - - // Then, also check if we need to issue a font switching - // action. - if face_id != Some(text.face_id) || text.size != size { - face_id = Some(text.face_id); - size = text.size; - - let name = format_eco!("F{}", self.font_map.map(text.face_id)); - content.set_font(Name(name.as_bytes()), size.to_pt() as f32); - } - - let face = self.fonts.get(text.face_id); - - // Position the text. - content.set_text_matrix([1.0, 0.0, 0.0, 1.0, x, y]); - - let mut positioned = content.show_positioned(); - let mut items = positioned.items(); - let mut adjustment = Em::zero(); - let mut encoded = vec![]; - - // Write the glyphs with kerning adjustments. - for glyph in &text.glyphs { - adjustment += glyph.x_offset; - - if !adjustment.is_zero() { - if !encoded.is_empty() { - items.show(Str(&encoded)); - encoded.clear(); - } - - items.adjust(-adjustment.to_pdf()); - adjustment = Em::zero(); - } - - encoded.push((glyph.id >> 8) as u8); - encoded.push((glyph.id & 0xff) as u8); - - if let Some(advance) = face.advance(glyph.id) { - adjustment += glyph.x_advance - advance; - } - - adjustment -= glyph.x_offset; - } - - if !encoded.is_empty() { - items.show(Str(&encoded)); - } - } - - Element::Geometry(ref geometry, paint) => { - content.save_state(); - - match *geometry { - Geometry::Rect(Size { w, h }) => { - let w = w.to_pt() as f32; - 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); - content.fill_nonzero(); - } - } - Geometry::Ellipse(size) => { - let path = geom::Path::ellipse(size); - write_fill(&mut content, paint); - write_path(&mut content, x, y, &path); - } - Geometry::Line(target, thickness) => { - write_stroke(&mut content, paint, thickness.to_pt() as f32); - 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) - } - } - - content.restore_state(); - } - - Element::Image(id, Size { w, h }) => { - let name = format!("Im{}", self.image_map.map(id)); - let w = w.to_pt() as f32; - let h = h.to_pt() as f32; - - content.save_state(); - content.concat_matrix([w, 0.0, 0.0, h, x, y - h]); - content.x_object(Name(name.as_bytes())); - content.restore_state(); - } - - Element::Link(_, _) => {} - } - } - - if in_text_state { - content.end_text(); - } - - self.writer - .stream(id, &deflate(&content.finish())) - .filter(Filter::FlateDecode); + let writer = PageExporter::new(self); + let content = writer.write(page); + self.writer.stream(id, &deflate(&content)).filter(Filter::FlateDecode); } fn write_fonts(&mut self) { @@ -350,7 +210,7 @@ impl<'a> PdfExporter<'a> { let num_glyphs = ttf.number_of_glyphs(); (0 .. num_glyphs).map(|g| { let x = ttf.glyph_hor_advance(GlyphId(g)).unwrap_or(0); - face.to_em(x).to_pdf() + face.to_em(x).to_font_units() }) }); @@ -363,16 +223,16 @@ impl<'a> PdfExporter<'a> { let global_bbox = ttf.global_bounding_box(); let bbox = Rect::new( - face.to_em(global_bbox.x_min).to_pdf(), - face.to_em(global_bbox.y_min).to_pdf(), - face.to_em(global_bbox.x_max).to_pdf(), - face.to_em(global_bbox.y_max).to_pdf(), + face.to_em(global_bbox.x_min).to_font_units(), + face.to_em(global_bbox.y_min).to_font_units(), + face.to_em(global_bbox.x_max).to_font_units(), + face.to_em(global_bbox.y_max).to_font_units(), ); let italic_angle = ttf.italic_angle().unwrap_or(0.0); - let ascender = face.ascender.to_pdf(); - let descender = face.descender.to_pdf(); - let cap_height = face.cap_height.to_pdf(); + let ascender = face.ascender.to_font_units(); + let descender = face.descender.to_font_units(); + let cap_height = face.cap_height.to_font_units(); let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0); // Write the font descriptor (contains metrics about the font). @@ -474,45 +334,244 @@ 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.set_fill_rgb(c.r as f32 / 255.0, c.g as f32 / 255.0, c.b as f32 / 255.0); +/// A writer for the contents of a single page. +struct PageExporter<'a> { + fonts: &'a FontStore, + font_map: &'a Remapper<FaceId>, + image_map: &'a Remapper<ImageId>, + content: Content, + in_text_state: bool, + face_id: Option<FaceId>, + font_size: Length, + font_fill: Option<Paint>, } -/// 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.set_stroke_rgb( - c.r as f32 / 255.0, - c.g as f32 / 255.0, - c.b as f32 / 255.0, - ); +impl<'a> PageExporter<'a> { + /// Create a new page exporter. + fn new(exporter: &'a PdfExporter) -> Self { + Self { + fonts: exporter.fonts, + font_map: &exporter.font_map, + image_map: &exporter.image_map, + content: Content::new(), + in_text_state: false, + face_id: None, + font_size: Length::zero(), + font_fill: None, } } - 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) { - let f = |length: Length| length.to_pt() as f32; - for elem in &path.0 { - match elem { - 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), - y + f(p2.y), - x + f(p3.x), - y + f(p3.y), - ), - geom::PathElement::ClosePath => content.close_path(), - }; + /// Write the page frame into the content stream. + fn write(mut self, frame: &Frame) -> Vec<u8> { + self.write_frame(0.0, frame.size.h.to_f32(), frame); + self.content.finish() + } + + /// Write a frame into the content stream. + fn write_frame(&mut self, x: f32, y: f32, frame: &Frame) { + if frame.clips { + let w = frame.size.w.to_f32(); + let h = frame.size.h.to_f32(); + self.content.save_state(); + self.content.move_to(x, y); + self.content.line_to(x + w, y); + self.content.line_to(x + w, y - h); + self.content.line_to(x, y - h); + self.content.clip_nonzero(); + self.content.end_path(); + } + + for (offset, element) in &frame.elements { + // Make sure the content stream is in the correct state. + match element { + Element::Text(_) if !self.in_text_state => { + self.content.begin_text(); + self.in_text_state = true; + } + + Element::Geometry(..) | Element::Image(..) if self.in_text_state => { + self.content.end_text(); + self.in_text_state = false; + } + + _ => {} + } + + let x = x + offset.x.to_f32(); + let y = y - offset.y.to_f32(); + + match *element { + Element::Text(ref text) => { + self.write_text(x, y, text); + } + Element::Geometry(ref geometry, paint) => { + self.write_geometry(x, y, geometry, paint); + } + Element::Image(id, size) => { + self.write_image(x, y, id, size); + } + Element::Link(_, _) => {} + Element::Frame(ref frame) => { + self.write_frame(x, y, frame); + } + } + } + + if self.in_text_state { + self.content.end_text(); + } + + if frame.clips { + self.content.restore_state(); + } + } + + /// Write a glyph run into the content stream. + fn write_text(&mut self, x: f32, y: f32, text: &Text) { + if self.font_fill != Some(text.fill) { + self.write_fill(text.fill); + self.font_fill = Some(text.fill); + } + + // Then, also check if we need to issue a font switching + // action. + if self.face_id != Some(text.face_id) || self.font_size != text.size { + self.face_id = Some(text.face_id); + self.font_size = text.size; + + let name = format_eco!("F{}", self.font_map.map(text.face_id)); + self.content.set_font(Name(name.as_bytes()), text.size.to_f32()); + } + + let face = self.fonts.get(text.face_id); + + // Position the text. + self.content.set_text_matrix([1.0, 0.0, 0.0, 1.0, x, y]); + + let mut positioned = self.content.show_positioned(); + let mut items = positioned.items(); + let mut adjustment = Em::zero(); + let mut encoded = vec![]; + + // Write the glyphs with kerning adjustments. + for glyph in &text.glyphs { + adjustment += glyph.x_offset; + + if !adjustment.is_zero() { + if !encoded.is_empty() { + items.show(Str(&encoded)); + encoded.clear(); + } + + items.adjust(-adjustment.to_font_units()); + adjustment = Em::zero(); + } + + encoded.push((glyph.id >> 8) as u8); + encoded.push((glyph.id & 0xff) as u8); + + if let Some(advance) = face.advance(glyph.id) { + adjustment += glyph.x_advance - advance; + } + + adjustment -= glyph.x_offset; + } + + if !encoded.is_empty() { + items.show(Str(&encoded)); + } + } + + /// Write an image into the content stream. + fn write_image(&mut self, x: f32, y: f32, id: ImageId, size: Size) { + let name = format!("Im{}", self.image_map.map(id)); + let w = size.w.to_f32(); + let h = size.h.to_f32(); + + self.content.save_state(); + self.content.concat_matrix([w, 0.0, 0.0, h, x, y - h]); + self.content.x_object(Name(name.as_bytes())); + self.content.restore_state(); + } + + /// Write a geometrical shape into the content stream. + fn write_geometry(&mut self, x: f32, y: f32, geometry: &Geometry, paint: Paint) { + self.content.save_state(); + + match *geometry { + Geometry::Rect(Size { w, h }) => { + let w = w.to_f32(); + let h = h.to_f32(); + if w > 0.0 && h > 0.0 { + self.write_fill(paint); + self.content.rect(x, y - h, w, h); + self.content.fill_nonzero(); + } + } + Geometry::Ellipse(size) => { + let path = geom::Path::ellipse(size); + self.write_fill(paint); + self.write_filled_path(x, y, &path); + } + Geometry::Line(target, thickness) => { + self.write_stroke(paint, thickness.to_f32()); + self.content.move_to(x, y); + self.content.line_to(x + target.x.to_f32(), y - target.y.to_f32()); + self.content.stroke(); + } + Geometry::Path(ref path) => { + self.write_fill(paint); + self.write_filled_path(x, y, path) + } + } + + self.content.restore_state(); + } + + /// Write and fill path into a content stream. + fn write_filled_path(&mut self, x: f32, y: f32, path: &geom::Path) { + for elem in &path.0 { + match elem { + geom::PathElement::MoveTo(p) => { + self.content.move_to(x + p.x.to_f32(), y + p.y.to_f32()) + } + geom::PathElement::LineTo(p) => { + self.content.line_to(x + p.x.to_f32(), y + p.y.to_f32()) + } + geom::PathElement::CubicTo(p1, p2, p3) => self.content.cubic_to( + x + p1.x.to_f32(), + y + p1.y.to_f32(), + x + p2.x.to_f32(), + y + p2.y.to_f32(), + x + p3.x.to_f32(), + y + p3.y.to_f32(), + ), + geom::PathElement::ClosePath => self.content.close_path(), + }; + } + self.content.fill_nonzero(); + } + + /// Write a fill change into a content stream. + fn write_fill(&mut self, fill: Paint) { + let Paint::Color(Color::Rgba(c)) = fill; + self.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(&mut self, stroke: Paint, thickness: f32) { + let Paint::Color(Color::Rgba(c)) = stroke; + self.content.set_stroke_rgb( + c.r as f32 / 255.0, + c.g as f32 / 255.0, + c.b as f32 / 255.0, + ); + self.content.set_line_width(thickness); } - content.fill_nonzero(); } /// The compression level for the deflating. @@ -693,14 +752,26 @@ where } } +/// Additional methods for [`Length`]. +trait LengthExt { + /// Convert an em length to a number of points. + fn to_f32(self) -> f32; +} + +impl LengthExt for Length { + fn to_f32(self) -> f32 { + self.to_pt() as f32 + } +} + /// Additional methods for [`Em`]. trait EmExt { /// Convert an em length to a number of PDF font units. - fn to_pdf(self) -> f32; + fn to_font_units(self) -> f32; } impl EmExt for Em { - fn to_pdf(self) -> f32 { + fn to_font_units(self) -> f32 { 1000.0 * self.get() as f32 } } |
