use std::collections::{HashMap, HashSet};
use std::io::Write as IoWrite;
use stet_core::context::Context;
use stet_core::dict::DictKey;
use stet_core::object::{EntityId, PsValue};
use stet_fonts::encoding::STANDARD_ENCODING;
use stet_fonts::truetype;
use stet_fonts::type2_charstring;
use crate::font_tracker::FontUsage;
use crate::pdf_objects::PdfObj;
use crate::pdf_writer::PdfWriter;
use crate::unicode_mapping;
const EEXEC_R: u16 = 55665;
const EEXEC_C1: u16 = 52845;
const EEXEC_C2: u16 = 22719;
fn eexec_encrypt(plaintext: &[u8]) -> Vec<u8> {
let prefix = [0u8; 4];
let mut r: u16 = EEXEC_R;
let mut result = Vec::with_capacity(plaintext.len() + 4);
for &plain_byte in prefix.iter().chain(plaintext.iter()) {
let cipher_byte = plain_byte ^ (r >> 8) as u8;
result.push(cipher_byte);
r = (cipher_byte as u16)
.wrapping_add(r)
.wrapping_mul(EEXEC_C1)
.wrapping_add(EEXEC_C2);
}
result
}
const CS_R: u16 = 4330;
fn decrypt_charstring(data: &[u8], len_iv: usize) -> Vec<u8> {
let mut r: u16 = CS_R;
let mut result = Vec::with_capacity(data.len());
for &cipher in data {
let plain = cipher ^ (r >> 8) as u8;
result.push(plain);
r = (cipher as u16)
.wrapping_add(r)
.wrapping_mul(EEXEC_C1)
.wrapping_add(EEXEC_C2);
}
if result.len() > len_iv {
result[len_iv..].to_vec()
} else {
Vec::new()
}
}
fn charstring_encrypt(plaintext: &[u8], len_iv: usize) -> Vec<u8> {
let prefix = vec![0u8; len_iv];
let mut r: u16 = CS_R;
let mut result = Vec::with_capacity(plaintext.len() + len_iv);
for &plain_byte in prefix.iter().chain(plaintext.iter()) {
let cipher_byte = plain_byte ^ (r >> 8) as u8;
result.push(cipher_byte);
r = (cipher_byte as u16)
.wrapping_add(r)
.wrapping_mul(EEXEC_C1)
.wrapping_add(EEXEC_C2);
}
result
}
fn find_subr_refs_deep(decrypted: &[u8], subrs: &[Vec<u8>], found: &mut HashSet<usize>) {
let mut stack: Vec<f64> = Vec::new();
let mut ps_stack: Vec<f64> = Vec::new();
scan_charstring(decrypted, subrs, found, &mut stack, &mut ps_stack, 0);
}
fn scan_charstring(
data: &[u8],
subrs: &[Vec<u8>],
found: &mut HashSet<usize>,
stack: &mut Vec<f64>,
ps_stack: &mut Vec<f64>,
depth: u32,
) {
if depth > 10 {
return;
}
let mut i = 0;
while i < data.len() {
let b = data[i];
i += 1;
match b {
12 => {
if i < data.len() {
let b2 = data[i];
i += 1;
match b2 {
12 => {
if stack.len() >= 2 {
let divisor = stack.pop().unwrap();
let dividend = stack.pop().unwrap();
if divisor != 0.0 {
stack.push(dividend / divisor);
}
}
}
16 => {
if stack.len() >= 2 {
let _subr_num = stack.pop().unwrap();
let n = stack.pop().unwrap() as usize;
let n = n.min(stack.len());
for _ in 0..n {
ps_stack.push(stack.pop().unwrap());
}
}
}
17 => {
if let Some(v) = ps_stack.pop() {
stack.push(v);
}
}
_ => stack.clear(),
}
}
}
11 => return, 14 => return, 13 => stack.clear(), 10 => {
if let Some(idx) = stack.pop() {
let idx = idx as i64;
if idx >= 0 {
let idx = idx as usize;
if idx < subrs.len() {
found.insert(idx);
scan_charstring(&subrs[idx], subrs, found, stack, ps_stack, depth + 1);
}
}
}
}
32..=246 => stack.push((b as i32 - 139) as f64),
247..=250 => {
if i < data.len() {
let b2 = data[i] as i32;
i += 1;
stack.push(((b as i32 - 247) * 256 + b2 + 108) as f64);
}
}
251..=254 => {
if i < data.len() {
let b2 = data[i] as i32;
i += 1;
stack.push((-(b as i32 - 251) * 256 - b2 - 108) as f64);
}
}
255 => {
if i + 3 < data.len() {
let val = i32::from_be_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]]);
i += 4;
stack.push(val as f64);
}
}
_ => stack.clear(),
}
}
}
fn compute_used_subrs(glyph_charstrings: &[Vec<u8>], subrs: &[Vec<u8>]) -> HashSet<usize> {
let mut needed: HashSet<usize> = HashSet::new();
for cs in glyph_charstrings {
find_subr_refs_deep(cs, subrs, &mut needed);
}
needed
}
fn find_seac_deps(decrypted: &[u8]) -> Vec<String> {
let mut deps = Vec::new();
let mut stack: Vec<f64> = Vec::new();
let mut i = 0;
while i < decrypted.len() {
let b = decrypted[i];
i += 1;
match b {
12 => {
if i < decrypted.len() {
let b2 = decrypted[i];
i += 1;
if b2 == 6 {
if stack.len() >= 5 {
let achar = stack[stack.len() - 1] as u8;
let bchar = stack[stack.len() - 2] as u8;
let bname = STANDARD_ENCODING[bchar as usize];
let aname = STANDARD_ENCODING[achar as usize];
if !bname.is_empty() && bname != ".notdef" {
deps.push(bname.to_string());
}
if !aname.is_empty() && aname != ".notdef" {
deps.push(aname.to_string());
}
}
}
stack.clear();
}
}
11 | 14 => return deps, 13 => stack.clear(), 10 => {
stack.pop();
} 32..=246 => stack.push((b as i32 - 139) as f64),
247..=250 => {
if i < decrypted.len() {
let b2 = decrypted[i] as i32;
i += 1;
stack.push(((b as i32 - 247) * 256 + b2 + 108) as f64);
}
}
251..=254 => {
if i < decrypted.len() {
let b2 = decrypted[i] as i32;
i += 1;
stack.push((-(b as i32 - 251) * 256 - b2 - 108) as f64);
}
}
255 => {
if i + 3 < decrypted.len() {
let val = i32::from_be_bytes([
decrypted[i],
decrypted[i + 1],
decrypted[i + 2],
decrypted[i + 3],
]);
i += 4;
stack.push(val as f64);
}
}
_ => stack.clear(),
}
}
deps
}
#[allow(clippy::too_many_arguments)]
fn build_type1_font_file(
ctx: &Context,
font_entity: EntityId,
usage: &FontUsage,
charstrings_entities: &[EntityId],
encoding_entities: &[Option<EntityId>],
private_entity: Option<EntityId>,
len_iv: usize,
subrs: &[Vec<u8>],
) -> Option<(Vec<u8>, usize, usize, usize)> {
let font_name_str = String::from_utf8_lossy(&usage.font_name);
let font_matrix = [0.001, 0.0, 0.0, 0.001, 0.0, 0.0];
let bbox = get_font_bbox(ctx, font_entity);
let glyph_set = compute_glyph_subset_multi(
ctx,
charstrings_entities,
encoding_entities,
&usage.used_codes,
len_iv,
);
let encoding = build_encoding_array_multi(ctx, encoding_entities);
let mut cleartext = Vec::new();
writeln!(cleartext, "%!PS-AdobeFont-1.0: {}", font_name_str).unwrap();
writeln!(cleartext, "12 dict begin").unwrap();
writeln!(cleartext, "/FontName /{} def", font_name_str).unwrap();
writeln!(cleartext, "/FontType 1 def").unwrap();
write!(cleartext, "/FontMatrix [").unwrap();
for (i, &v) in font_matrix.iter().enumerate() {
if i > 0 {
write!(cleartext, " ").unwrap();
}
write!(cleartext, "{}", format_float(v)).unwrap();
}
writeln!(cleartext, "] readonly def").unwrap();
writeln!(
cleartext,
"/FontBBox [{} {} {} {}] readonly def",
bbox[0] as i32, bbox[1] as i32, bbox[2] as i32, bbox[3] as i32
)
.unwrap();
writeln!(cleartext, "/Encoding 256 array").unwrap();
writeln!(cleartext, "0 1 255 {{1 index exch /.notdef put}} for").unwrap();
for (code, name) in encoding.iter().enumerate() {
if *name != ".notdef" {
writeln!(cleartext, "dup {} /{} put", code, name).unwrap();
}
}
writeln!(cleartext, "readonly def").unwrap();
writeln!(cleartext, "currentdict end").unwrap();
cleartext.extend_from_slice(b"currentfile eexec\n");
let length1 = cleartext.len();
let mut lines: Vec<Vec<u8>> = Vec::new();
lines.push(b"dup".to_vec());
lines.push(b"/Private 17 dict dup begin".to_vec());
lines.push(b"/RD {string currentfile exch readstring pop} executeonly def".to_vec());
lines.push(b"/ND {noaccess def} executeonly def".to_vec());
lines.push(b"/NP {noaccess put} executeonly def".to_vec());
lines.push(format!("/lenIV {} def", len_iv).into_bytes());
lines.push(b"/MinFeature {16 16} def".to_vec());
lines.push(b"/password 5839 def".to_vec());
if let Some(pe) = private_entity {
emit_private_hint_lines(&mut lines, ctx, pe);
}
if !subrs.is_empty() {
let mut glyph_charstrings: Vec<Vec<u8>> = Vec::new();
for glyph_name in &glyph_set {
if let Some(cs_bytes) = get_raw_charstring_bytes(ctx, charstrings_entities, glyph_name)
{
glyph_charstrings.push(decrypt_charstring(&cs_bytes, len_iv));
}
}
let used_subrs = compute_used_subrs(&glyph_charstrings, subrs);
let trimmed_len = used_subrs.iter().copied().max().map(|m| m + 1).unwrap_or(0);
if trimmed_len > 0 {
let return_stub = charstring_encrypt(&[11], len_iv);
lines.push(format!("/Subrs {} array", trimmed_len).into_bytes());
#[allow(clippy::needless_range_loop)]
for i in 0..trimmed_len {
let encrypted = if used_subrs.contains(&i) {
charstring_encrypt(&subrs[i], len_iv)
} else {
return_stub.clone()
};
let mut entry = format!("dup {} {} RD ", i, encrypted.len()).into_bytes();
entry.extend_from_slice(&encrypted);
entry.extend_from_slice(b"NP");
lines.push(entry);
}
lines.push(b"ND".to_vec());
}
}
lines.push(format!("2 index /CharStrings {} dict dup begin", glyph_set.len()).into_bytes());
if let Some(cs_bytes) = get_charstring_bytes(ctx, charstrings_entities, ".notdef") {
let mut entry = format!("/.notdef {} RD ", cs_bytes.len()).into_bytes();
entry.extend_from_slice(&cs_bytes);
entry.extend_from_slice(b"ND");
lines.push(entry);
}
for glyph_name in &glyph_set {
if glyph_name == ".notdef" {
continue;
}
if let Some(cs_bytes) = get_charstring_bytes(ctx, charstrings_entities, glyph_name) {
let mut entry = format!("/{} {} RD ", glyph_name, cs_bytes.len()).into_bytes();
entry.extend_from_slice(&cs_bytes);
entry.extend_from_slice(b"ND");
lines.push(entry);
}
}
lines.push(b"end".to_vec());
lines.push(b"end".to_vec());
lines.push(b"readonly put".to_vec());
lines.push(b"noaccess put".to_vec());
lines.push(b"dup /FontName get exch definefont pop".to_vec());
lines.push(b"mark currentfile closefile".to_vec());
let private: Vec<u8> = lines.join(&b'\n');
let encrypted = eexec_encrypt(&private);
let length2 = encrypted.len();
let mut footer = Vec::new();
footer.extend_from_slice(&[b'0'; 512]);
footer.extend_from_slice(b"\ncleartomark\n");
let length3 = footer.len();
let mut result = Vec::with_capacity(cleartext.len() + encrypted.len() + footer.len() + 20);
result.push(0x80);
result.push(0x01);
result.extend_from_slice(&(cleartext.len() as u32).to_le_bytes());
result.extend_from_slice(&cleartext);
result.push(0x80);
result.push(0x02);
result.extend_from_slice(&(encrypted.len() as u32).to_le_bytes());
result.extend_from_slice(&encrypted);
if !footer.is_empty() {
result.push(0x80);
result.push(0x01);
result.extend_from_slice(&(footer.len() as u32).to_le_bytes());
result.extend_from_slice(&footer);
}
result.push(0x80);
result.push(0x03);
Some((result, length1, length2, length3))
}
fn compute_glyph_subset_multi(
ctx: &Context,
charstrings_entities: &[EntityId],
encoding_entities: &[Option<EntityId>],
used_codes: &HashSet<u16>,
len_iv: usize,
) -> Vec<String> {
let mut needed: HashSet<String> = HashSet::new();
needed.insert(".notdef".to_string());
for enc in encoding_entities {
let Some(enc_entity) = enc else { continue };
for &code in used_codes {
if code > 255 {
continue;
}
let glyph_obj = ctx.arrays.get_element(*enc_entity, code as u32);
if let PsValue::Name(id) = glyph_obj.value {
let name = String::from_utf8_lossy(ctx.names.get_bytes(id)).to_string();
if name != ".notdef" {
needed.insert(name);
}
}
}
}
let mut pending: Vec<String> = needed.iter().cloned().collect();
while let Some(glyph_name) = pending.pop() {
if let Some(cs_bytes) = get_raw_charstring_bytes(ctx, charstrings_entities, &glyph_name) {
let decrypted = decrypt_charstring(&cs_bytes, len_iv);
for dep in find_seac_deps(&decrypted) {
if needed.insert(dep.clone()) {
pending.push(dep);
}
}
}
}
let mut result: Vec<String> = needed.into_iter().collect();
result.sort();
result
}
fn get_raw_charstring_bytes(
ctx: &Context,
charstrings_entities: &[EntityId],
glyph_name: &str,
) -> Option<Vec<u8>> {
let name_id = ctx.names.find(glyph_name.as_bytes())?;
for &cs_entity in charstrings_entities {
if let Some(cs_obj) = ctx.dicts.get(cs_entity, &DictKey::Name(name_id))
&& let PsValue::String { entity, start, len } = cs_obj.value
{
return Some(ctx.strings.get(entity, start, len).to_vec());
}
}
None
}
fn get_charstring_bytes(
ctx: &Context,
charstrings_entities: &[EntityId],
glyph_name: &str,
) -> Option<Vec<u8>> {
get_raw_charstring_bytes(ctx, charstrings_entities, glyph_name)
}
fn build_encoding_array_multi(
ctx: &Context,
encoding_entities: &[Option<EntityId>],
) -> Vec<String> {
let mut encoding = vec![".notdef".to_string(); 256];
for enc in encoding_entities {
let Some(enc_entity) = enc else { continue };
for code in 0..256u32 {
if encoding[code as usize] != ".notdef" {
continue; }
let obj = ctx.arrays.get_element(*enc_entity, code);
if let PsValue::Name(id) = obj.value {
let name = String::from_utf8_lossy(ctx.names.get_bytes(id)).to_string();
if name != ".notdef" {
encoding[code as usize] = name;
}
}
}
}
encoding
}
fn emit_private_hint_lines(lines: &mut Vec<Vec<u8>>, ctx: &Context, pe: EntityId) {
let numeric_keys = [
"BlueFuzz",
"BlueScale",
"BlueShift",
"ForceBold",
"LanguageGroup",
"ExpansionFactor",
];
for key in &numeric_keys {
if let Some(name_id) = ctx.names.find(key.as_bytes())
&& let Some(obj) = ctx.dicts.get(pe, &DictKey::Name(name_id))
{
if let Some(v) = obj.as_f64() {
lines.push(format!("/{} {} def", key, format_float(v)).into_bytes());
} else if let PsValue::Bool(b) = obj.value {
lines.push(format!("/{} {} def", key, b).into_bytes());
}
}
}
let array_keys = [
"BlueValues",
"OtherBlues",
"FamilyBlues",
"FamilyOtherBlues",
"StdHW",
"StdVW",
"StemSnapH",
"StemSnapV",
];
for key in &array_keys {
if let Some(name_id) = ctx.names.find(key.as_bytes())
&& let Some(obj) = ctx.dicts.get(pe, &DictKey::Name(name_id))
&& let PsValue::Array { entity, start, len } = obj.value
{
let elems = ctx.arrays.get(entity, start, len);
let mut s = format!("/{} [", key);
for (i, elem) in elems.iter().enumerate() {
if i > 0 {
s.push(' ');
}
if let Some(v) = elem.as_f64() {
s.push_str(&format_float(v));
}
}
s.push_str("] def");
lines.push(s.into_bytes());
}
}
}
fn format_float(v: f64) -> String {
if v == v.floor() && v.abs() < 1e9 {
format!("{}", v as i64)
} else {
format!("{:.6}", v)
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
}
}
fn extract_charstring_width(decrypted: &[u8], subrs: &[Vec<u8>]) -> Option<f64> {
let mut stack: Vec<f64> = Vec::new();
let mut i = 0;
while i < decrypted.len() {
let b = decrypted[i];
i += 1;
match b {
13 => {
return stack.last().copied();
}
12 => {
if i < decrypted.len() {
let b2 = decrypted[i];
i += 1;
match b2 {
7 => {
if stack.len() >= 2 {
return Some(stack[stack.len() - 2]);
}
}
12 => {
if stack.len() >= 2 {
let b_val = stack.pop().unwrap();
let a_val = stack.pop().unwrap();
stack.push(if b_val != 0.0 { a_val / b_val } else { 0.0 });
}
}
_ => {} }
}
}
10 => {
if let Some(idx) = stack.pop() {
let idx = idx as usize;
if idx < subrs.len()
&& let Some(w) = extract_charstring_width(&subrs[idx], subrs)
{
return Some(w);
}
}
}
11 | 14 => {
return None;
}
32..=246 => stack.push((b as i32 - 139) as f64),
247..=250 => {
if i < decrypted.len() {
let b2 = decrypted[i] as i32;
i += 1;
stack.push(((b as i32 - 247) * 256 + b2 + 108) as f64);
}
}
251..=254 => {
if i < decrypted.len() {
let b2 = decrypted[i] as i32;
i += 1;
stack.push((-(b as i32 - 251) * 256 - b2 - 108) as f64);
}
}
255 => {
if i + 3 < decrypted.len() {
let val = i32::from_be_bytes([
decrypted[i],
decrypted[i + 1],
decrypted[i + 2],
decrypted[i + 3],
]);
i += 4;
stack.push(val as f64);
}
}
_ => {
}
}
}
None
}
pub fn build_font_resource(
writer: &mut PdfWriter,
usage: &FontUsage,
ctx: &Context,
) -> Option<u32> {
let result = match usage.font_type {
1 => build_type1_font(writer, usage, ctx),
2 => build_type2_font(writer, usage, ctx),
3 => None, 0 | 42 => build_cid_font(writer, usage, ctx),
_ => None,
};
if result.is_none() {
eprintln!(
"[font_embedder] FAILED type={} name={} entity={:?} codes={}",
usage.font_type,
String::from_utf8_lossy(&usage.font_name),
usage.font_entity,
usage.used_codes.len()
);
}
result
}
fn build_type1_font(writer: &mut PdfWriter, usage: &FontUsage, ctx: &Context) -> Option<u32> {
let font_entity = usage.font_entity;
let font_name_str = String::from_utf8_lossy(&usage.font_name);
let mut charstrings_entities: Vec<EntityId> = Vec::new();
for &ent in &usage.all_entities {
if let Some(obj) = ctx
.dicts
.get(ent, &DictKey::Name(ctx.name_cache.n_char_strings))
&& let PsValue::Dict(e) = obj.value
&& !charstrings_entities.contains(&e)
{
charstrings_entities.push(e);
}
}
charstrings_entities.sort_by(|a, b| {
let a_count = ctx.dicts.entry(*a).entries.len();
let b_count = ctx.dicts.entry(*b).entries.len();
b_count.cmp(&a_count)
});
let mut private_entity = None;
for &ent in &usage.all_entities {
if let Some(obj) = ctx.dicts.get(ent, &DictKey::Name(ctx.name_cache.n_private))
&& let PsValue::Dict(e) = obj.value
{
private_entity = Some(e);
break;
}
}
let len_iv = private_entity
.and_then(|pe| {
ctx.dicts
.get(pe, &DictKey::Name(ctx.name_cache.n_len_iv))
.and_then(|v| v.as_i32())
})
.unwrap_or(4) as usize;
let subrs = get_decrypted_subrs(ctx, private_entity, len_iv);
let mut first_char: u16 = 255;
let mut last_char: u16 = 0;
for &code in &usage.used_codes {
if code <= 255 {
first_char = first_char.min(code);
last_char = last_char.max(code);
}
}
if first_char > last_char {
first_char = 0;
last_char = 0;
}
let mut widths: HashMap<u16, i32> = HashMap::new();
if !charstrings_entities.is_empty() {
for &ent in &usage.all_entities {
let enc = ctx
.dicts
.get(ent, &DictKey::Name(ctx.name_cache.n_encoding))
.and_then(|obj| match obj.value {
PsValue::Array { entity, .. } => Some(entity),
_ => None,
});
let Some(enc_entity) = enc else { continue };
for code in first_char..=last_char {
if widths.contains_key(&code) {
continue;
}
let glyph_name_obj = ctx.arrays.get_element(enc_entity, code as u32);
let glyph_name_id = match glyph_name_obj.value {
PsValue::Name(id) => id,
_ => continue,
};
let glyph_name_bytes = ctx.names.get_bytes(glyph_name_id);
if glyph_name_bytes == b".notdef" {
continue;
}
let mut found = None;
for &cs_entity in &charstrings_entities {
if let Some(obj) = ctx.dicts.get(cs_entity, &DictKey::Name(glyph_name_id))
&& let PsValue::String { entity, start, len } = obj.value
{
found = Some((entity, start, len));
break;
}
}
let Some((cs_ent, cs_start, cs_len)) = found else {
continue;
};
let cs_bytes = ctx.strings.get(cs_ent, cs_start, cs_len);
let decrypted = decrypt_charstring(cs_bytes, len_iv);
if let Some(w) = extract_charstring_width(&decrypted, &subrs) {
widths.insert(code, w as i32);
}
}
}
}
let widths_array: Vec<PdfObj> = (first_char..=last_char)
.map(|code| PdfObj::Int(*widths.get(&code).unwrap_or(&0) as i64))
.collect();
let all_enc_entities: Vec<Option<EntityId>> = usage
.all_entities
.iter()
.map(|&ent| {
ctx.dicts
.get(ent, &DictKey::Name(ctx.name_cache.n_encoding))
.and_then(|obj| match obj.value {
PsValue::Array { entity, .. } => Some(entity),
_ => None,
})
})
.collect();
let tounicode_map = build_tounicode_map_multi(ctx, &all_enc_entities, &usage.used_codes);
let tounicode_ref = if !tounicode_map.is_empty() {
let cmap_data = generate_tounicode_cmap(&tounicode_map, &font_name_str);
Some(writer.add_stream(Vec::new(), &cmap_data, true))
} else {
None
};
let font_file_ref = if !usage.is_standard_14 {
if !charstrings_entities.is_empty() {
build_type1_font_file(
ctx,
font_entity,
usage,
&charstrings_entities,
&all_enc_entities,
private_entity,
len_iv,
&subrs,
)
.map(|(data, len1, len2, len3)| {
let entries = vec![
(b"Length1".to_vec(), PdfObj::Int(len1 as i64)),
(b"Length2".to_vec(), PdfObj::Int(len2 as i64)),
(b"Length3".to_vec(), PdfObj::Int(len3 as i64)),
];
writer.add_stream(entries, &data, true)
})
} else {
None
}
} else {
None
};
let bbox = get_font_bbox(ctx, font_entity);
let flags = compute_font_flags(ctx, font_entity);
let descriptor_ref =
build_font_descriptor(writer, &usage.font_name, &bbox, flags, font_file_ref, None);
let encoding_obj =
build_encoding_differences_multi(ctx, &all_enc_entities, first_char, last_char);
let mut font_entries: Vec<(Vec<u8>, PdfObj)> = vec![
(b"Type".to_vec(), PdfObj::name("Font")),
(b"Subtype".to_vec(), PdfObj::name("Type1")),
(b"BaseFont".to_vec(), PdfObj::Name(usage.font_name.clone())),
(b"FirstChar".to_vec(), PdfObj::Int(first_char as i64)),
(b"LastChar".to_vec(), PdfObj::Int(last_char as i64)),
(b"Widths".to_vec(), PdfObj::Array(widths_array)),
(b"FontDescriptor".to_vec(), PdfObj::Ref(descriptor_ref)),
];
if let Some(enc) = encoding_obj {
font_entries.push((b"Encoding".to_vec(), enc));
}
if let Some(tu_ref) = tounicode_ref {
font_entries.push((b"ToUnicode".to_vec(), PdfObj::Ref(tu_ref)));
}
Some(writer.add_object(&PdfObj::Dict(font_entries)))
}
fn build_type2_font(writer: &mut PdfWriter, usage: &FontUsage, ctx: &Context) -> Option<u32> {
let font_entity = usage.font_entity;
let font_name_str = String::from_utf8_lossy(&usage.font_name);
let encoding_entity = ctx
.dicts
.get(font_entity, &DictKey::Name(ctx.name_cache.n_encoding))
.and_then(|obj| match obj.value {
PsValue::Array { entity, .. } => Some(entity),
_ => None,
});
let charstrings_entity = ctx
.dicts
.get(font_entity, &DictKey::Name(ctx.name_cache.n_char_strings))
.and_then(|obj| match obj.value {
PsValue::Dict(e) => Some(e),
_ => None,
});
let private_entity = ctx
.dicts
.get(font_entity, &DictKey::Name(ctx.name_cache.n_private))
.and_then(|obj| match obj.value {
PsValue::Dict(e) => Some(e),
_ => None,
});
let mut default_width_x = 0.0;
let mut nominal_width_x = 0.0;
let mut local_subrs: Vec<Vec<u8>> = Vec::new();
if let Some(pe) = private_entity {
if let Some(name_id) = ctx.names.find(b"defaultWidthX")
&& let Some(obj) = ctx.dicts.get(pe, &DictKey::Name(name_id))
&& let Some(v) = obj.as_f64()
{
default_width_x = v;
}
if let Some(name_id) = ctx.names.find(b"nominalWidthX")
&& let Some(obj) = ctx.dicts.get(pe, &DictKey::Name(name_id))
&& let Some(v) = obj.as_f64()
{
nominal_width_x = v;
}
if let Some(obj) = ctx.dicts.get(pe, &DictKey::Name(ctx.name_cache.n_subrs))
&& let PsValue::Array { entity, start, len } = obj.value
{
let elems = ctx.arrays.get(entity, start, len);
local_subrs = elems
.iter()
.map(|o| match o.value {
PsValue::String { entity, start, len } => {
ctx.strings.get(entity, start, len).to_vec()
}
_ => Vec::new(),
})
.collect();
}
}
let mut global_subrs: Vec<Vec<u8>> = Vec::new();
if let Some(name_id) = ctx.names.find(b"_cff_global_subrs")
&& let Some(obj) = ctx.dicts.get(font_entity, &DictKey::Name(name_id))
&& let PsValue::Array { entity, start, len } = obj.value
{
let elems = ctx.arrays.get(entity, start, len);
global_subrs = elems
.iter()
.map(|o| match o.value {
PsValue::String { entity, start, len } => {
ctx.strings.get(entity, start, len).to_vec()
}
_ => Vec::new(),
})
.collect();
}
let mut first_char: u16 = 255;
let mut last_char: u16 = 0;
for &code in &usage.used_codes {
if code <= 255 {
first_char = first_char.min(code);
last_char = last_char.max(code);
}
}
if first_char > last_char {
first_char = 0;
last_char = 0;
}
let mut widths: HashMap<u16, i32> = HashMap::new();
if let (Some(enc_entity), Some(cs_entity)) = (encoding_entity, charstrings_entity) {
for code in first_char..=last_char {
let glyph_name_obj = ctx.arrays.get_element(enc_entity, code as u32);
let glyph_name_id = match glyph_name_obj.value {
PsValue::Name(id) => id,
_ => continue,
};
let cs_obj = match ctx.dicts.get(cs_entity, &DictKey::Name(glyph_name_id)) {
Some(obj) => obj,
None => continue,
};
let (cs_ent, cs_start, cs_len) = match cs_obj.value {
PsValue::String { entity, start, len } => (entity, start, len),
_ => continue,
};
let cs_bytes = ctx.strings.get(cs_ent, cs_start, cs_len).to_vec();
if let Ok(result) = type2_charstring::execute_type2_charstring(
&cs_bytes,
&local_subrs,
&global_subrs,
default_width_x,
nominal_width_x,
false,
) {
widths.insert(code, result.width_x as i32);
}
}
}
let widths_array: Vec<PdfObj> = (first_char..=last_char)
.map(|code| PdfObj::Int(*widths.get(&code).unwrap_or(&0) as i64))
.collect();
let tounicode_map = build_tounicode_map(ctx, encoding_entity, &usage.used_codes);
let tounicode_ref = if !tounicode_map.is_empty() {
let cmap_data = generate_tounicode_cmap(&tounicode_map, &font_name_str);
Some(writer.add_stream(Vec::new(), &cmap_data, true))
} else {
None
};
let encoding_obj = build_encoding_differences(ctx, encoding_entity, first_char, last_char);
let font_file3_ref = ctx
.names
.find(b"_CFFData")
.and_then(|name_id| ctx.dicts.get(font_entity, &DictKey::Name(name_id)))
.and_then(|obj| match obj.value {
PsValue::String { entity, start, len } => {
let cff_bytes = ctx.strings.get(entity, start, len).to_vec();
let entries = vec![(b"Subtype".to_vec(), PdfObj::name("Type1C"))];
Some(writer.add_stream(entries, &cff_bytes, true))
}
_ => None,
});
let bbox = get_font_bbox(ctx, font_entity);
let flags = compute_font_flags(ctx, font_entity);
let descriptor_ref =
build_font_descriptor(writer, &usage.font_name, &bbox, flags, None, font_file3_ref);
let mut font_entries: Vec<(Vec<u8>, PdfObj)> = vec![
(b"Type".to_vec(), PdfObj::name("Font")),
(b"Subtype".to_vec(), PdfObj::name("Type1")),
(b"BaseFont".to_vec(), PdfObj::Name(usage.font_name.clone())),
(b"FirstChar".to_vec(), PdfObj::Int(first_char as i64)),
(b"LastChar".to_vec(), PdfObj::Int(last_char as i64)),
(b"Widths".to_vec(), PdfObj::Array(widths_array)),
(b"FontDescriptor".to_vec(), PdfObj::Ref(descriptor_ref)),
];
if let Some(enc) = encoding_obj {
font_entries.push((b"Encoding".to_vec(), enc));
}
if let Some(tu_ref) = tounicode_ref {
font_entries.push((b"ToUnicode".to_vec(), PdfObj::Ref(tu_ref)));
}
Some(writer.add_object(&PdfObj::Dict(font_entries)))
}
fn get_decrypted_subrs(
ctx: &Context,
private_entity: Option<EntityId>,
len_iv: usize,
) -> Vec<Vec<u8>> {
let Some(pe) = private_entity else {
return Vec::new();
};
let subrs_obj = ctx.dicts.get(pe, &DictKey::Name(ctx.name_cache.n_subrs));
let Some(obj) = subrs_obj else {
return Vec::new();
};
let (entity, start, len) = match obj.value {
PsValue::Array { entity, start, len } => (entity, start, len),
_ => return Vec::new(),
};
let elems = ctx.arrays.get(entity, start, len);
elems
.iter()
.map(|o| match o.value {
PsValue::String { entity, start, len } => {
let raw = ctx.strings.get(entity, start, len);
decrypt_charstring(raw, len_iv)
}
_ => Vec::new(),
})
.collect()
}
pub fn build_tounicode_map(
ctx: &Context,
encoding_entity: Option<EntityId>,
used_codes: &HashSet<u16>,
) -> HashMap<u16, u16> {
let mut map = HashMap::new();
let Some(enc_entity) = encoding_entity else {
return map;
};
for &code in used_codes {
if code > 255 {
continue;
}
let glyph_name_obj = ctx.arrays.get_element(enc_entity, code as u32);
if let PsValue::Name(id) = glyph_name_obj.value {
let name_bytes = ctx.names.get_bytes(id);
if let Ok(name_str) = std::str::from_utf8(name_bytes)
&& name_str != ".notdef"
&& let Some(unicode) = unicode_mapping::glyph_name_to_unicode(name_str)
{
map.insert(code, unicode);
}
}
}
map
}
pub fn build_tounicode_map_multi(
ctx: &Context,
encoding_entities: &[Option<EntityId>],
used_codes: &HashSet<u16>,
) -> HashMap<u16, u16> {
let mut map = HashMap::new();
for enc in encoding_entities {
let Some(enc_entity) = enc else { continue };
for &code in used_codes {
if code > 255 || map.contains_key(&code) {
continue;
}
let glyph_name_obj = ctx.arrays.get_element(*enc_entity, code as u32);
if let PsValue::Name(id) = glyph_name_obj.value {
let name_bytes = ctx.names.get_bytes(id);
if let Ok(name_str) = std::str::from_utf8(name_bytes)
&& name_str != ".notdef"
&& let Some(unicode) = unicode_mapping::glyph_name_to_unicode(name_str)
{
map.insert(code, unicode);
}
}
}
}
map
}
pub fn generate_tounicode_cmap(map: &HashMap<u16, u16>, font_name: &str) -> Vec<u8> {
let mut lines: Vec<Vec<u8>> = Vec::new();
lines.push(b"/CIDInit /ProcSet findresource begin".to_vec());
lines.push(b"12 dict begin".to_vec());
lines.push(b"begincmap".to_vec());
lines.push(b"/CIDSystemInfo <<".to_vec());
lines.push(b" /Registry (Adobe)".to_vec());
lines.push(b" /Ordering (UCS)".to_vec());
lines.push(b" /Supplement 0".to_vec());
lines.push(b">> def".to_vec());
lines.push(format!("/CMapName /{}-UCS def", font_name).into_bytes());
lines.push(b"/CMapType 2 def".to_vec());
lines.push(b"1 begincodespacerange".to_vec());
lines.push(b"<00> <FF>".to_vec());
lines.push(b"endcodespacerange".to_vec());
let mut sorted: Vec<_> = map.iter().collect();
sorted.sort_by_key(|&(&code, _)| code);
for chunk in sorted.chunks(100) {
lines.push(format!("{} beginbfchar", chunk.len()).into_bytes());
for &(&code, &unicode) in chunk {
lines.push(format!("<{:02X}> <{:04X}>", code, unicode).into_bytes());
}
lines.push(b"endbfchar".to_vec());
}
lines.push(b"endcmap".to_vec());
lines.push(b"CMapName currentdict /CMap defineresource pop".to_vec());
lines.push(b"end".to_vec());
lines.push(b"end".to_vec());
lines.join(&b'\n')
}
pub fn build_tounicode_for_fallback(
writer: &mut PdfWriter,
usage: &FontUsage,
ctx: &Context,
) -> Option<u32> {
let all_enc_entities: Vec<Option<EntityId>> = usage
.all_entities
.iter()
.map(|&ent| {
ctx.dicts
.get(ent, &DictKey::Name(ctx.name_cache.n_encoding))
.and_then(|obj| match obj.value {
PsValue::Array { entity, .. } => Some(entity),
_ => None,
})
})
.collect();
let tounicode_map = build_tounicode_map_multi(ctx, &all_enc_entities, &usage.used_codes);
if tounicode_map.is_empty() {
return None;
}
let font_name_str = String::from_utf8_lossy(&usage.font_name);
let cmap_data = generate_tounicode_cmap(&tounicode_map, &font_name_str);
Some(writer.add_stream(Vec::new(), &cmap_data, true))
}
fn build_encoding_differences(
ctx: &Context,
encoding_entity: Option<EntityId>,
first_char: u16,
last_char: u16,
) -> Option<PdfObj> {
build_encoding_differences_multi(ctx, &[encoding_entity], first_char, last_char)
}
fn build_encoding_differences_multi(
ctx: &Context,
encoding_entities: &[Option<EntityId>],
first_char: u16,
last_char: u16,
) -> Option<PdfObj> {
let mut differences: Vec<PdfObj> = Vec::new();
let mut need_code = true;
for code in first_char..=last_char {
let mut found_name: Option<Vec<u8>> = None;
for enc in encoding_entities {
let Some(enc_entity) = enc else { continue };
let glyph_obj = ctx.arrays.get_element(*enc_entity, code as u32);
if let PsValue::Name(id) = glyph_obj.value {
let name_bytes = ctx.names.get_bytes(id);
if name_bytes != b".notdef" {
found_name = Some(name_bytes.to_vec());
break;
}
}
}
let Some(name_bytes) = found_name else {
need_code = true;
continue;
};
if need_code {
differences.push(PdfObj::Int(code as i64));
need_code = false;
}
differences.push(PdfObj::Name(name_bytes));
}
if differences.is_empty() {
return None;
}
Some(PdfObj::Dict(vec![
(b"Type".to_vec(), PdfObj::name("Encoding")),
(b"Differences".to_vec(), PdfObj::Array(differences)),
]))
}
fn get_font_bbox(ctx: &Context, font_entity: EntityId) -> [f64; 4] {
if let Some(name_id) = ctx.names.find(b"FontBBox")
&& let Some(obj) = ctx.dicts.get(font_entity, &DictKey::Name(name_id))
&& let PsValue::Array { entity, start, len } = obj.value
&& len >= 4
{
let elems = ctx.arrays.get(entity, start, len);
return [
elems[0].as_f64().unwrap_or(0.0),
elems[1].as_f64().unwrap_or(0.0),
elems[2].as_f64().unwrap_or(0.0),
elems[3].as_f64().unwrap_or(0.0),
];
}
[0.0, 0.0, 1000.0, 1000.0]
}
fn compute_font_flags(_ctx: &Context, _font_entity: EntityId) -> u32 {
0x0020 }
fn build_font_descriptor(
writer: &mut PdfWriter,
font_name: &[u8],
bbox: &[f64; 4],
flags: u32,
font_file_ref: Option<u32>,
font_file3_ref: Option<u32>,
) -> u32 {
let mut entries = vec![
(b"Type".to_vec(), PdfObj::name("FontDescriptor")),
(b"FontName".to_vec(), PdfObj::Name(font_name.to_vec())),
(b"Flags".to_vec(), PdfObj::Int(flags as i64)),
(
b"FontBBox".to_vec(),
PdfObj::Array(bbox.iter().map(|&v| PdfObj::Real(v)).collect()),
),
(b"Ascent".to_vec(), PdfObj::Real(bbox[3].max(800.0))),
(b"Descent".to_vec(), PdfObj::Real(bbox[1].min(-200.0))),
(b"StemV".to_vec(), PdfObj::Int(80)),
(b"ItalicAngle".to_vec(), PdfObj::Int(0)),
(b"CapHeight".to_vec(), PdfObj::Int(700)),
];
if let Some(ff_ref) = font_file_ref {
entries.push((b"FontFile".to_vec(), PdfObj::Ref(ff_ref)));
}
if let Some(ff3_ref) = font_file3_ref {
entries.push((b"FontFile3".to_vec(), PdfObj::Ref(ff3_ref)));
}
writer.add_object(&PdfObj::Dict(entries))
}
fn find_composite_glyph_deps(glyf_data: &[u8]) -> Vec<u16> {
let mut deps = Vec::new();
if glyf_data.len() < 10 {
return deps;
}
let num_contours = truetype::read_i16(glyf_data, 0);
if num_contours >= 0 {
return deps; }
let mut offset = 10; loop {
if offset + 4 > glyf_data.len() {
break;
}
let flags = truetype::read_u16(glyf_data, offset);
let glyph_index = truetype::read_u16(glyf_data, offset + 2);
deps.push(glyph_index);
offset += 4;
if flags & 0x0001 != 0 {
offset += 4; } else {
offset += 2;
}
if flags & 0x0008 != 0 {
offset += 2; } else if flags & 0x0040 != 0 {
offset += 4; } else if flags & 0x0080 != 0 {
offset += 8; }
if flags & 0x0020 == 0 {
break; }
}
deps
}
fn compute_used_gids(font_data: &[u8], seed_gids: &HashSet<u16>) -> HashSet<u16> {
let mut used = seed_gids.clone();
used.insert(0); let mut worklist: Vec<u16> = used.iter().copied().collect();
while let Some(gid) = worklist.pop() {
if let Some(glyf_bytes) = truetype::get_glyf_data(font_data, gid) {
for dep_gid in find_composite_glyph_deps(&glyf_bytes) {
if used.insert(dep_gid) {
worklist.push(dep_gid);
}
}
}
}
used
}
fn subset_truetype(font_data: &[u8], used_gids: &HashSet<u16>) -> Option<Vec<u8>> {
let (maxp_off, _) = truetype::find_table(font_data, b"maxp")?;
if maxp_off + 6 > font_data.len() {
return None;
}
let num_glyphs = truetype::read_u16(font_data, maxp_off + 4) as usize;
let has_glyf = truetype::find_table(font_data, b"glyf").is_some();
let has_loca = truetype::find_table(font_data, b"loca").is_some();
if !has_glyf || !has_loca {
return None; }
let mut new_glyf: Vec<u8> = Vec::new();
let mut loca_offsets: Vec<u32> = Vec::with_capacity(num_glyphs + 1);
for gid in 0..num_glyphs {
loca_offsets.push(new_glyf.len() as u32);
if used_gids.contains(&(gid as u16))
&& let Some(glyf_bytes) = truetype::get_glyf_data(font_data, gid as u16)
{
new_glyf.extend_from_slice(&glyf_bytes);
if !new_glyf.len().is_multiple_of(2) {
new_glyf.push(0);
}
}
}
loca_offsets.push(new_glyf.len() as u32);
let mut loca_data = Vec::with_capacity(loca_offsets.len() * 4);
for &off in &loca_offsets {
loca_data.extend_from_slice(&off.to_be_bytes());
}
let num_tables = truetype::read_u16(font_data, 4) as usize;
let mut keep_tables: Vec<(&[u8], Vec<u8>)> = Vec::new();
for i in 0..num_tables {
let entry_off = 12 + i * 16;
if entry_off + 16 > font_data.len() {
break;
}
let tag = &font_data[entry_off..entry_off + 4];
let tbl_offset = truetype::read_u32(font_data, entry_off + 8) as usize;
let tbl_length = truetype::read_u32(font_data, entry_off + 12) as usize;
if tag == b"glyf" || tag == b"loca" {
continue; }
if tbl_offset + tbl_length <= font_data.len() {
let mut data = font_data[tbl_offset..tbl_offset + tbl_length].to_vec();
if tag == b"head" && data.len() >= 52 {
data[50] = 0;
data[51] = 1;
}
keep_tables.push((tag, data));
}
}
keep_tables.push((b"glyf", new_glyf));
keep_tables.push((b"loca", loca_data));
keep_tables.sort_by_key(|(tag, _)| *tag);
Some(assemble_truetype(&keep_tables))
}
fn reconstruct_truetype(
raw_sfnts: &[u8],
ctx: &Context,
cidfont_entity: EntityId,
) -> Option<Vec<u8>> {
let gd_name = ctx.names.find(b"GlyphDirectory")?;
let gd_obj = ctx.dicts.get(cidfont_entity, &DictKey::Name(gd_name))?;
let gd_entity = match gd_obj.value {
PsValue::Dict(e) => e,
_ => return None,
};
let (maxp_off, _) = truetype::find_table(raw_sfnts, b"maxp")?;
if maxp_off + 6 > raw_sfnts.len() {
return None;
}
let num_glyphs = truetype::read_u16(raw_sfnts, maxp_off + 4) as usize;
if num_glyphs == 0 {
return None;
}
let mut glyf_data: Vec<u8> = Vec::new();
let mut loca_offsets: Vec<u32> = Vec::with_capacity(num_glyphs + 1);
for gid in 0..num_glyphs {
loca_offsets.push(glyf_data.len() as u32);
if let Some(entry_obj) = ctx.dicts.get(gd_entity, &DictKey::Int(gid as i32))
&& let PsValue::String { entity, start, len } = entry_obj.value
{
let glyph_bytes = ctx.strings.get(entity, start, len);
if !glyph_bytes.is_empty() {
glyf_data.extend_from_slice(glyph_bytes);
if !glyf_data.len().is_multiple_of(2) {
glyf_data.push(0);
}
}
}
}
loca_offsets.push(glyf_data.len() as u32);
let mut loca_data = Vec::with_capacity(loca_offsets.len() * 4);
for &offset in &loca_offsets {
loca_data.extend_from_slice(&offset.to_be_bytes());
}
let num_tables_raw = if raw_sfnts.len() >= 6 {
truetype::read_u16(raw_sfnts, 4) as usize
} else {
return None;
};
let mut keep_tables: Vec<(&[u8], Vec<u8>)> = Vec::new();
for i in 0..num_tables_raw {
let entry_off = 12 + i * 16;
if entry_off + 16 > raw_sfnts.len() {
break;
}
let tag = &raw_sfnts[entry_off..entry_off + 4];
let tbl_offset = truetype::read_u32(raw_sfnts, entry_off + 8) as usize;
let tbl_length = truetype::read_u32(raw_sfnts, entry_off + 12) as usize;
if tag == b"gdir" || tag == b"glyf" || tag == b"loca" {
continue;
}
if tbl_offset + tbl_length <= raw_sfnts.len() {
let mut data = raw_sfnts[tbl_offset..tbl_offset + tbl_length].to_vec();
if tag == b"head" && data.len() >= 52 {
data[50] = 0;
data[51] = 1; }
keep_tables.push((tag, data));
}
}
keep_tables.push((b"glyf", glyf_data));
keep_tables.push((b"loca", loca_data));
keep_tables.sort_by_key(|(tag, _)| *tag);
Some(assemble_truetype(&keep_tables))
}
fn assemble_truetype(tables: &[(&[u8], Vec<u8>)]) -> Vec<u8> {
let num_tables = tables.len();
let entry_selector = if num_tables > 0 {
(num_tables as f64).log2().floor() as u16
} else {
0
};
let search_range = (1u16 << entry_selector) * 16;
let range_shift = (num_tables as u16) * 16 - search_range;
let header_size = 12 + num_tables * 16;
let mut current_offset = header_size;
let mut table_entries: Vec<(&[u8], &[u8], usize, usize)> = Vec::new();
for (tag, data) in tables {
let padded_len = (data.len() + 3) & !3;
table_entries.push((tag, data.as_slice(), current_offset, padded_len));
current_offset += padded_len;
}
let mut result = Vec::with_capacity(current_offset);
result.extend_from_slice(&0x00010000u32.to_be_bytes());
result.extend_from_slice(&(num_tables as u16).to_be_bytes());
result.extend_from_slice(&search_range.to_be_bytes());
result.extend_from_slice(&entry_selector.to_be_bytes());
result.extend_from_slice(&range_shift.to_be_bytes());
for &(tag, data, offset, _padded_len) in &table_entries {
let checksum = calc_table_checksum(data);
result.extend_from_slice(tag);
result.extend_from_slice(&checksum.to_be_bytes());
result.extend_from_slice(&(offset as u32).to_be_bytes());
result.extend_from_slice(&(data.len() as u32).to_be_bytes());
}
for &(_tag, data, _offset, padded_len) in &table_entries {
result.extend_from_slice(data);
let padding = padded_len - data.len();
result.extend(std::iter::repeat_n(0u8, padding));
}
result
}
fn calc_table_checksum(data: &[u8]) -> u32 {
let mut total: u32 = 0;
let mut i = 0;
while i + 4 <= data.len() {
total = total.wrapping_add(u32::from_be_bytes([
data[i],
data[i + 1],
data[i + 2],
data[i + 3],
]));
i += 4;
}
if i < data.len() {
let mut last = [0u8; 4];
for (j, &b) in data[i..].iter().enumerate() {
last[j] = b;
}
total = total.wrapping_add(u32::from_be_bytes(last));
}
total
}
fn build_cid_font(writer: &mut PdfWriter, usage: &FontUsage, ctx: &Context) -> Option<u32> {
let font_entity = usage.font_entity;
let cidfont_entity = match get_cidfont_descendant(ctx, font_entity) {
Some(e) => e,
None => {
return build_type42_font(writer, usage, ctx);
}
};
let raw_font_data = concatenate_sfnts(ctx, cidfont_entity)?;
let needs_reconstruction = truetype::find_table(&raw_font_data, b"glyf").is_none()
|| truetype::find_table(&raw_font_data, b"loca").is_none();
let font_data = if needs_reconstruction {
match reconstruct_truetype(&raw_font_data, ctx, cidfont_entity) {
Some(reconstructed) => reconstructed,
None => raw_font_data,
}
} else {
raw_font_data
};
let units_per_em = truetype::get_units_per_em(&font_data);
let scale = 1000.0 / units_per_em as f64;
let cid_to_gid = if needs_reconstruction {
usage
.used_codes
.iter()
.map(|&cid| (cid, cid))
.collect::<HashMap<u16, u16>>()
} else {
build_cid_to_gid_from_cmap(&font_data)
};
let max_cid = usage.used_codes.iter().copied().max().unwrap_or(0);
let w_array = build_w_array(&font_data, &usage.used_codes, &cid_to_gid, scale);
let default_width = truetype::get_advance_width(&font_data, 0)
.map(|w| (w as f64 * scale).round() as i64)
.unwrap_or(1000);
let cid_to_gid_value = if needs_reconstruction {
PdfObj::name("Identity")
} else {
let cid_to_gid_data = build_cid_to_gid_stream(&cid_to_gid, max_cid);
PdfObj::Ref(writer.add_stream(Vec::new(), &cid_to_gid_data, true))
};
let seed_gids: HashSet<u16> = usage
.used_codes
.iter()
.filter_map(|&cid| cid_to_gid.get(&cid).copied())
.collect();
let used_gids = compute_used_gids(&font_data, &seed_gids);
let font_data = subset_truetype(&font_data, &used_gids).unwrap_or(font_data);
let font_file_entries = vec![(b"Length1".to_vec(), PdfObj::Int(font_data.len() as i64))];
let font_file2_ref = writer.add_stream(font_file_entries, &font_data, true);
let bbox = get_truetype_bbox(&font_data, scale);
let (ascent, descent) = get_truetype_ascent_descent(&font_data, scale);
let descriptor_ref = {
let mut entries = vec![
(b"Type".to_vec(), PdfObj::name("FontDescriptor")),
(b"FontName".to_vec(), PdfObj::Name(usage.font_name.clone())),
(b"Flags".to_vec(), PdfObj::Int(0x0004)), (
b"FontBBox".to_vec(),
PdfObj::Array(bbox.iter().map(|&v| PdfObj::Real(v)).collect()),
),
(b"Ascent".to_vec(), PdfObj::Real(ascent)),
(b"Descent".to_vec(), PdfObj::Real(descent)),
(b"StemV".to_vec(), PdfObj::Int(80)),
(b"ItalicAngle".to_vec(), PdfObj::Int(0)),
(b"CapHeight".to_vec(), PdfObj::Int(700)),
(b"FontFile2".to_vec(), PdfObj::Ref(font_file2_ref)),
];
let _ = &mut entries;
writer.add_object(&PdfObj::Dict(entries))
};
let cid_system_info = PdfObj::Dict(vec![
(b"Registry".to_vec(), PdfObj::LitString(b"Adobe".to_vec())),
(
b"Ordering".to_vec(),
PdfObj::LitString(b"Identity".to_vec()),
),
(b"Supplement".to_vec(), PdfObj::Int(0)),
]);
let mut cid_font_entries: Vec<(Vec<u8>, PdfObj)> = vec![
(b"Type".to_vec(), PdfObj::name("Font")),
(b"Subtype".to_vec(), PdfObj::name("CIDFontType2")),
(b"BaseFont".to_vec(), PdfObj::Name(usage.font_name.clone())),
(b"CIDSystemInfo".to_vec(), cid_system_info),
(b"FontDescriptor".to_vec(), PdfObj::Ref(descriptor_ref)),
(b"DW".to_vec(), PdfObj::Int(default_width)),
(b"CIDToGIDMap".to_vec(), cid_to_gid_value),
];
if !w_array.is_empty() {
cid_font_entries.push((b"W".to_vec(), PdfObj::Array(w_array)));
}
let cid_font_ref = writer.add_object(&PdfObj::Dict(cid_font_entries));
let tounicode_map = build_cid_tounicode(&font_data, &usage.used_codes, &cid_to_gid);
let tounicode_ref = if !tounicode_map.is_empty() {
let font_name_str = String::from_utf8_lossy(&usage.font_name);
let cmap_data = generate_cid_tounicode_cmap(&tounicode_map, &font_name_str);
Some(writer.add_stream(Vec::new(), &cmap_data, true))
} else {
None
};
let mut type0_entries: Vec<(Vec<u8>, PdfObj)> = vec![
(b"Type".to_vec(), PdfObj::name("Font")),
(b"Subtype".to_vec(), PdfObj::name("Type0")),
(b"BaseFont".to_vec(), PdfObj::Name(usage.font_name.clone())),
(b"Encoding".to_vec(), PdfObj::name("Identity-H")),
(
b"DescendantFonts".to_vec(),
PdfObj::Array(vec![PdfObj::Ref(cid_font_ref)]),
),
];
if let Some(tu_ref) = tounicode_ref {
type0_entries.push((b"ToUnicode".to_vec(), PdfObj::Ref(tu_ref)));
}
Some(writer.add_object(&PdfObj::Dict(type0_entries)))
}
fn get_cidfont_descendant(ctx: &Context, font_entity: EntityId) -> Option<EntityId> {
let fdep_name = ctx.names.find(b"FDepVector")?;
let fdep_obj = ctx.dicts.get(font_entity, &DictKey::Name(fdep_name))?;
let (arr_entity, arr_start, _) = match fdep_obj.value {
PsValue::Array { entity, start, len } => (entity, start, len),
_ => return None,
};
let cidfont_obj = ctx.arrays.get_element(arr_entity, arr_start);
match cidfont_obj.value {
PsValue::Dict(e) => Some(e),
_ => None,
}
}
fn concatenate_sfnts(ctx: &Context, dict_entity: EntityId) -> Option<Vec<u8>> {
let sfnts_name = ctx.names.find(b"sfnts")?;
let sfnts_obj = ctx.dicts.get(dict_entity, &DictKey::Name(sfnts_name))?;
let (arr_entity, arr_start, arr_len) = match sfnts_obj.value {
PsValue::Array { entity, start, len } => (entity, start, len),
_ => return None,
};
let mut strings: Vec<&[u8]> = Vec::new();
for i in 0..arr_len {
let elem = ctx.arrays.get_element(arr_entity, arr_start + i);
if let PsValue::String { entity, start, len } = elem.value {
strings.push(ctx.strings.get(entity, start, len));
}
}
if strings.is_empty() {
return None;
}
Some(truetype::concatenate_sfnts(&strings))
}
fn build_cid_to_gid_from_cmap(font_data: &[u8]) -> HashMap<u16, u16> {
let mut map = HashMap::new();
let Some((cmap_off, _cmap_len)) = truetype::find_table(font_data, b"cmap") else {
return map;
};
if cmap_off + 4 > font_data.len() {
return map;
}
let num_subtables = truetype::read_u16(font_data, cmap_off + 2) as usize;
for i in 0..num_subtables {
let rec_off = cmap_off + 4 + i * 8;
if rec_off + 8 > font_data.len() {
break;
}
let platform_id = truetype::read_u16(font_data, rec_off);
let encoding_id = truetype::read_u16(font_data, rec_off + 2);
let subtable_offset = truetype::read_u32(font_data, rec_off + 4) as usize;
let is_unicode =
platform_id == 0 || (platform_id == 3 && (encoding_id == 1 || encoding_id == 10));
if !is_unicode {
continue;
}
let st_off = cmap_off + subtable_offset;
if st_off + 2 > font_data.len() {
continue;
}
let format = truetype::read_u16(font_data, st_off);
match format {
4 => parse_cmap_format4(font_data, st_off, &mut map),
12 => parse_cmap_format12(font_data, st_off, &mut map),
_ => {}
}
}
map
}
fn parse_cmap_format4(font_data: &[u8], offset: usize, map: &mut HashMap<u16, u16>) {
if offset + 14 > font_data.len() {
return;
}
let seg_count = truetype::read_u16(font_data, offset + 6) as usize / 2;
let end_codes_off = offset + 14;
let start_codes_off = end_codes_off + seg_count * 2 + 2; let id_delta_off = start_codes_off + seg_count * 2;
let id_range_off = id_delta_off + seg_count * 2;
for seg in 0..seg_count {
if end_codes_off + seg * 2 + 2 > font_data.len()
|| start_codes_off + seg * 2 + 2 > font_data.len()
|| id_delta_off + seg * 2 + 2 > font_data.len()
|| id_range_off + seg * 2 + 2 > font_data.len()
{
break;
}
let end_code = truetype::read_u16(font_data, end_codes_off + seg * 2);
let start_code = truetype::read_u16(font_data, start_codes_off + seg * 2);
let id_delta = truetype::read_i16(font_data, id_delta_off + seg * 2) as i32;
let id_range_offset = truetype::read_u16(font_data, id_range_off + seg * 2) as usize;
if start_code == 0xFFFF {
break;
}
for code in start_code..=end_code {
let gid = if id_range_offset == 0 {
((code as i32 + id_delta) & 0xFFFF) as u16
} else {
let glyph_idx_off = id_range_off
+ seg * 2
+ id_range_offset
+ (code as usize - start_code as usize) * 2;
if glyph_idx_off + 2 <= font_data.len() {
let gid_raw = truetype::read_u16(font_data, glyph_idx_off);
if gid_raw != 0 {
((gid_raw as i32 + id_delta) & 0xFFFF) as u16
} else {
0
}
} else {
0
}
};
if gid != 0 {
map.insert(code, gid);
}
}
}
}
fn parse_cmap_format12(font_data: &[u8], offset: usize, map: &mut HashMap<u16, u16>) {
if offset + 16 > font_data.len() {
return;
}
let n_groups = truetype::read_u32(font_data, offset + 12) as usize;
let groups_off = offset + 16;
for i in 0..n_groups {
let g_off = groups_off + i * 12;
if g_off + 12 > font_data.len() {
break;
}
let start_char = truetype::read_u32(font_data, g_off);
let end_char = truetype::read_u32(font_data, g_off + 4);
let start_gid = truetype::read_u32(font_data, g_off + 8);
for code in start_char..=end_char.min(0xFFFF) {
let gid = start_gid + (code - start_char);
if gid != 0 && gid <= 0xFFFF {
map.insert(code as u16, gid as u16);
}
}
}
}
fn build_w_array(
font_data: &[u8],
used_codes: &HashSet<u16>,
cid_to_gid: &HashMap<u16, u16>,
scale: f64,
) -> Vec<PdfObj> {
let mut cid_widths: Vec<(u16, i64)> = Vec::new();
for &cid in used_codes {
let gid = cid_to_gid.get(&cid).copied().unwrap_or(cid);
if let Some(aw) = truetype::get_advance_width(font_data, gid) {
let w = (aw as f64 * scale).round() as i64;
cid_widths.push((cid, w));
}
}
cid_widths.sort_by_key(|&(cid, _)| cid);
let mut result: Vec<PdfObj> = Vec::new();
let mut i = 0;
while i < cid_widths.len() {
let start_cid = cid_widths[i].0;
let mut widths = vec![PdfObj::Int(cid_widths[i].1)];
let mut j = i + 1;
while j < cid_widths.len() && cid_widths[j].0 == cid_widths[j - 1].0 + 1 {
widths.push(PdfObj::Int(cid_widths[j].1));
j += 1;
}
result.push(PdfObj::Int(start_cid as i64));
result.push(PdfObj::Array(widths));
i = j;
}
result
}
fn build_cid_to_gid_stream(cid_to_gid: &HashMap<u16, u16>, max_cid: u16) -> Vec<u8> {
let len = (max_cid as usize + 1) * 2;
let mut data = vec![0u8; len];
for (&cid, &gid) in cid_to_gid {
if (cid as usize) <= max_cid as usize {
let off = cid as usize * 2;
data[off] = (gid >> 8) as u8;
data[off + 1] = (gid & 0xFF) as u8;
}
}
data
}
fn build_cid_tounicode(
font_data: &[u8],
used_codes: &HashSet<u16>,
cid_to_gid: &HashMap<u16, u16>,
) -> HashMap<u16, u16> {
let mut gid_to_unicode: HashMap<u16, u16> = HashMap::new();
if let Some((cmap_off, _)) = truetype::find_table(font_data, b"cmap")
&& cmap_off + 4 <= font_data.len()
{
let num_subtables = truetype::read_u16(font_data, cmap_off + 2) as usize;
for i in 0..num_subtables {
let rec_off = cmap_off + 4 + i * 8;
if rec_off + 8 > font_data.len() {
break;
}
let platform_id = truetype::read_u16(font_data, rec_off);
let encoding_id = truetype::read_u16(font_data, rec_off + 2);
let st_offset = truetype::read_u32(font_data, rec_off + 4) as usize;
let is_unicode =
platform_id == 0 || (platform_id == 3 && (encoding_id == 1 || encoding_id == 10));
if !is_unicode {
continue;
}
let st_off = cmap_off + st_offset;
if st_off + 2 > font_data.len() {
continue;
}
let mut unicode_to_gid = HashMap::new();
let format = truetype::read_u16(font_data, st_off);
match format {
4 => parse_cmap_format4(font_data, st_off, &mut unicode_to_gid),
12 => parse_cmap_format12(font_data, st_off, &mut unicode_to_gid),
_ => {}
}
for (unicode, gid) in unicode_to_gid {
gid_to_unicode.entry(gid).or_insert(unicode);
}
break; }
}
let mut map = HashMap::new();
for &cid in used_codes {
let gid = cid_to_gid.get(&cid).copied().unwrap_or(cid);
if let Some(&unicode) = gid_to_unicode.get(&gid) {
map.insert(cid, unicode);
} else if cid > 0 && cid < 0xFFFE {
map.insert(cid, cid);
}
}
map
}
fn generate_cid_tounicode_cmap(map: &HashMap<u16, u16>, font_name: &str) -> Vec<u8> {
let mut lines: Vec<Vec<u8>> = Vec::new();
lines.push(b"/CIDInit /ProcSet findresource begin".to_vec());
lines.push(b"12 dict begin".to_vec());
lines.push(b"begincmap".to_vec());
lines.push(b"/CIDSystemInfo <<".to_vec());
lines.push(b" /Registry (Adobe)".to_vec());
lines.push(b" /Ordering (UCS)".to_vec());
lines.push(b" /Supplement 0".to_vec());
lines.push(b">> def".to_vec());
lines.push(format!("/CMapName /{}-UCS def", font_name).into_bytes());
lines.push(b"/CMapType 2 def".to_vec());
lines.push(b"1 begincodespacerange".to_vec());
lines.push(b"<0000> <FFFF>".to_vec());
lines.push(b"endcodespacerange".to_vec());
let mut sorted: Vec<_> = map.iter().collect();
sorted.sort_by_key(|&(&code, _)| code);
for chunk in sorted.chunks(100) {
lines.push(format!("{} beginbfchar", chunk.len()).into_bytes());
for &(&cid, &unicode) in chunk {
lines.push(format!("<{:04X}> <{:04X}>", cid, unicode).into_bytes());
}
lines.push(b"endbfchar".to_vec());
}
lines.push(b"endcmap".to_vec());
lines.push(b"CMapName currentdict /CMap defineresource pop".to_vec());
lines.push(b"end".to_vec());
lines.push(b"end".to_vec());
lines.join(&b'\n')
}
fn get_truetype_bbox(font_data: &[u8], scale: f64) -> [f64; 4] {
if let Some((head_off, _)) = truetype::find_table(font_data, b"head")
&& head_off + 44 <= font_data.len()
{
let x_min = truetype::read_i16(font_data, head_off + 36) as f64 * scale;
let y_min = truetype::read_i16(font_data, head_off + 38) as f64 * scale;
let x_max = truetype::read_i16(font_data, head_off + 40) as f64 * scale;
let y_max = truetype::read_i16(font_data, head_off + 42) as f64 * scale;
return [x_min, y_min, x_max, y_max];
}
[0.0, -200.0, 1000.0, 800.0]
}
fn get_truetype_ascent_descent(font_data: &[u8], scale: f64) -> (f64, f64) {
if let Some((hhea_off, _)) = truetype::find_table(font_data, b"hhea")
&& hhea_off + 8 <= font_data.len()
{
let ascent = truetype::read_i16(font_data, hhea_off + 4) as f64 * scale;
let descent = truetype::read_i16(font_data, hhea_off + 6) as f64 * scale;
return (ascent, descent);
}
(800.0, -200.0)
}
fn build_type42_font(writer: &mut PdfWriter, usage: &FontUsage, ctx: &Context) -> Option<u32> {
let font_entity = usage.font_entity;
let font_name_str = String::from_utf8_lossy(&usage.font_name);
let font_data = concatenate_sfnts(ctx, font_entity)?;
let units_per_em = truetype::get_units_per_em(&font_data);
let scale = 1000.0 / units_per_em as f64;
let encoding_entity = ctx
.dicts
.get(font_entity, &DictKey::Name(ctx.name_cache.n_encoding))
.and_then(|obj| match obj.value {
PsValue::Array { entity, .. } => Some(entity),
_ => None,
});
let charstrings_entity = ctx
.dicts
.get(font_entity, &DictKey::Name(ctx.name_cache.n_char_strings))
.and_then(|obj| match obj.value {
PsValue::Dict(e) => Some(e),
_ => None,
});
let mut first_char: u16 = 255;
let mut last_char: u16 = 0;
for &code in &usage.used_codes {
if code <= 255 {
first_char = first_char.min(code);
last_char = last_char.max(code);
}
}
if first_char > last_char {
first_char = 0;
last_char = 0;
}
let mut widths: HashMap<u16, i32> = HashMap::new();
if let (Some(enc_entity), Some(cs_entity)) = (encoding_entity, charstrings_entity) {
for code in first_char..=last_char {
let glyph_name_obj = ctx.arrays.get_element(enc_entity, code as u32);
let glyph_name_id = match glyph_name_obj.value {
PsValue::Name(id) => id,
_ => continue,
};
let cs_obj = match ctx.dicts.get(cs_entity, &DictKey::Name(glyph_name_id)) {
Some(obj) => obj,
None => continue,
};
let gid = match cs_obj.as_i32() {
Some(g) => g as u16,
None => continue,
};
if let Some(aw) = truetype::get_advance_width(&font_data, gid) {
widths.insert(code, (aw as f64 * scale).round() as i32);
}
}
}
let widths_array: Vec<PdfObj> = (first_char..=last_char)
.map(|code| PdfObj::Int(*widths.get(&code).unwrap_or(&0) as i64))
.collect();
let tounicode_map = build_tounicode_map(ctx, encoding_entity, &usage.used_codes);
let tounicode_ref = if !tounicode_map.is_empty() {
let cmap_data = generate_tounicode_cmap(&tounicode_map, &font_name_str);
Some(writer.add_stream(Vec::new(), &cmap_data, true))
} else {
None
};
let font_data =
if let (Some(enc_entity), Some(cs_entity)) = (encoding_entity, charstrings_entity) {
let mut seed_gids: HashSet<u16> = HashSet::new();
for &code in &usage.used_codes {
if code > 255 {
continue;
}
let glyph_name_obj = ctx.arrays.get_element(enc_entity, code as u32);
if let PsValue::Name(name_id) = glyph_name_obj.value
&& let Some(cs_obj) = ctx.dicts.get(cs_entity, &DictKey::Name(name_id))
&& let Some(gid) = cs_obj.as_i32()
{
seed_gids.insert(gid as u16);
}
}
if seed_gids.is_empty() {
font_data } else {
let used_gids = compute_used_gids(&font_data, &seed_gids);
subset_truetype(&font_data, &used_gids).unwrap_or(font_data)
}
} else {
font_data };
let font_file_entries = vec![(b"Length1".to_vec(), PdfObj::Int(font_data.len() as i64))];
let font_file2_ref = writer.add_stream(font_file_entries, &font_data, true);
let bbox = get_truetype_bbox(&font_data, scale);
let (ascent, descent) = get_truetype_ascent_descent(&font_data, scale);
let descriptor_ref = {
let entries = vec![
(b"Type".to_vec(), PdfObj::name("FontDescriptor")),
(b"FontName".to_vec(), PdfObj::Name(usage.font_name.clone())),
(b"Flags".to_vec(), PdfObj::Int(0x0020)), (
b"FontBBox".to_vec(),
PdfObj::Array(bbox.iter().map(|&v| PdfObj::Real(v)).collect()),
),
(b"Ascent".to_vec(), PdfObj::Real(ascent)),
(b"Descent".to_vec(), PdfObj::Real(descent)),
(b"StemV".to_vec(), PdfObj::Int(80)),
(b"ItalicAngle".to_vec(), PdfObj::Int(0)),
(b"CapHeight".to_vec(), PdfObj::Int(700)),
(b"FontFile2".to_vec(), PdfObj::Ref(font_file2_ref)),
];
writer.add_object(&PdfObj::Dict(entries))
};
let encoding_obj = build_encoding_differences(ctx, encoding_entity, first_char, last_char);
let mut font_entries: Vec<(Vec<u8>, PdfObj)> = vec![
(b"Type".to_vec(), PdfObj::name("Font")),
(b"Subtype".to_vec(), PdfObj::name("TrueType")),
(b"BaseFont".to_vec(), PdfObj::Name(usage.font_name.clone())),
(b"FirstChar".to_vec(), PdfObj::Int(first_char as i64)),
(b"LastChar".to_vec(), PdfObj::Int(last_char as i64)),
(b"Widths".to_vec(), PdfObj::Array(widths_array)),
(b"FontDescriptor".to_vec(), PdfObj::Ref(descriptor_ref)),
];
if let Some(enc) = encoding_obj {
font_entries.push((b"Encoding".to_vec(), enc));
}
if let Some(tu_ref) = tounicode_ref {
font_entries.push((b"ToUnicode".to_vec(), PdfObj::Ref(tu_ref)));
}
Some(writer.add_object(&PdfObj::Dict(font_entries)))
}
pub fn extract_widths(usage: &FontUsage, ctx: &Context) -> HashMap<u16, i32> {
match usage.font_type {
1 => extract_type1_widths(usage, ctx),
2 => extract_type2_widths(usage, ctx),
0 | 42 => extract_cid_widths(usage, ctx),
_ => HashMap::new(),
}
}
fn extract_type1_widths(usage: &FontUsage, ctx: &Context) -> HashMap<u16, i32> {
let font_entity = usage.font_entity;
let encoding_entity = ctx
.dicts
.get(font_entity, &DictKey::Name(ctx.name_cache.n_encoding))
.and_then(|obj| match obj.value {
PsValue::Array { entity, .. } => Some(entity),
_ => None,
});
let charstrings_entity = ctx
.dicts
.get(font_entity, &DictKey::Name(ctx.name_cache.n_char_strings))
.and_then(|obj| match obj.value {
PsValue::Dict(e) => Some(e),
_ => None,
});
let private_entity = ctx
.dicts
.get(font_entity, &DictKey::Name(ctx.name_cache.n_private))
.and_then(|obj| match obj.value {
PsValue::Dict(e) => Some(e),
_ => None,
});
let len_iv = private_entity
.and_then(|pe| {
ctx.dicts
.get(pe, &DictKey::Name(ctx.name_cache.n_len_iv))
.and_then(|v| v.as_i32())
})
.unwrap_or(4) as usize;
let subrs = get_decrypted_subrs(ctx, private_entity, len_iv);
let mut widths: HashMap<u16, i32> = HashMap::new();
if let (Some(enc_entity), Some(cs_entity)) = (encoding_entity, charstrings_entity) {
for &code in &usage.used_codes {
if code > 255 {
continue;
}
let glyph_name_obj = ctx.arrays.get_element(enc_entity, code as u32);
let glyph_name_id = match glyph_name_obj.value {
PsValue::Name(id) => id,
_ => continue,
};
let cs_obj = match ctx.dicts.get(cs_entity, &DictKey::Name(glyph_name_id)) {
Some(obj) => obj,
None => continue,
};
let (cs_ent, cs_start, cs_len) = match cs_obj.value {
PsValue::String { entity, start, len } => (entity, start, len),
_ => continue,
};
let cs_bytes = ctx.strings.get(cs_ent, cs_start, cs_len);
let decrypted = decrypt_charstring(cs_bytes, len_iv);
if let Some(w) = extract_charstring_width(&decrypted, &subrs) {
widths.insert(code, w as i32);
}
}
}
widths
}
fn extract_type2_widths(usage: &FontUsage, ctx: &Context) -> HashMap<u16, i32> {
let font_entity = usage.font_entity;
let encoding_entity = ctx
.dicts
.get(font_entity, &DictKey::Name(ctx.name_cache.n_encoding))
.and_then(|obj| match obj.value {
PsValue::Array { entity, .. } => Some(entity),
_ => None,
});
let charstrings_entity = ctx
.dicts
.get(font_entity, &DictKey::Name(ctx.name_cache.n_char_strings))
.and_then(|obj| match obj.value {
PsValue::Dict(e) => Some(e),
_ => None,
});
let private_entity = ctx
.dicts
.get(font_entity, &DictKey::Name(ctx.name_cache.n_private))
.and_then(|obj| match obj.value {
PsValue::Dict(e) => Some(e),
_ => None,
});
let mut default_width_x = 0.0;
let mut nominal_width_x = 0.0;
if let Some(pe) = private_entity {
if let Some(name_id) = ctx.names.find(b"defaultWidthX")
&& let Some(obj) = ctx.dicts.get(pe, &DictKey::Name(name_id))
&& let Some(v) = obj.as_f64()
{
default_width_x = v;
}
if let Some(name_id) = ctx.names.find(b"nominalWidthX")
&& let Some(obj) = ctx.dicts.get(pe, &DictKey::Name(name_id))
&& let Some(v) = obj.as_f64()
{
nominal_width_x = v;
}
}
let mut local_subrs: Vec<Vec<u8>> = Vec::new();
if let Some(pe) = private_entity
&& let Some(name_id) = ctx.names.find(b"Subrs")
&& let Some(obj) = ctx.dicts.get(pe, &DictKey::Name(name_id))
&& let PsValue::Array { entity, start, len } = obj.value
{
let elems = ctx.arrays.get(entity, start, len);
local_subrs = elems
.iter()
.map(|o| match o.value {
PsValue::String { entity, start, len } => {
ctx.strings.get(entity, start, len).to_vec()
}
_ => Vec::new(),
})
.collect();
}
let mut global_subrs: Vec<Vec<u8>> = Vec::new();
if let Some(name_id) = ctx.names.find(b"_cff_global_subrs")
&& let Some(obj) = ctx.dicts.get(font_entity, &DictKey::Name(name_id))
&& let PsValue::Array { entity, start, len } = obj.value
{
let elems = ctx.arrays.get(entity, start, len);
global_subrs = elems
.iter()
.map(|o| match o.value {
PsValue::String { entity, start, len } => {
ctx.strings.get(entity, start, len).to_vec()
}
_ => Vec::new(),
})
.collect();
}
let mut widths: HashMap<u16, i32> = HashMap::new();
if let (Some(enc_entity), Some(cs_entity)) = (encoding_entity, charstrings_entity) {
for &code in &usage.used_codes {
if code > 255 {
continue;
}
let glyph_name_obj = ctx.arrays.get_element(enc_entity, code as u32);
let glyph_name_id = match glyph_name_obj.value {
PsValue::Name(id) => id,
_ => continue,
};
let cs_obj = match ctx.dicts.get(cs_entity, &DictKey::Name(glyph_name_id)) {
Some(obj) => obj,
None => continue,
};
let (cs_ent, cs_start, cs_len) = match cs_obj.value {
PsValue::String { entity, start, len } => (entity, start, len),
_ => continue,
};
let cs_bytes = ctx.strings.get(cs_ent, cs_start, cs_len).to_vec();
if let Ok(result) = type2_charstring::execute_type2_charstring(
&cs_bytes,
&local_subrs,
&global_subrs,
default_width_x,
nominal_width_x,
false,
) {
widths.insert(code, result.width_x as i32);
}
}
}
widths
}
fn extract_cid_widths(usage: &FontUsage, ctx: &Context) -> HashMap<u16, i32> {
let font_entity = usage.font_entity;
let cid_entity = if usage.font_type == 0 {
get_cidfont_descendant(ctx, font_entity).unwrap_or(font_entity)
} else {
font_entity
};
let font_data = match concatenate_sfnts(ctx, cid_entity) {
Some(d) => d,
None => return HashMap::new(),
};
if font_data.len() < 20 {
return HashMap::new();
}
let upm = truetype::get_units_per_em(&font_data);
let scale = 1000.0 / upm as f64;
let mut cid_to_gid: HashMap<u16, u16> = HashMap::new();
if usage.font_type == 0 {
cid_to_gid = build_cid_to_gid_from_cmap(&font_data);
}
if usage.font_type == 42 {
let encoding_entity = ctx
.dicts
.get(font_entity, &DictKey::Name(ctx.name_cache.n_encoding))
.and_then(|obj| match obj.value {
PsValue::Array { entity, .. } => Some(entity),
_ => None,
});
let charstrings_entity = ctx
.dicts
.get(font_entity, &DictKey::Name(ctx.name_cache.n_char_strings))
.and_then(|obj| match obj.value {
PsValue::Dict(e) => Some(e),
_ => None,
});
if let (Some(enc_entity), Some(cs_entity)) = (encoding_entity, charstrings_entity) {
for &code in &usage.used_codes {
if code > 255 {
continue;
}
let glyph_name_obj = ctx.arrays.get_element(enc_entity, code as u32);
let glyph_name_id = match glyph_name_obj.value {
PsValue::Name(id) => id,
_ => continue,
};
let cs_obj = match ctx.dicts.get(cs_entity, &DictKey::Name(glyph_name_id)) {
Some(obj) => obj,
None => continue,
};
if let Some(gid) = cs_obj.as_i32() {
cid_to_gid.insert(code, gid as u16);
}
}
}
}
let mut widths: HashMap<u16, i32> = HashMap::new();
for &code in &usage.used_codes {
let gid = cid_to_gid.get(&code).copied().unwrap_or(code);
if let Some(aw) = truetype::get_advance_width(&font_data, gid) {
widths.insert(code, (aw as f64 * scale).round() as i32);
}
}
widths
}