use lopdf::{Object, StringFormat};
use owned_ttf_parser::{AsFaceRef as _, Face, OwnedFace};
use std::{
collections::{BTreeMap, HashMap},
io::BufWriter,
mem,
path::Path,
};
use time::OffsetDateTime;
use unicode_normalization::UnicodeNormalization as _;
use crate::error::ContextError;
#[derive(Clone, Copy, Debug, Default)]
struct FontMetrics {
ascent: i16,
descent: i16,
units_per_em: u16,
}
#[derive(Clone, Copy, Debug, Default)]
struct GlyphMetrics {
width: u32,
height: u32,
}
#[derive(Clone, Debug)]
struct TtfFontFace {
inner: std::sync::Arc<owned_ttf_parser::OwnedFace>,
units_per_em: u16,
}
impl TtfFontFace {
fn font_metrics(&self) -> FontMetrics {
FontMetrics {
ascent: self.face().ascender(),
descent: self.face().descender(),
units_per_em: self.units_per_em,
}
}
fn glyph_id(&self, codepoint: char) -> Option<u16> {
self.face()
.glyph_index(codepoint)
.map(|glyph_id| glyph_id.0)
}
fn glyph_ids(&self) -> HashMap<u16, char> {
let font_subtables = self.face().tables().cmap.map(|cmap| {
cmap.subtables
.into_iter()
.filter(|font_subtable| font_subtable.is_unicode())
});
let Some(font_subtables) = font_subtables else {
return HashMap::new();
};
let mut gid_to_codepoint_map =
HashMap::with_capacity(self.face().number_of_glyphs().into());
for font_subtable in font_subtables {
font_subtable.codepoints(|codepoint| {
use std::convert::TryFrom as _;
if let Ok(character) = char::try_from(codepoint) {
if let Some(glyph_index) = font_subtable
.glyph_index(codepoint)
.filter(|index| index.0 > 0)
{
gid_to_codepoint_map
.entry(glyph_index.0)
.or_insert(character);
}
}
})
}
gid_to_codepoint_map
}
fn glyph_count(&self) -> u16 {
self.face().number_of_glyphs()
}
fn glyph_metrics(&self, glyph_id: u16) -> Option<GlyphMetrics> {
let glyph_id = owned_ttf_parser::GlyphId(glyph_id);
if let Some(width) = self.face().glyph_hor_advance(glyph_id) {
let width = width as u32;
let height = self
.face()
.glyph_bounding_box(glyph_id)
.map(|bounding_box| {
bounding_box.y_max - bounding_box.y_min - self.face().descender()
})
.unwrap_or(1000) as u32;
Some(GlyphMetrics { width, height })
} else {
None
}
}
pub fn from_bytes(data: &[u8]) -> Result<Self, ContextError> {
let face = OwnedFace::from_vec(data.to_vec(), 0)
.map_err(|error| ContextError::with_error("Failed to parse font", &error))?;
let units_per_em = face.as_face_ref().units_per_em();
Ok(Self {
inner: std::sync::Arc::new(face),
units_per_em,
})
}
fn face(&self) -> &Face<'_> {
self.inner.as_face_ref()
}
}
#[derive(Debug, Clone)]
struct Font {
bytes: Vec<u8>,
ttf_face: TtfFontFace,
face_identifier: String,
}
impl Font {
fn insert_into_document(&self, inner_document: &mut lopdf::Document) -> lopdf::Dictionary {
use lopdf::Object::*;
let face_metrics = self.ttf_face.font_metrics();
let font_stream = lopdf::Stream::new(
lopdf::Dictionary::from_iter(vec![("Length1", Integer(self.bytes.len() as i64))]),
self.bytes.clone(),
)
.with_compression(false);
let mut font_vector: Vec<(::std::string::String, lopdf::Object)> = vec![
("Type".into(), Name("Font".into())),
("Subtype".into(), Name("Type0".into())),
(
"BaseFont".into(),
Name(self.face_identifier.clone().into_bytes()),
),
("Encoding".into(), Name("Identity-H".into())),
];
let mut font_descriptor_vector: Vec<(::std::string::String, lopdf::Object)> = vec![
("Type".into(), Name("FontDescriptor".into())),
(
"FontName".into(),
Name(self.face_identifier.clone().into_bytes()),
),
("Ascent".into(), Integer(i64::from(face_metrics.ascent))),
("Descent".into(), Integer(i64::from(face_metrics.descent))),
("CapHeight".into(), Integer(i64::from(face_metrics.ascent))),
("ItalicAngle".into(), Integer(0)), ("Flags".into(), Integer(32)),
("StemV".into(), Integer(80)),
];
let mut maximum_character_height = 0;
let mut total_width = 0;
let mut gid_to_glyph_properties_map = BTreeMap::<u32, (u32, u32, u32)>::new();
gid_to_glyph_properties_map.insert(0, (0, 1000, 1000));
for (glyph_id, character) in self.ttf_face.glyph_ids() {
if let Some(glyph_metrics) = self.ttf_face.glyph_metrics(glyph_id) {
if glyph_metrics.height > maximum_character_height {
maximum_character_height = glyph_metrics.height;
}
total_width += glyph_metrics.width;
gid_to_glyph_properties_map.insert(
glyph_id as u32,
(character as u32, glyph_metrics.width, glyph_metrics.height),
);
}
}
let mut current_first_bit: u16 = 0; let mut all_gid_to_character_blocks = Vec::new();
let mut character_widths = Vec::<(u32, u32)>::new();
let mut current_gid_to_character_block = Vec::new();
for (glyph_id, (character, glyph_width, _glyph_height)) in
gid_to_glyph_properties_map.iter()
{
if (*glyph_id >> 8) as u16 != current_first_bit
|| current_gid_to_character_block.len() >= 100
{
all_gid_to_character_blocks.push(current_gid_to_character_block.clone());
current_gid_to_character_block = Vec::new();
current_first_bit = (*glyph_id >> 8) as u16;
}
current_gid_to_character_block.push((*glyph_id, *character));
character_widths.push((*glyph_id, *glyph_width));
}
all_gid_to_character_blocks.push(current_gid_to_character_block);
let cid_to_unicode_map =
generate_cid_to_unicode_map(self.face_identifier.clone(), all_gid_to_character_blocks);
let cid_to_unicode_map_stream = lopdf::Stream::new(
lopdf::Dictionary::new(),
cid_to_unicode_map.as_bytes().to_vec(),
);
let cid_to_unicode_map_stream_id = inner_document.add_object(cid_to_unicode_map_stream);
let mut width_objects = Vec::<Object>::new();
let mut current_lesser_glyph_id = 0;
let mut current_upper_gid = 0;
let mut current_widths_vector = Vec::<Object>::new();
let percentage_font_scaling = 1000.0 / (face_metrics.units_per_em as f32);
for glyph_id in 0..self.ttf_face.glyph_count() {
if let Some(GlyphMetrics { width, .. }) = self.ttf_face.glyph_metrics(glyph_id) {
if glyph_id == current_upper_gid {
current_widths_vector
.push(Integer((width as f32 * percentage_font_scaling) as i64));
current_upper_gid += 1;
} else {
width_objects.push(Integer(current_lesser_glyph_id as i64));
width_objects.push(Array(std::mem::take(&mut current_widths_vector)));
current_widths_vector
.push(Integer((width as f32 * percentage_font_scaling) as i64));
current_lesser_glyph_id = glyph_id;
current_upper_gid = glyph_id + 1;
}
} else {
log::warn!("Glyph ID {} for the font {:?} has no width, skipping it when adding it to the document from the font", glyph_id, self.face_identifier);
continue;
}
}
width_objects.push(Integer(current_lesser_glyph_id as i64));
width_objects.push(Array(std::mem::take(&mut current_widths_vector)));
let mut font_descriptors = lopdf::Dictionary::from_iter(vec![
("Type", Name("Font".into())),
("Subtype", Name("CIDFontType2".into())),
("BaseFont", Name(self.face_identifier.clone().into())),
(
"CIDSystemInfo",
Dictionary(lopdf::Dictionary::from_iter(vec![
("Registry", String("Adobe".into(), StringFormat::Literal)),
("Ordering", String("Identity".into(), StringFormat::Literal)),
("Supplement", Integer(0)),
])),
),
("W", Array(width_objects)), ("DW", Integer(1000)), ]);
let font_bounding_box = vec![
Integer(0),
Integer(maximum_character_height as i64),
Integer(total_width as i64),
Integer(maximum_character_height as i64),
];
font_descriptor_vector.push((
"FontFile2".into(),
Reference(inner_document.add_object(font_stream)),
));
font_descriptor_vector.push(("FontBBox".into(), Array(font_bounding_box)));
let font_descriptor_vector_id =
inner_document.add_object(lopdf::Dictionary::from_iter(font_descriptor_vector));
font_descriptors.set("FontDescriptor", Reference(font_descriptor_vector_id));
font_vector.push((
"DescendantFonts".into(),
Array(vec![Dictionary(font_descriptors)]),
));
font_vector.push(("ToUnicode".into(), Reference(cid_to_unicode_map_stream_id)));
lopdf::Dictionary::from_iter(font_vector)
}
}
#[derive(Debug, Clone)]
struct PdfLayer {
name: String,
operations: Vec<lopdf::content::Operation>,
}
impl From<PdfLayer> for lopdf::Stream {
fn from(value: PdfLayer) -> Self {
use lopdf::{Dictionary, Stream};
let stream_content = lopdf::content::Content {
operations: value.operations,
};
Stream::new(
Dictionary::new(),
stream_content
.encode()
.map_err(|error| {
ContextError::with_error("Failed to encode PDF layer content", &error)
})
.unwrap(),
)
.with_compression(false) }
}
use nalgebra_glm as glm;
#[allow(dead_code)]
#[derive(Debug, Clone)]
struct ImageXObject {
width: f32,
height: f32,
bits_per_component: u16,
interpolate: bool,
image_data: Vec<u8>,
soft_mask: Option<lopdf::ObjectId>,
clipping_bounding_box: Option<glm::Mat4>,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
enum XObject {
Image(ImageXObject),
}
impl From<XObject> for lopdf::Object {
fn from(value: XObject) -> Self {
match value {
XObject::Image(_) => {
unimplemented!()
}
}
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
struct XObjectReference(String);
impl XObjectReference {
#[allow(dead_code)]
fn new(index: usize) -> Self {
Self(format!("X{index}"))
}
}
#[derive(Default, Debug, Clone)]
struct XObjectMap(HashMap<String, XObject>);
impl XObjectMap {
fn insert_into_document(&self, document: &mut lopdf::Document) -> lopdf::Dictionary {
self.0
.iter()
.map(|(name, object)| {
let object: lopdf::Object = object.clone().into();
let object_reference = document.add_object(object);
(name.clone(), lopdf::Object::Reference(object_reference))
})
.collect()
}
}
#[derive(Debug, Clone)]
struct OcgReference(String);
impl OcgReference {
fn new(index: usize) -> Self {
Self(format!("MC{index}"))
}
}
#[derive(Default, Debug, Clone)]
struct OcgLayersMap(Vec<(OcgReference, lopdf::Object)>);
impl OcgLayersMap {
fn add_ocg(&mut self, object: lopdf::Object) -> OcgReference {
let length = self.0.len();
let ocg_reference = OcgReference::new(length);
self.0.push((ocg_reference.clone(), object));
ocg_reference
}
}
impl From<OcgLayersMap> for lopdf::Dictionary {
fn from(value: OcgLayersMap) -> Self {
let mut dictionary = lopdf::Dictionary::new();
for entry in value.0 {
dictionary.set((entry.0).0, entry.1);
}
dictionary
}
}
#[derive(Default, Debug, Clone)]
struct PdfResources {
xobjects: XObjectMap,
ocg_layers: OcgLayersMap,
}
impl PdfResources {
fn with_document_and_layers(
&self,
inner_document: &mut lopdf::Document,
layers: Vec<lopdf::Object>,
) -> (lopdf::Dictionary, Vec<OcgReference>) {
let mut dictionary = lopdf::Dictionary::new();
let mut ocg_layers_dictionary = self.ocg_layers.clone();
let mut ocg_references = Vec::<OcgReference>::new();
let xobjects_dictionary: lopdf::Dictionary =
self.xobjects.insert_into_document(inner_document);
if !layers.is_empty() {
for layer in layers {
ocg_references.push(ocg_layers_dictionary.add_ocg(layer));
}
let current_ocg_dictionary: lopdf::Dictionary = ocg_layers_dictionary.into();
if !current_ocg_dictionary.is_empty() {
dictionary.set(
"Properties",
lopdf::Object::Dictionary(current_ocg_dictionary),
);
}
}
if !xobjects_dictionary.is_empty() {
dictionary.set("XObject", lopdf::Object::Dictionary(xobjects_dictionary));
}
(dictionary, ocg_references)
}
}
#[derive(Debug, Clone)]
struct PdfPage {
number: usize,
width: f32,
height: f32,
layers: Vec<PdfLayer>,
resources: PdfResources,
extend_with: Option<lopdf::Dictionary>,
}
impl PdfPage {
fn collect_resources_and_streams(
&mut self,
inner_document: &mut lopdf::Document,
layers: &[(usize, lopdf::Object)],
) -> Result<(lopdf::Dictionary, Vec<lopdf::Stream>), ContextError> {
let current_layers = layers.iter().map(|layer| layer.1.clone()).collect();
let (resource_dictionary, ocg_references) = self
.resources
.with_document_and_layers(inner_document, current_layers);
let mut layer_streams = Vec::<lopdf::Stream>::new();
use lopdf::content::Operation;
use lopdf::Object::*;
for (index, layer) in self.layers.iter_mut().enumerate() {
layer.operations.insert(0, Operation::new("q", vec![]));
layer.operations.insert(
0,
Operation::new(
"BDC",
vec![
Name("OC".into()),
Name(
ocg_references
.get(index)
.ok_or(ContextError::with_context(
"Unable to find the index in the OCG references",
))?
.0
.clone()
.into(),
),
],
),
);
layer.operations.push(Operation::new("Q", vec![]));
layer.operations.push(Operation::new("EMC", vec![]));
let layer_stream = layer.clone().into();
layer_streams.push(layer_stream);
}
Ok((resource_dictionary, layer_streams))
}
}
fn millimeters_to_points(millimeters: f32) -> f32 {
millimeters * 2.834646
}
pub struct PdfDocument {
fonts: BTreeMap<String, (lopdf::ObjectId, Font)>,
pub inner_document: lopdf::Document,
pub identifier: String,
pages: Vec<PdfPage>,
}
impl PdfDocument {
pub fn new(pdf_document_identifier: String) -> Self {
PdfDocument {
fonts: BTreeMap::default(),
inner_document: lopdf::Document::with_version("1.5"),
identifier: pdf_document_identifier,
pages: Vec::new(),
}
}
pub fn add_page_with_layer(&mut self, page_width: f32, page_height: f32) -> (usize, usize) {
let mut pdf_page = PdfPage {
number: self.pages.len() + 1,
width: millimeters_to_points(page_width), height: millimeters_to_points(page_height),
layers: Vec::new(), resources: PdfResources::default(),
extend_with: None, };
let pdf_layer = PdfLayer {
name: "Layer0".into(),
operations: Vec::new(),
};
pdf_page.layers.push(pdf_layer);
self.pages.push(pdf_page);
let page_index = self.pages.len() - 1;
let layer_index_in_page = 0;
(page_index, layer_index_in_page)
}
pub fn add_font(&mut self, font_path: &Path) -> Result<usize, ContextError> {
let font_bytes = std::fs::read(font_path).map_err(|error| {
ContextError::with_error("Failed to read font, probably the path is wrong", &error)
})?;
let ttf_font_face = TtfFontFace::from_bytes(&font_bytes)
.map_err(|error| ContextError::with_error("Failed to parse font", &error))?;
let font = Font {
bytes: font_bytes,
ttf_face: ttf_font_face,
face_identifier: format!("F{}", self.fonts.len()),
};
let font_object_id = self.inner_document.new_object_id();
self.fonts
.insert(font.face_identifier.clone(), (font_object_id, font.clone()));
let font_index = self.fonts.len() - 1;
Ok(font_index)
}
#[allow(clippy::too_many_arguments)]
pub fn write_text_to_layer_in_page(
&mut self,
page_index: usize,
layer_index: usize,
color: [f32; 3],
text: String,
font_index: usize,
font_size: f32,
caret_position: [f32; 2],
) -> Result<(), ContextError> {
let font = self.get_font(font_index)?.1.clone();
self.add_operations_to_layer_in_page(
layer_index,
page_index,
vec![
lopdf::content::Operation::new("BT", vec![]), lopdf::content::Operation::new(
"Tf",
vec![font.face_identifier.clone().into(), (font_size).into()],
), lopdf::content::Operation::new("Td", {
let [x, y] = caret_position;
vec![
millimeters_to_points(x).into(),
millimeters_to_points(y).into(),
]
}), lopdf::content::Operation::new("rg", {
let [r, g, b] = color;
vec![r, g, b].into_iter().map(lopdf::Object::Real).collect()
}),
],
)?;
let mut glyph_id_list = Vec::<u16>::new();
for character in text.nfc() {
if let Some(glyph_id) = font.ttf_face.glyph_id(character) {
glyph_id_list.push(glyph_id);
} else {
log::warn!("Unable to find the character {:?} in the font", character)
}
}
let glyph_id_bytes = glyph_id_list
.iter()
.flat_map(|x| vec![(x >> 8) as u8, (x & 255) as u8])
.collect::<Vec<u8>>();
self.add_operations_to_layer_in_page(
layer_index,
page_index,
vec![lopdf::content::Operation::new(
"Tj",
vec![lopdf::Object::String(
glyph_id_bytes,
lopdf::StringFormat::Hexadecimal,
)],
)],
)?;
self.add_operations_to_layer_in_page(
layer_index,
page_index,
vec![lopdf::content::Operation::new("ET", vec![])],
)?;
Ok(())
}
pub fn write_all(&mut self, instance_id: String) -> Result<(), ContextError> {
use lopdf::Object::*;
use lopdf::StringFormat::*;
let document_info = lopdf::Dictionary::from_iter(vec![
("Trapped", "False".into()),
(
"CreationDate",
String(
to_pdf_timestamp_format(&OffsetDateTime::UNIX_EPOCH).into_bytes(),
Literal,
),
),
(
"ModDate",
String(
to_pdf_timestamp_format(&OffsetDateTime::UNIX_EPOCH).into_bytes(),
Literal,
),
),
(
"GTS_PDFX_Version",
String("PDF/A-3:2012".to_string().into_bytes(), Literal),
),
("Title", String("Unknown".to_string().into_bytes(), Literal)),
(
"Author",
String("Unknown".to_string().into_bytes(), Literal),
),
(
"Creator",
String("Unknown".to_string().into_bytes(), Literal),
),
(
"Producer",
String("Unknown".to_string().into_bytes(), Literal),
),
(
"Subject",
String("Unknown".to_string().into_bytes(), Literal),
),
(
"Identifier",
String(self.identifier.clone().into_bytes(), Literal),
),
("Keywords", String("".to_string().into_bytes(), Literal)),
]);
let document_info_id = self.inner_document.add_object(Dictionary(document_info));
let pages_id = self.inner_document.new_object_id();
let mut catalog = lopdf::Dictionary::from_iter(vec![
("Type", "Catalog".into()),
("PageLayout", "OneColumn".into()),
("PageMode", "UseNone".into()),
("Pages", Reference(pages_id)),
]);
let mut pages = lopdf::Dictionary::from_iter(vec![
("Type", "Pages".into()),
("Count", Integer(self.pages.len() as i64)),
]);
let ocg_usage_dictionary = lopdf::Dictionary::from_iter(vec![
("Type", Name("OCG".into())),
(
"CreatorInfo",
Dictionary(lopdf::Dictionary::from_iter(vec![
("Creator", String("Adobe Illustrator 14.0".into(), Literal)), ("Subtype", Name("Artwork".into())),
])),
),
]);
let usage_ocg_dictionary_id = self.inner_document.add_object(ocg_usage_dictionary);
let intent_array = Array(vec![Name("View".into()), Name("Design".into())]);
let intent_array_id = self.inner_document.add_object(intent_array);
let page_layer_numbers_and_names: Vec<(usize, Vec<::std::string::String>)> = self
.pages
.iter()
.map(|page| {
(
page.number,
page.layers.iter().map(|layer| layer.name.clone()).collect(),
)
})
.collect();
let ocg_association: Vec<(usize, Vec<(usize, lopdf::Object)>)> =
page_layer_numbers_and_names
.into_iter()
.map(|(page_index, layer_names)| {
let layer_indices_and_dictionary_references = layer_names
.into_iter()
.enumerate()
.map(|(layer_index, layer_name)| {
let ocg_dictionary = lopdf::Dictionary::from_iter(vec![
("Type", Name("OCG".into())),
("Name", String(layer_name.into(), Literal)),
("Intent", Reference(intent_array_id)),
("Usage", Reference(usage_ocg_dictionary_id)),
]);
let ocg_dictionary_id =
self.inner_document.add_object(Dictionary(ocg_dictionary));
(layer_index, Reference(ocg_dictionary_id))
})
.collect();
(page_index, layer_indices_and_dictionary_references)
})
.collect();
let ocg_dictionary_references: Vec<lopdf::Object> = ocg_association
.iter()
.flat_map(|(_, layers)| {
layers
.iter()
.map(|(_, dictionary_reference)| dictionary_reference.clone())
})
.collect();
catalog.set(
"OCProperties",
Dictionary(lopdf::Dictionary::from_iter(vec![
("OCGs", Array(ocg_dictionary_references.clone())),
(
"D",
Dictionary(lopdf::Dictionary::from_iter(vec![
("Order", Array(ocg_dictionary_references.clone())),
("RBGroups", Array(vec![])),
("ON", Array(ocg_dictionary_references)),
])),
),
])),
);
let catalog_id = self.inner_document.add_object(catalog);
self.inner_document
.trailer
.set("Root", Reference(catalog_id));
self.inner_document
.trailer
.set("Info", Reference(document_info_id));
self.inner_document.trailer.set(
"ID",
Array(vec![
String(self.identifier.clone().into_bytes(), Literal),
String(instance_id.as_bytes().to_vec(), Literal),
]),
);
let fonts_dictionary = self.insert_fonts_into_document();
let fonts_dictionary_id = self.inner_document.add_object(fonts_dictionary);
let mut page_ids = Vec::<lopdf::Object>::new();
for (index, page) in self.pages.iter_mut().enumerate() {
let mut page_dictionary = lopdf::Dictionary::from_iter(vec![
("Type", "Page".into()),
("Rotate", Integer(0)),
(
"MediaBox",
vec![0.into(), 0.into(), page.width.into(), page.height.into()].into(),
),
(
"TrimBox",
vec![0.into(), 0.into(), page.width.into(), page.height.into()].into(),
),
(
"CropBox",
vec![0.into(), 0.into(), page.width.into(), page.height.into()].into(),
),
("Annots", vec![].into()),
("Parent", Reference(pages_id)),
]);
if let Some(extension) = &page.extend_with {
for (key, value) in extension.iter() {
page_dictionary.set(key.to_vec(), value.clone())
}
}
let unmerged_layer = ocg_association.iter().find(|ocg| ocg.0 - 1 == index).ok_or({
let comparisons = ocg_association.iter().map(|ocg| ocg.0 - 1).collect::<Vec<_>>();
ContextError::with_context(
format!("Unable to collect the resources needed for rendering the page: can't find {:?} in {:?}", index, comparisons),
)
})?;
let (mut resource_dictionary, layer_streams) =
page.collect_resources_and_streams(&mut self.inner_document, &unmerged_layer.1)?;
resource_dictionary.set("Font", Reference(fonts_dictionary_id));
let resources_page_id = self
.inner_document
.add_object(Dictionary(resource_dictionary));
page_dictionary.set("Resources", Reference(resources_page_id));
let mut merged_layer_streams = Vec::<u8>::new();
for mut stream in layer_streams {
merged_layer_streams.append(&mut stream.content);
}
let merged_layer_stream =
lopdf::Stream::new(lopdf::Dictionary::new(), merged_layer_streams);
let page_content_id = self.inner_document.add_object(merged_layer_stream);
page_dictionary.set("Contents", Reference(page_content_id));
let page_id = self.inner_document.add_object(page_dictionary);
page_ids.push(Reference(page_id))
}
pages.set::<_, lopdf::Object>("Kids".to_string(), page_ids.into());
self.inner_document
.objects
.insert(pages_id, Dictionary(pages));
Ok(())
}
pub fn optimize(&mut self) {
self.inner_document.prune_objects();
self.inner_document.delete_zero_length_streams();
self.inner_document.renumber_objects();
self.inner_document.compress();
}
pub fn save_to_bytes(&mut self) -> Result<Vec<u8>, ContextError> {
let mut pdf_document_bytes = Vec::new();
let mut writer = BufWriter::new(&mut pdf_document_bytes);
self.inner_document.save_to(&mut writer).map_err(|error| {
ContextError::with_error("Error while saving the PDF document to bytes", &error)
})?;
mem::drop(writer);
Ok(pdf_document_bytes)
}
fn insert_fonts_into_document(&mut self) -> lopdf::Dictionary {
let mut font_dictionary = lopdf::Dictionary::new();
for (font_id, font) in self.fonts.iter_mut() {
let collected_font_dictionary = font.1.insert_into_document(&mut self.inner_document);
self.inner_document
.objects
.insert(font.0, lopdf::Object::Dictionary(collected_font_dictionary));
font_dictionary.set(font_id.clone(), lopdf::Object::Reference(font.0));
}
font_dictionary
}
fn add_operations_to_layer_in_page(
&mut self,
layer_index: usize,
page_index: usize,
operations: Vec<lopdf::content::Operation>,
) -> Result<(), ContextError> {
let pdf_layer_reference = self.get_mut_layer_in_page(layer_index, page_index)?;
pdf_layer_reference.operations.extend(operations);
Ok(())
}
fn get_font(&mut self, font_index: usize) -> Result<&((u32, u16), Font), ContextError> {
self.fonts
.get(&format!("F{font_index}"))
.ok_or(ContextError::with_context(format!(
"Failed to find font {} into the fonts map",
font_index
)))
}
fn get_mut_layer_in_page(
&mut self,
layer_index: usize,
page_index: usize,
) -> Result<&mut PdfLayer, ContextError> {
let pdf_page = self
.pages
.get_mut(page_index)
.ok_or(ContextError::with_context(format!(
"Failed to find the page with index {}",
page_index
)))?;
let pdf_layer = pdf_page
.layers
.get_mut(layer_index)
.ok_or(ContextError::with_context(format!(
"Failed to find the layer with index {}",
layer_index
)))?;
Ok(pdf_layer)
}
}
type GlyphId = u32;
type UnicodeCodePoint = u32;
type CmapBlock = Vec<(GlyphId, UnicodeCodePoint)>;
fn generate_cid_to_unicode_map(face_name: String, all_cmap_blocks: Vec<CmapBlock>) -> String {
let mut cid_to_unicode_map =
format!(include_str!("../assets/gid_to_unicode_beg.txt"), face_name);
for cmap_block in all_cmap_blocks
.into_iter()
.filter(|block| !block.is_empty() || block.len() < 100)
{
cid_to_unicode_map.push_str(format!("{} beginbfchar\r\n", cmap_block.len()).as_str());
for (glyph_id, unicode) in cmap_block {
cid_to_unicode_map.push_str(format!("<{glyph_id:04x}> <{unicode:04x}>\n").as_str());
}
cid_to_unicode_map.push_str("endbfchar\r\n");
}
cid_to_unicode_map.push_str(include_str!("../assets/gid_to_unicode_end.txt"));
cid_to_unicode_map
}
fn to_pdf_timestamp_format(date: &OffsetDateTime) -> String {
let offset = date.offset();
let offset_sign = if offset.is_negative() { '-' } else { '+' };
format!(
"D:{:04}{:02}{:02}{:02}{:02}{:02}{offset_sign}{:02}'{:02}'",
date.year(),
u8::from(date.month()),
date.day(),
date.hour(),
date.minute(),
date.second(),
offset.whole_hours().abs(),
offset.minutes_past_hour().abs(),
)
}
pub fn optimize_pdf_file_with_gs(pdf_path: &str) -> Result<(), ContextError> {
let child = std::process::Command::new("gs")
.arg("-sDEVICE=pdfwrite")
.arg("-dCompatibilityLevel=1.5")
.arg("-dPDFSETTINGS=/ebook")
.arg("-dNOPAUSE")
.arg("-dQUIET")
.arg("-dBATCH")
.arg(format!("-sOutputFile={}.swp", pdf_path))
.arg(pdf_path)
.spawn();
match child {
Ok(mut child) => {
let status = child.wait().map_err(|error| {
ContextError::with_error("Unable to wait for the gs command execution", &error)
})?;
if !status.success() {
return Err(ContextError::with_context(format!(
"gs failed with status {:?}",
status
)));
}
std::fs::rename(format!("{}.swp", pdf_path), pdf_path).map_err(|error| {
ContextError::with_error("Unable to rename the optimized PDF file", &error)
})?;
}
Err(error) => {
return Err(ContextError::with_error(
"Unable to run the gs command",
&error,
));
}
}
Ok(())
}
pub fn optimize_pdf_file_with_ps2pdf(pdf_path: &str) -> Result<(), ContextError> {
let child = std::process::Command::new("ps2pdf")
.arg(pdf_path)
.arg(format!("{}.swp", pdf_path))
.spawn();
match child {
Ok(mut child) => {
let status = child.wait().map_err(|error| {
ContextError::with_error("Unable to wait for the ps2pdf command execution", &error)
})?;
if !status.success() {
return Err(ContextError::with_context(format!(
"ps2pdf failed with status {:?}",
status
)));
}
std::fs::rename(format!("{}.swp", pdf_path), pdf_path).map_err(|error| {
ContextError::with_error("Unable to rename the optimized PDF file", &error)
})?;
}
Err(error) => {
return Err(ContextError::with_error(
"Unable to run the ps2pdf command",
&error,
));
}
}
Ok(())
}