use std::collections::HashMap;
use std::path::Path;
use crate::cff_parser;
use crate::context::Context;
use crate::dict::DictKey;
use crate::object::{ObjFlags, PsObject, PsValue};
use crate::system_fonts::{
extract_ps_name_from_name_table, parse_cmap_table, parse_post_table, unicode_to_glyph_name,
};
use crate::truetype::{find_table, get_units_per_em, read_i16};
pub fn load_otf_cff(ctx: &mut Context, path: &Path) -> bool {
let data = match std::fs::read(path) {
Ok(d) => d,
Err(_) => return false,
};
let (cff_offset, cff_length) = match find_table(&data, b"CFF ") {
Some(t) => t,
None => return false,
};
if cff_offset + cff_length > data.len() {
return false;
}
let cff_data = &data[cff_offset..cff_offset + cff_length];
let cff_fonts = match cff_parser::parse_cff(cff_data) {
Ok(f) => f,
Err(_) => return false,
};
let saved_vm_mode = ctx.vm_alloc_mode;
ctx.vm_alloc_mode = true;
let sl = ctx.save_stack.current_level();
let cs = ctx.save_stack.last_save_id();
let cff_str_entity = ctx.strings.allocate_from_with(cff_data, sl, true, cs);
let cff_data_len = cff_data.len() as u32;
let mut success = true;
for cff_font in &cff_fonts {
if register_cff_font(ctx, cff_font, cff_str_entity, cff_data_len).is_err() {
success = false;
break;
}
}
ctx.vm_alloc_mode = saved_vm_mode;
success
}
pub fn load_ttf(ctx: &mut Context, path: &Path) -> bool {
let data = match std::fs::read(path) {
Ok(d) => d,
Err(_) => return false,
};
load_ttf_from_data(ctx, &data)
}
pub fn load_ttf_from_data(ctx: &mut Context, data: &[u8]) -> bool {
if data.len() < 12 {
return false;
}
let font_name = match extract_ps_name_from_name_table(data) {
Some(n) => n,
None => return false,
};
let units_per_em = get_units_per_em(data);
let em_scale = 1.0 / units_per_em as f64;
let (x_min, y_min, x_max, y_max) = get_font_bbox(data);
let bbox = [
x_min as f64 * em_scale,
y_min as f64 * em_scale,
x_max as f64 * em_scale,
y_max as f64 * em_scale,
];
let unicode_to_gid = match parse_cmap_table(data) {
Some(m) => m,
None => return false,
};
let gid_to_name = parse_post_table(data);
let resolve_glyph_name = |gid: u16, unicode: Option<u32>| -> String {
if let Some(ref post_map) = gid_to_name
&& let Some(name) = post_map.get(&gid)
{
return name.clone();
}
if let Some(cp) = unicode {
if let Some(name) = unicode_to_glyph_name(cp) {
return name.to_string();
}
return format!("uni{:04X}", cp);
}
format!("glyph{}", gid)
};
let mut encoding = vec![".notdef".to_string(); 256];
let mut charstrings: HashMap<String, u16> = HashMap::new();
charstrings.insert(".notdef".to_string(), 0);
for code in 0u32..256 {
if let Some(&gid) = unicode_to_gid.get(&code) {
let name = resolve_glyph_name(gid, Some(code));
encoding[code as usize] = name.clone();
charstrings.insert(name, gid);
}
}
for (&unicode, &gid) in &unicode_to_gid {
if unicode >= 256 {
let name = resolve_glyph_name(gid, Some(unicode));
charstrings.entry(name).or_insert(gid);
}
}
let saved_vm_mode = ctx.vm_alloc_mode;
ctx.vm_alloc_mode = true;
let success = build_type42_font(ctx, &font_name, &bbox, &encoding, &charstrings, data);
ctx.vm_alloc_mode = saved_vm_mode;
success
}
fn build_type42_font(
ctx: &mut Context,
font_name: &str,
bbox: &[f64; 4],
encoding: &[String],
charstrings: &HashMap<String, u16>,
font_data: &[u8],
) -> bool {
let sl = ctx.save_stack.current_level();
let cs = ctx.save_stack.last_save_id();
let font_entity = ctx.dicts.allocate_with(16, b"Type42", sl, true, cs);
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_font_type),
PsObject::int(42),
);
let font_name_id = ctx.names.intern(font_name.as_bytes());
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_font_name),
PsObject::name_lit(font_name_id),
);
let fm_items = [
PsObject::real(1.0),
PsObject::real(0.0),
PsObject::real(0.0),
PsObject::real(1.0),
PsObject::real(0.0),
PsObject::real(0.0),
];
let fm_entity = ctx.arrays.allocate_from_with(&fm_items, sl, true, cs);
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_font_matrix),
PsObject::array(fm_entity, 6),
);
let bb_items = [
PsObject::real(bbox[0]),
PsObject::real(bbox[1]),
PsObject::real(bbox[2]),
PsObject::real(bbox[3]),
];
let bb_entity = ctx.arrays.allocate_from_with(&bb_items, sl, true, cs);
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_font_bbox),
PsObject::array(bb_entity, 4),
);
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_paint_type),
PsObject::int(0),
);
let enc_entity = ctx.arrays.allocate_with(256, sl, true, cs);
for (i, name) in encoding.iter().enumerate() {
let name_id = ctx.names.intern(name.as_bytes());
ctx.arrays
.set_element(enc_entity, i as u32, PsObject::name_lit(name_id));
}
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_encoding),
PsObject::array(enc_entity, 256),
);
let cs_entity =
ctx.dicts
.allocate_with(charstrings.len().max(10), b"CharStrings", sl, true, cs);
for (name, &gid) in charstrings {
let name_id = ctx.names.intern(name.as_bytes());
ctx.dicts
.put(cs_entity, DictKey::Name(name_id), PsObject::int(gid as i32));
}
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_char_strings),
PsObject::dict(cs_entity),
);
let sfnts_entity = ctx.arrays.allocate_with(1, sl, true, cs);
let str_entity = ctx.strings.allocate_from_with(font_data, sl, true, cs);
ctx.arrays.set_element(
sfnts_entity,
0,
PsObject::string(str_entity, font_data.len() as u32),
);
let sfnts_key = DictKey::Name(ctx.names.intern(b"sfnts"));
ctx.dicts
.put(font_entity, sfnts_key, PsObject::array(sfnts_entity, 1));
let upem_key = DictKey::Name(ctx.names.intern(b"_unitsPerEm"));
let upem = get_units_per_em(font_data);
ctx.dicts
.put(font_entity, upem_key, PsObject::int(upem as i32));
let fid = ctx.next_fid;
ctx.next_fid += 1;
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_fid),
PsObject {
value: PsValue::FontID(fid),
flags: ObjFlags::literal(),
},
);
let font_dict_obj = PsObject::dict(font_entity);
let definefont_id = ctx.names.intern(b"definefont");
let key = DictKey::Name(definefont_id);
if let Some(op_obj) = ctx.dict_load(&key)
&& let PsValue::Operator(opcode) = op_obj.value
{
if ctx.o_stack.push(PsObject::name_lit(font_name_id)).is_err() {
return false;
}
if ctx.o_stack.push(font_dict_obj).is_err() {
return false;
}
let func = ctx.operators[opcode.0 as usize].func;
if func(ctx).is_err() {
return false;
}
let _ = ctx.o_stack.pop();
} else {
ctx.dicts.put(
ctx.font_directory,
DictKey::Name(font_name_id),
font_dict_obj,
);
}
true
}
fn get_font_bbox(font_data: &[u8]) -> (i16, i16, i16, i16) {
if let Some((head_off, _)) = find_table(font_data, b"head")
&& head_off + 54 <= font_data.len()
{
let x_min = read_i16(font_data, head_off + 36);
let y_min = read_i16(font_data, head_off + 38);
let x_max = read_i16(font_data, head_off + 40);
let y_max = read_i16(font_data, head_off + 42);
return (x_min, y_min, x_max, y_max);
}
(0, 0, 1000, 1000)
}
fn register_cff_font(
ctx: &mut Context,
cff_font: &cff_parser::CffFont,
cff_str_entity: crate::object::EntityId,
cff_data_len: u32,
) -> Result<(), crate::error::PsError> {
let sl = ctx.save_stack.current_level();
let cs = ctx.save_stack.last_save_id();
let font_entity = ctx.dicts.allocate_with(16, b"CFF", sl, true, cs);
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_font_type),
PsObject::int(2),
);
let font_name_id = ctx.names.intern(cff_font.name.as_bytes());
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_font_name),
PsObject::name_lit(font_name_id),
);
let fm_entity = ctx.arrays.allocate_with(6, sl, true, cs);
for (i, &val) in cff_font.font_matrix.iter().enumerate() {
ctx.arrays
.set_element(fm_entity, i as u32, PsObject::real(val));
}
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_font_matrix),
PsObject::array(fm_entity, 6),
);
let bbox_entity = ctx.arrays.allocate_with(4, sl, true, cs);
for (i, &val) in cff_font.font_bbox.iter().enumerate() {
ctx.arrays
.set_element(bbox_entity, i as u32, PsObject::real(val));
}
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_font_bbox),
PsObject::array(bbox_entity, 4),
);
let enc_entity = ctx.arrays.allocate_with(256, sl, true, cs);
for code in 0..256u32 {
let gid = cff_font.encoding[code as usize] as usize;
let glyph_name = if gid < cff_font.charset.len() {
&cff_font.charset[gid]
} else {
".notdef"
};
let name_id = ctx.names.intern(glyph_name.as_bytes());
ctx.arrays
.set_element(enc_entity, code, PsObject::name_lit(name_id));
}
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_encoding),
PsObject::array(enc_entity, 256),
);
let cs_entity = ctx.dicts.allocate_with(16, b"CFF", sl, true, cs);
for (gid, cs_data) in cff_font.char_strings.iter().enumerate() {
let str_entity = ctx.strings.allocate_from_with(cs_data, sl, true, cs);
let cs_obj = PsObject::string(str_entity, cs_data.len() as u32);
if gid < cff_font.charset.len() {
let glyph_name = &cff_font.charset[gid];
let name_id = ctx.names.intern(glyph_name.as_bytes());
ctx.dicts.put(cs_entity, DictKey::Name(name_id), cs_obj);
}
if cff_font.is_cid {
ctx.dicts.put(cs_entity, DictKey::Int(gid as i64), cs_obj);
}
}
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_char_strings),
PsObject::dict(cs_entity),
);
let priv_entity = ctx.dicts.allocate_with(16, b"CFF", sl, true, cs);
ctx.dicts.put(
priv_entity,
DictKey::Name(ctx.names.intern(b"defaultWidthX")),
PsObject::real(cff_font.default_width_x),
);
ctx.dicts.put(
priv_entity,
DictKey::Name(ctx.names.intern(b"nominalWidthX")),
PsObject::real(cff_font.nominal_width_x),
);
if !cff_font.local_subrs.is_empty() {
let subrs_len = cff_font.local_subrs.len() as u32;
let subrs_entity = ctx.arrays.allocate_with(subrs_len as usize, sl, true, cs);
for (i, subr_data) in cff_font.local_subrs.iter().enumerate() {
let str_entity = ctx.strings.allocate_from_with(subr_data, sl, true, cs);
ctx.arrays.set_element(
subrs_entity,
i as u32,
PsObject::string(str_entity, subr_data.len() as u32),
);
}
ctx.dicts.put(
priv_entity,
DictKey::Name(ctx.name_cache.n_subrs),
PsObject {
value: PsValue::Array {
entity: subrs_entity,
start: 0,
len: subrs_len,
},
flags: ObjFlags::literal_composite(),
},
);
}
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_private),
PsObject::dict(priv_entity),
);
if !cff_font.global_subrs.is_empty() {
let gs_key = DictKey::Name(ctx.names.intern(b"_cff_global_subrs"));
let gs_len = cff_font.global_subrs.len() as u32;
let gs_entity = ctx.arrays.allocate_with(gs_len as usize, sl, true, cs);
for (i, subr_data) in cff_font.global_subrs.iter().enumerate() {
let str_entity = ctx.strings.allocate_from_with(subr_data, sl, true, cs);
ctx.arrays.set_element(
gs_entity,
i as u32,
PsObject::string(str_entity, subr_data.len() as u32),
);
}
ctx.dicts.put(
font_entity,
gs_key,
PsObject {
value: PsValue::Array {
entity: gs_entity,
start: 0,
len: gs_len,
},
flags: ObjFlags::literal_composite(),
},
);
}
let cff_data_key = DictKey::Name(ctx.names.intern(b"_CFFData"));
ctx.dicts.put(
font_entity,
cff_data_key,
PsObject::string(cff_str_entity, cff_data_len),
);
let fid = ctx.next_fid;
ctx.next_fid += 1;
ctx.dicts.put(
font_entity,
DictKey::Name(ctx.name_cache.n_fid),
PsObject {
value: PsValue::FontID(fid),
flags: ObjFlags::literal(),
},
);
let font_dict_obj = PsObject::dict(font_entity);
let definefont_id = ctx.names.intern(b"definefont");
let key = DictKey::Name(definefont_id);
if let Some(op_obj) = ctx.dict_load(&key)
&& let PsValue::Operator(opcode) = op_obj.value
{
ctx.o_stack.push(PsObject::name_lit(font_name_id))?;
ctx.o_stack.push(font_dict_obj)?;
let func = ctx.operators[opcode.0 as usize].func;
func(ctx)?;
let _ = ctx.o_stack.pop();
} else {
ctx.dicts.put(
ctx.font_directory,
DictKey::Name(font_name_id),
font_dict_obj,
);
}
Ok(())
}
pub fn is_cff_font(data: &[u8]) -> bool {
data.len() >= 4 && &data[0..4] == b"OTTO"
}
pub fn load_binary_font(ctx: &mut Context, path: &Path) -> bool {
let data = match std::fs::read(path) {
Ok(d) => d,
Err(_) => return false,
};
if data.len() < 4 {
return false;
}
if is_cff_font(&data) {
load_otf_cff(ctx, path)
} else {
load_ttf_from_data(ctx, &data)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_cff_font() {
assert!(is_cff_font(b"OTTO\x00\x00\x00\x00"));
assert!(!is_cff_font(b"\x00\x01\x00\x00"));
assert!(!is_cff_font(b"tru"));
}
#[test]
fn test_load_system_ttf() {
let test_fonts = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf",
];
for path_str in &test_fonts {
let path = Path::new(path_str);
if path.exists() {
let mut ctx = Context::new();
let ok = load_ttf(&mut ctx, path);
assert!(ok, "Failed to load {}", path_str);
let name = extract_ps_name_from_name_table(&std::fs::read(path).unwrap());
if let Some(font_name) = name {
let name_id = ctx.names.intern(font_name.as_bytes());
let key = DictKey::Name(name_id);
assert!(
ctx.dicts.get(ctx.font_directory, &key).is_some(),
"Font '{}' not in FontDirectory",
font_name
);
}
return;
}
}
eprintln!("Skipping TTF loading test — no test font found");
}
#[test]
fn test_load_system_otf() {
let test_fonts = ["/usr/share/fonts/opentype/urw-base35/C059-Italic.otf"];
for path_str in &test_fonts {
let path = Path::new(path_str);
if path.exists() {
let mut ctx = Context::new();
let ok = load_otf_cff(&mut ctx, path);
assert!(ok, "Failed to load CFF from {}", path_str);
return;
}
}
eprintln!("Skipping OTF loading test — no test font found");
}
}