mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +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.
|
//! 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 |
@ -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]
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user