summaryrefslogtreecommitdiff
path: root/src/export/pdf.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-11-16 21:32:29 +0100
committerLaurenz <laurmaedje@gmail.com>2021-11-16 21:32:29 +0100
commit9a800daa82833c57eee04e92c701ca9a05a67d3b (patch)
treea2c790f606037319393e9da3150bf58b44d4171d /src/export/pdf.rs
parent0cdf17216f47312f634d2dea8db237118ede72ce (diff)
Image fit modes
Diffstat (limited to 'src/export/pdf.rs')
-rw-r--r--src/export/pdf.rs463
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
}
}