Embed each image only once into the PDF 🥔

This commit is contained in:
Laurenz 2020-11-27 23:17:25 +01:00
parent 98f77e4d80
commit 982e7671a6
3 changed files with 66 additions and 38 deletions

View File

@ -1,6 +1,8 @@
//! Exporting into _PDF_ documents. //! Exporting into _PDF_ documents.
use std::cmp::Eq;
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::Hash;
use fontdock::FaceId; use fontdock::FaceId;
use image::{DynamicImage, GenericImageView}; use image::{DynamicImage, GenericImageView};
@ -29,21 +31,9 @@ struct PdfExporter<'a> {
writer: PdfWriter, writer: PdfWriter,
layouts: &'a [BoxLayout], layouts: &'a [BoxLayout],
env: &'a Env, env: &'a Env,
/// We need to know exactly which indirect reference id will be used for
/// which objects up-front to correctly declare the document catalogue, page
/// tree and so on. These offsets are computed in the beginning and stored
/// here.
refs: Refs, refs: Refs,
/// We assign a new PDF-internal index to each used face. fonts: Remapper<FaceId>,
/// There are two mappings: images: Remapper<ResourceId>,
/// Forwards from the old face ids to the new pdf indices.
fonts_to_pdf: HashMap<FaceId, usize>,
/// Backwards from the pdf indices to the old face ids.
fonts_to_layout: Vec<FaceId>,
/// The already visited images.
images: Vec<ResourceId>,
/// The total number of images.
image_count: usize,
} }
impl<'a> PdfExporter<'a> { impl<'a> PdfExporter<'a> {
@ -51,36 +41,27 @@ impl<'a> PdfExporter<'a> {
let mut writer = PdfWriter::new(1, 7); let mut writer = PdfWriter::new(1, 7);
writer.set_indent(2); writer.set_indent(2);
let mut fonts_to_pdf = HashMap::new(); let mut fonts = Remapper::new();
let mut fonts_to_layout = vec![]; let mut images = Remapper::new();
let mut image_count = 0;
for layout in layouts { for layout in layouts {
for (_, element) in &layout.elements { for (_, element) in &layout.elements {
match element { match element {
LayoutElement::Text(shaped) => { LayoutElement::Text(shaped) => fonts.insert(shaped.face),
fonts_to_pdf.entry(shaped.face).or_insert_with(|| { LayoutElement::Image(image) => images.insert(image.resource),
let next_id = fonts_to_layout.len();
fonts_to_layout.push(shaped.face);
next_id
});
}
LayoutElement::Image(_) => image_count += 1,
} }
} }
} }
let refs = Refs::new(layouts.len(), fonts_to_pdf.len(), image_count); let refs = Refs::new(layouts.len(), fonts.len(), images.len());
Self { Self {
writer, writer,
layouts, layouts,
env, env,
refs, refs,
fonts_to_pdf, fonts,
fonts_to_layout, images,
images: vec![],
image_count,
} }
} }
@ -102,7 +83,7 @@ impl<'a> PdfExporter<'a> {
let mut resources = pages.resources(); let mut resources = pages.resources();
let mut fonts = resources.fonts(); let mut fonts = resources.fonts();
for (refs, f) in self.refs.fonts().zip(0 .. self.fonts_to_pdf.len()) { for (refs, f) in self.refs.fonts().zip(self.fonts.pdf_indices()) {
let name = format!("F{}", f); let name = format!("F{}", f);
fonts.pair(Name(name.as_bytes()), refs.type0_font); fonts.pair(Name(name.as_bytes()), refs.type0_font);
} }
@ -110,7 +91,7 @@ impl<'a> PdfExporter<'a> {
drop(fonts); drop(fonts);
let mut images = resources.x_objects(); let mut images = resources.x_objects();
for (id, im) in self.refs.images().zip(0 .. self.image_count) { for (id, im) in self.refs.images().zip(self.images.pdf_indices()) {
let name = format!("Im{}", im); let name = format!("Im{}", im);
images.pair(Name(name.as_bytes()), id); images.pair(Name(name.as_bytes()), id);
} }
@ -158,7 +139,7 @@ impl<'a> PdfExporter<'a> {
face = shaped.face; face = shaped.face;
size = shaped.font_size; size = shaped.font_size;
let name = format!("F{}", self.fonts_to_pdf[&shaped.face]); let name = format!("F{}", self.fonts.map(shaped.face));
text.font(Name(name.as_bytes()), size.to_pt() as f32); text.font(Name(name.as_bytes()), size.to_pt() as f32);
} }
@ -173,7 +154,7 @@ impl<'a> PdfExporter<'a> {
for (pos, element) in &page.elements { for (pos, element) in &page.elements {
if let LayoutElement::Image(image) = element { if let LayoutElement::Image(image) = element {
let name = format!("Im{}", self.images.len()); let name = format!("Im{}", self.images.map(image.resource));
let size = image.size; let size = image.size;
let x = pos.x.to_pt() as f32; let x = pos.x.to_pt() as f32;
let y = (page.size.height - pos.y - size.height).to_pt() as f32; let y = (page.size.height - pos.y - size.height).to_pt() as f32;
@ -184,8 +165,6 @@ impl<'a> PdfExporter<'a> {
content.matrix(w, 0.0, 0.0, h, x, y); content.matrix(w, 0.0, 0.0, h, x, y);
content.x_object(Name(name.as_bytes())); content.x_object(Name(name.as_bytes()));
content.restore_state(); content.restore_state();
self.images.push(image.resource);
} }
} }
@ -193,7 +172,7 @@ impl<'a> PdfExporter<'a> {
} }
fn write_fonts(&mut self) { fn write_fonts(&mut self) {
for (refs, &face_id) in self.refs.fonts().zip(&self.fonts_to_layout) { for (refs, face_id) in self.refs.fonts().zip(self.fonts.layout_indices()) {
let owned_face = self.env.fonts.get_loaded(face_id); let owned_face = self.env.fonts.get_loaded(face_id);
let face = owned_face.get(); let face = owned_face.get();
@ -302,7 +281,7 @@ impl<'a> PdfExporter<'a> {
} }
fn write_images(&mut self) { fn write_images(&mut self) {
for (id, &resource) in self.refs.images().zip(&self.images) { for (id, resource) in self.refs.images().zip(self.images.layout_indices()) {
let image = self.env.resources.get_loaded::<DynamicImage>(resource); let image = self.env.resources.get_loaded::<DynamicImage>(resource);
let data = image.to_rgb8().into_raw(); let data = image.to_rgb8().into_raw();
self.writer self.writer
@ -315,6 +294,9 @@ impl<'a> PdfExporter<'a> {
} }
} }
/// We need to know exactly which indirect reference id will be used for which
/// objects up-front to correctly declare the document catalogue, page tree and
/// so on. These offsets are computed in the beginning and stored here.
struct Refs { struct Refs {
catalog: Ref, catalog: Ref,
page_tree: Ref, page_tree: Ref,
@ -380,3 +362,48 @@ impl Refs {
(self.images_start .. self.end).map(Ref::new) (self.images_start .. self.end).map(Ref::new)
} }
} }
/// Used to assign new, consecutive PDF-internal indices to things.
struct Remapper<Index> {
/// Forwards from the old indices to the new pdf indices.
to_pdf: HashMap<Index, usize>,
/// Backwards from the pdf indices to the old indices.
to_layout: Vec<Index>,
}
impl<Index> Remapper<Index>
where
Index: Copy + Eq + Hash,
{
fn new() -> Self {
Self {
to_pdf: HashMap::new(),
to_layout: vec![],
}
}
fn len(&self) -> usize {
self.to_layout.len()
}
fn insert(&mut self, index: Index) {
let to_layout = &mut self.to_layout;
self.to_pdf.entry(index).or_insert_with(|| {
let pdf_index = to_layout.len();
to_layout.push(index);
pdf_index
});
}
fn map(&self, index: Index) -> usize {
self.to_pdf[&index]
}
fn pdf_indices(&self) -> impl Iterator<Item = usize> {
0 .. self.to_pdf.len()
}
fn layout_indices(&self) -> impl Iterator<Item = Index> + '_ {
self.to_layout.iter().copied()
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 930 KiB

After

Width:  |  Height:  |  Size: 930 KiB

View File

@ -4,6 +4,7 @@
[pagebreak] [pagebreak]
# Tiger
[image: "res/tiger.jpg", width=3cm] [image: "res/tiger.jpg", width=3cm]
[image: "res/tiger.jpg", height=3cm] [image: "res/tiger.jpg", height=3cm]