use crate::error::{Error, Result};
use crate::fonts::GlyphRemapper;
use crate::object::Object;
use crate::writer::font_manager::EmbeddedFont;
use crate::writer::object_serializer::ObjectSerializer;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy)]
pub struct EmbeddedFontIds {
pub type0: u32,
pub cidfont: u32,
pub descriptor: u32,
pub font_file: u32,
pub tounicode: u32,
}
pub fn build_embedded_font_objects(
font: &mut EmbeddedFont,
mut id_alloc: impl FnMut() -> u32,
) -> Result<(EmbeddedFontIds, Vec<(u32, Object)>, GlyphRemapper)> {
let font_file_id = id_alloc();
let descriptor_id = id_alloc();
let cidfont_id = id_alloc();
let tounicode_id = id_alloc();
let type0_id = id_alloc();
let ids = EmbeddedFontIds {
type0: type0_id,
cidfont: cidfont_id,
descriptor: descriptor_id,
font_file: font_file_id,
tounicode: tounicode_id,
};
let mut out: Vec<(u32, Object)> = Vec::with_capacity(5);
let base_font = font.subset_name().to_string();
let (font_bytes, remapper) =
crate::fonts::subset_font_bytes(font.font_data(), 0, font.used_glyphs())
.map_err(|e| Error::Font(format!("font subsetting failed: {e}")))?;
let length1 = font_bytes.len() as i64;
let mut ff_dict: HashMap<String, Object> = HashMap::new();
ff_dict.insert("Length".to_string(), ObjectSerializer::integer(length1));
ff_dict.insert("Length1".to_string(), ObjectSerializer::integer(length1));
out.push((
font_file_id,
Object::Stream {
dict: ff_dict,
data: bytes::Bytes::from(font_bytes),
},
));
let (llx, lly, urx, ury) = font.bbox;
let descriptor = ObjectSerializer::dict(vec![
("Type", ObjectSerializer::name("FontDescriptor")),
("FontName", ObjectSerializer::name(&base_font)),
("Flags", ObjectSerializer::integer(font.flags as i64)),
(
"FontBBox",
ObjectSerializer::rect(llx as f64, lly as f64, urx as f64, ury as f64),
),
("ItalicAngle", Object::Real(font.italic_angle as f64)),
("Ascent", ObjectSerializer::integer(font.ascender as i64)),
("Descent", ObjectSerializer::integer(font.descender as i64)),
("CapHeight", ObjectSerializer::integer(font.cap_height as i64)),
("XHeight", ObjectSerializer::integer(font.x_height as i64)),
("StemV", ObjectSerializer::integer(font.stem_v as i64)),
("FontFile2", ObjectSerializer::reference(font_file_id, 0)),
]);
out.push((descriptor_id, descriptor));
let widths_str = font.generate_widths_array(&remapper);
let cid_system_info = ObjectSerializer::dict(vec![
("Registry", ObjectSerializer::string("Adobe")),
("Ordering", ObjectSerializer::string("Identity")),
("Supplement", ObjectSerializer::integer(0)),
]);
let cidfont = ObjectSerializer::dict(vec![
("Type", ObjectSerializer::name("Font")),
("Subtype", ObjectSerializer::name("CIDFontType2")),
("BaseFont", ObjectSerializer::name(&base_font)),
("CIDSystemInfo", cid_system_info),
("FontDescriptor", ObjectSerializer::reference(descriptor_id, 0)),
("CIDToGIDMap", ObjectSerializer::name("Identity")),
("W", parse_widths_string_to_array(&widths_str)),
]);
out.push((cidfont_id, cidfont));
let cmap_bytes = font.generate_tounicode_cmap(&remapper).into_bytes();
let mut cmap_dict: HashMap<String, Object> = HashMap::new();
cmap_dict.insert("Length".to_string(), ObjectSerializer::integer(cmap_bytes.len() as i64));
out.push((
tounicode_id,
Object::Stream {
dict: cmap_dict,
data: bytes::Bytes::from(cmap_bytes),
},
));
let type0 = ObjectSerializer::dict(vec![
("Type", ObjectSerializer::name("Font")),
("Subtype", ObjectSerializer::name("Type0")),
("BaseFont", ObjectSerializer::name(&base_font)),
("Encoding", ObjectSerializer::name("Identity-H")),
(
"DescendantFonts",
Object::Array(vec![ObjectSerializer::reference(cidfont_id, 0)]),
),
("ToUnicode", ObjectSerializer::reference(tounicode_id, 0)),
]);
out.push((type0_id, type0));
Ok((ids, out, remapper))
}
fn parse_widths_string_to_array(s: &str) -> Object {
let mut stack: Vec<Vec<Object>> = vec![Vec::new()];
let mut number = String::new();
let flush_number = |stack: &mut Vec<Vec<Object>>, number: &mut String| {
if !number.is_empty() {
if let Ok(n) = number.parse::<i64>() {
stack
.last_mut()
.expect("widths-array stack must never empty")
.push(ObjectSerializer::integer(n));
}
number.clear();
}
};
for ch in s.chars() {
match ch {
'[' => {
flush_number(&mut stack, &mut number);
stack.push(Vec::new());
},
']' => {
flush_number(&mut stack, &mut number);
let popped = stack.pop().unwrap_or_default();
stack
.last_mut()
.expect("widths-array stack must keep at least the root level")
.push(Object::Array(popped));
},
c if c.is_ascii_digit() || c == '-' => number.push(c),
_ => flush_number(&mut stack, &mut number),
}
}
flush_number(&mut stack, &mut number);
let mut root = stack.pop().unwrap_or_default();
if root.len() == 1 {
root.pop().unwrap_or(Object::Array(Vec::new()))
} else {
Object::Array(root)
}
}
pub type FontResourceName = String;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_widths_simple() {
let parsed = parse_widths_string_to_array("[ 36 [ 600 600 ] 65 [ 720 ] ]");
match parsed {
Object::Array(items) => assert_eq!(items.len(), 4),
other => panic!("expected array, got {other:?}"),
}
}
#[test]
fn parse_widths_empty() {
let parsed = parse_widths_string_to_array("[]");
match parsed {
Object::Array(items) => assert!(items.is_empty()),
other => panic!("expected empty array, got {other:?}"),
}
}
}