use std::collections::BTreeMap;
use super::common;
use crate::core::objects::{Dictionary, Object, PdfName, PdfStream};
use crate::error::PdfResult;
pub struct CidFont {
data: Vec<u8>,
ps_name: String,
units_per_em: u16,
ascent: f32,
descent: f32,
cap_height: f32,
bbox: [f32; 4],
}
impl CidFont {
pub fn from_ttf(data: &[u8]) -> PdfResult<Self> {
let m = common::parse_font_metrics(data)?;
Ok(Self {
data: data.to_vec(),
ps_name: m.ps_name,
units_per_em: m.units_per_em,
ascent: m.ascent,
descent: m.descent,
cap_height: m.cap_height,
bbox: m.bbox,
})
}
pub fn ps_name(&self) -> &str {
&self.ps_name
}
pub fn subset(&self, chars: &[char]) -> PdfResult<SubsetCidFont> {
let result = common::collect_glyphs_and_subset(&self.data, chars)?;
Ok(SubsetCidFont {
data: result.data,
ps_name: format!("{}+CIDSubset", self.ps_name),
units_per_em: self.units_per_em,
ascent: self.ascent,
descent: self.descent,
cap_height: self.cap_height,
bbox: self.bbox,
char_to_gid: result.char_to_gid,
old_to_new: result.old_to_new,
widths: result.widths,
})
}
}
pub struct SubsetCidFont {
data: Vec<u8>,
ps_name: String,
units_per_em: u16,
ascent: f32,
descent: f32,
cap_height: f32,
bbox: [f32; 4],
char_to_gid: BTreeMap<char, u16>,
old_to_new: BTreeMap<u16, u16>,
widths: BTreeMap<u16, u16>,
}
impl SubsetCidFont {
pub fn encode_text(&self, text: &str) -> Vec<u8> {
let mut result = Vec::with_capacity(text.len() * 2);
for ch in text.chars() {
if let Some(&old_gid) = self.char_to_gid.get(&ch) {
if let Some(&new_gid) = self.old_to_new.get(&old_gid) {
result.extend_from_slice(&new_gid.to_be_bytes());
}
}
}
result
}
pub fn to_type0_dictionary(
&self,
descendant_ref: Object,
to_unicode_ref: Object,
) -> Dictionary {
let mut dict = Dictionary::new();
dict.insert(PdfName::new("Type"), Object::Name(PdfName::new("Font")));
dict.insert(PdfName::new("Subtype"), Object::Name(PdfName::new("Type0")));
dict.insert(
PdfName::new("BaseFont"),
Object::Name(PdfName::new(&self.ps_name)),
);
dict.insert(
PdfName::new("Encoding"),
Object::Name(PdfName::new("Identity-H")),
);
dict.insert(
PdfName::new("DescendantFonts"),
Object::Array(vec![descendant_ref]),
);
dict.insert(PdfName::new("ToUnicode"), to_unicode_ref);
dict
}
pub fn to_cidfont_dictionary(&self, descriptor_ref: Object) -> Dictionary {
let mut dict = Dictionary::new();
dict.insert(PdfName::new("Type"), Object::Name(PdfName::new("Font")));
dict.insert(
PdfName::new("Subtype"),
Object::Name(PdfName::new("CIDFontType2")),
);
dict.insert(
PdfName::new("BaseFont"),
Object::Name(PdfName::new(&self.ps_name)),
);
let mut sys_info = Dictionary::new();
sys_info.insert(
PdfName::new("Registry"),
Object::String(crate::core::objects::PdfString::from_literal("Adobe")),
);
sys_info.insert(
PdfName::new("Ordering"),
Object::String(crate::core::objects::PdfString::from_literal("Identity")),
);
sys_info.insert(PdfName::new("Supplement"), Object::Integer(0));
dict.insert(PdfName::new("CIDSystemInfo"), Object::Dictionary(sys_info));
let scale = 1000.0 / self.units_per_em as f64;
let mut w_array: Vec<Object> = Vec::new();
for (&old_gid, &new_gid) in &self.old_to_new {
let w = self.widths.get(&old_gid).copied().unwrap_or(0);
let scaled = (w as f64 * scale).round() as i64;
w_array.push(Object::Integer(new_gid as i64));
w_array.push(Object::Array(vec![Object::Integer(scaled)]));
}
if !w_array.is_empty() {
dict.insert(PdfName::new("W"), Object::Array(w_array));
}
let default_w = self
.widths
.get(&0)
.map(|&w| (w as f64 * scale).round() as i64)
.unwrap_or(1000);
dict.insert(PdfName::new("DW"), Object::Integer(default_w));
dict.insert(PdfName::new("FontDescriptor"), descriptor_ref);
dict.insert(
PdfName::new("CIDToGIDMap"),
Object::Name(PdfName::new("Identity")),
);
dict
}
pub fn to_font_descriptor(&self, font_file_ref: Object) -> Dictionary {
common::build_font_descriptor(common::FontDescriptorParams {
ps_name: &self.ps_name,
units_per_em: self.units_per_em,
ascent: self.ascent,
descent: self.descent,
cap_height: self.cap_height,
bbox: self.bbox,
flags: 4, font_file_key: "FontFile2",
font_file_ref,
})
}
pub fn to_font_stream(&self) -> PdfResult<PdfStream> {
common::build_font_stream(&self.data)
}
pub fn to_unicode_cmap(&self) -> PdfResult<PdfStream> {
common::build_to_unicode_cmap(&self.char_to_gid, &self.old_to_new, 2)
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn glyph_count(&self) -> usize {
self.old_to_new.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_font_data() -> Option<Vec<u8>> {
let paths = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/TTF/DejaVuSans.ttf",
"/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf",
];
for path in &paths {
if let Ok(data) = std::fs::read(path) {
return Some(data);
}
}
None
}
#[test]
fn cidfont_from_ttf() {
let data = match test_font_data() {
Some(d) => d,
None => return,
};
let font = CidFont::from_ttf(&data).unwrap();
assert!(!font.ps_name().is_empty());
}
#[test]
fn cidfont_dictionary() {
let data = match test_font_data() {
Some(d) => d,
None => return,
};
let font = CidFont::from_ttf(&data).unwrap();
let subset = font.subset(&['A', 'B', 'C']).unwrap();
let type0 = subset.to_type0_dictionary(Object::Null, Object::Null);
assert_eq!(
type0
.get(&PdfName::new("Subtype"))
.and_then(|o| o.as_name()),
Some("Type0")
);
assert_eq!(
type0
.get(&PdfName::new("Encoding"))
.and_then(|o| o.as_name()),
Some("Identity-H")
);
assert!(type0.get(&PdfName::new("DescendantFonts")).is_some());
}
#[test]
fn cidfont_cid_widths() {
let data = match test_font_data() {
Some(d) => d,
None => return,
};
let font = CidFont::from_ttf(&data).unwrap();
let subset = font.subset(&['A', 'B']).unwrap();
let cid_dict = subset.to_cidfont_dictionary(Object::Null);
assert_eq!(
cid_dict
.get(&PdfName::new("Subtype"))
.and_then(|o| o.as_name()),
Some("CIDFontType2")
);
assert!(cid_dict.get(&PdfName::new("W")).is_some());
assert!(cid_dict.get(&PdfName::new("DW")).is_some());
}
#[test]
fn cidfont_to_unicode() {
let data = match test_font_data() {
Some(d) => d,
None => return,
};
let font = CidFont::from_ttf(&data).unwrap();
let subset = font.subset(&['H', 'e', 'l', 'o']).unwrap();
let cmap_stream = subset.to_unicode_cmap().unwrap();
let cmap_data = cmap_stream.decode_data().unwrap();
let cmap_str = String::from_utf8(cmap_data).unwrap();
assert!(cmap_str.contains("<0000> <FFFF>"));
assert!(cmap_str.contains("beginbfchar"));
}
#[test]
fn cidfont_encode_text() {
let data = match test_font_data() {
Some(d) => d,
None => return,
};
let font = CidFont::from_ttf(&data).unwrap();
let subset = font.subset(&['H', 'i']).unwrap();
let encoded = subset.encode_text("Hi");
assert_eq!(encoded.len(), 4);
let gid1 = u16::from_be_bytes([encoded[0], encoded[1]]);
let gid2 = u16::from_be_bytes([encoded[2], encoded[3]]);
assert_ne!(gid1, gid2); }
#[test]
fn cidfont_subset() {
let data = match test_font_data() {
Some(d) => d,
None => return,
};
let font = CidFont::from_ttf(&data).unwrap();
let subset = font.subset(&['A', 'B', 'C']).unwrap();
assert!(subset.data().len() < data.len());
assert!(subset.glyph_count() > 0);
}
#[test]
fn cidfont_font_descriptor() {
let data = match test_font_data() {
Some(d) => d,
None => return,
};
let font = CidFont::from_ttf(&data).unwrap();
let subset = font.subset(&['A']).unwrap();
let desc = subset.to_font_descriptor(Object::Null);
assert_eq!(
desc.get(&PdfName::new("Type")).and_then(|o| o.as_name()),
Some("FontDescriptor")
);
assert_eq!(
desc.get(&PdfName::new("Flags")).and_then(|o| o.as_i64()),
Some(4)
);
}
}