mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Embed each image only once into the PDF 🥔
This commit is contained in:
parent
98f77e4d80
commit
982e7671a6
@ -1,6 +1,8 @@
|
||||
//! Exporting into _PDF_ documents.
|
||||
|
||||
use std::cmp::Eq;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
|
||||
use fontdock::FaceId;
|
||||
use image::{DynamicImage, GenericImageView};
|
||||
@ -29,21 +31,9 @@ struct PdfExporter<'a> {
|
||||
writer: PdfWriter,
|
||||
layouts: &'a [BoxLayout],
|
||||
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,
|
||||
/// We assign a new PDF-internal index to each used face.
|
||||
/// There are two mappings:
|
||||
/// 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,
|
||||
fonts: Remapper<FaceId>,
|
||||
images: Remapper<ResourceId>,
|
||||
}
|
||||
|
||||
impl<'a> PdfExporter<'a> {
|
||||
@ -51,36 +41,27 @@ impl<'a> PdfExporter<'a> {
|
||||
let mut writer = PdfWriter::new(1, 7);
|
||||
writer.set_indent(2);
|
||||
|
||||
let mut fonts_to_pdf = HashMap::new();
|
||||
let mut fonts_to_layout = vec![];
|
||||
let mut image_count = 0;
|
||||
let mut fonts = Remapper::new();
|
||||
let mut images = Remapper::new();
|
||||
|
||||
for layout in layouts {
|
||||
for (_, element) in &layout.elements {
|
||||
match element {
|
||||
LayoutElement::Text(shaped) => {
|
||||
fonts_to_pdf.entry(shaped.face).or_insert_with(|| {
|
||||
let next_id = fonts_to_layout.len();
|
||||
fonts_to_layout.push(shaped.face);
|
||||
next_id
|
||||
});
|
||||
}
|
||||
LayoutElement::Image(_) => image_count += 1,
|
||||
LayoutElement::Text(shaped) => fonts.insert(shaped.face),
|
||||
LayoutElement::Image(image) => images.insert(image.resource),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let refs = Refs::new(layouts.len(), fonts_to_pdf.len(), image_count);
|
||||
let refs = Refs::new(layouts.len(), fonts.len(), images.len());
|
||||
|
||||
Self {
|
||||
writer,
|
||||
layouts,
|
||||
env,
|
||||
refs,
|
||||
fonts_to_pdf,
|
||||
fonts_to_layout,
|
||||
images: vec![],
|
||||
image_count,
|
||||
fonts,
|
||||
images,
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,7 +83,7 @@ impl<'a> PdfExporter<'a> {
|
||||
|
||||
let mut resources = pages.resources();
|
||||
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);
|
||||
fonts.pair(Name(name.as_bytes()), refs.type0_font);
|
||||
}
|
||||
@ -110,7 +91,7 @@ impl<'a> PdfExporter<'a> {
|
||||
drop(fonts);
|
||||
|
||||
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);
|
||||
images.pair(Name(name.as_bytes()), id);
|
||||
}
|
||||
@ -158,7 +139,7 @@ impl<'a> PdfExporter<'a> {
|
||||
face = shaped.face;
|
||||
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);
|
||||
}
|
||||
|
||||
@ -173,7 +154,7 @@ impl<'a> PdfExporter<'a> {
|
||||
|
||||
for (pos, element) in &page.elements {
|
||||
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 x = pos.x.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.x_object(Name(name.as_bytes()));
|
||||
content.restore_state();
|
||||
|
||||
self.images.push(image.resource);
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +172,7 @@ impl<'a> PdfExporter<'a> {
|
||||
}
|
||||
|
||||
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 face = owned_face.get();
|
||||
|
||||
@ -302,7 +281,7 @@ impl<'a> PdfExporter<'a> {
|
||||
}
|
||||
|
||||
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 data = image.to_rgb8().into_raw();
|
||||
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 {
|
||||
catalog: Ref,
|
||||
page_tree: Ref,
|
||||
@ -380,3 +362,48 @@ impl Refs {
|
||||
(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 |
@ -4,6 +4,7 @@
|
||||
|
||||
[pagebreak]
|
||||
|
||||
# Tiger
|
||||
[image: "res/tiger.jpg", width=3cm]
|
||||
[image: "res/tiger.jpg", height=3cm]
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user