use std::collections::HashMap;
pub fn generate_type0_font_dict(
base_font_name: &str,
cid_font_obj_id: usize,
to_unicode_obj_id: usize,
) -> String {
format!(
"<<\n\
/Type /Font\n\
/Subtype /Type0\n\
/BaseFont /{}-Identity-H\n\
/Encoding /Identity-H\n\
/DescendantFonts [{} 0 R]\n\
/ToUnicode {} 0 R\n\
>>",
base_font_name, cid_font_obj_id, to_unicode_obj_id
)
}
pub fn generate_cidfont_dict(
base_font_name: &str,
descriptor_obj_id: usize,
widths: &[u16],
default_width: u16,
) -> String {
format!(
"<<\n\
/Type /Font\n\
/Subtype /CIDFontType2\n\
/BaseFont /{}\n\
/CIDSystemInfo <<\n\
/Registry (Adobe)\n\
/Ordering (Identity)\n\
/Supplement 0\n\
>>\n\
/FontDescriptor {} 0 R\n\
/DW {}\n\
/W [0 [{} ]]\n\
>>",
base_font_name,
descriptor_obj_id,
default_width,
widths
.iter()
.map(|w| w.to_string())
.collect::<Vec<_>>()
.join(" ")
)
}
pub fn generate_cidfont_tounicode_cmap(char_map: &HashMap<u16, char>) -> String {
let mut cmap = String::from(
"/CIDInit /ProcSet findresource begin\n\
12 dict begin\n\
begincmap\n\
/CIDSystemInfo <<\n\
/Registry (Adobe)\n\
/Ordering (UCS)\n\
/Supplement 0\n\
>> def\n\
/CMapName /Adobe-Identity-UCS def\n\
/CMapType 2 def\n\
1 begincodespacerange\n\
<0000> <FFFF>\n\
endcodespacerange\n",
);
if !char_map.is_empty() {
cmap.push_str(&format!("{} beginbfchar\n", char_map.len()));
for (&glyph_id, &ch) in char_map.iter() {
cmap.push_str(&format!("<{:04X}> <{:04X}>\n", glyph_id, ch as u32));
}
cmap.push_str("endbfchar\n");
}
cmap.push_str(
"endcmap\n\
CMapName currentdict /CMap defineresource pop\n\
end\n\
end\n",
);
cmap
}
pub fn encode_text_utf16be(text: &str) -> String {
let mut result = String::from("<FEFF");
for ch in text.chars() {
let code = ch as u32;
if code <= 0xFFFF {
result.push_str(&format!("{:04X}", code));
} else {
let code = code - 0x10000;
let high = 0xD800 + (code >> 10);
let low = 0xDC00 + (code & 0x3FF);
result.push_str(&format!("{:04X}{:04X}", high, low));
}
}
result.push('>');
result
}
pub fn generate_cidtogidmap_stream(
char_to_glyph: &std::collections::HashMap<char, u16>,
used_chars: &std::collections::BTreeSet<char>,
) -> Vec<u8> {
let max_cid = used_chars.iter().map(|&c| c as u32).max().unwrap_or(0);
let mut map = vec![0u8; ((max_cid + 1) * 2) as usize];
for &ch in used_chars.iter() {
let cid = ch as u32;
if let Some(&gid) = char_to_glyph.get(&ch) {
let offset = (cid * 2) as usize;
map[offset] = (gid >> 8) as u8;
map[offset + 1] = (gid & 0xFF) as u8;
}
}
map
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_text_utf16be_ascii() {
let encoded = encode_text_utf16be("Hello");
assert!(encoded.starts_with("<FEFF"));
assert!(encoded.ends_with('>'));
assert!(encoded.contains("0048"));
assert!(encoded.contains("0065"));
}
#[test]
fn test_encode_text_utf16be_japanese() {
let encoded = encode_text_utf16be("請求書");
assert!(encoded.starts_with("<FEFF"));
assert!(encoded.contains("8ACB"));
assert!(encoded.contains("6C42"));
assert!(encoded.contains("66F8"));
}
#[test]
fn test_encode_text_utf16be_mixed() {
let encoded = encode_text_utf16be("Hello世界");
assert!(encoded.starts_with("<FEFF"));
assert!(encoded.contains("0048")); assert!(encoded.contains("4E16")); assert!(encoded.contains("754C")); }
#[test]
fn test_tounicode_cmap_generation() {
let mut char_map = HashMap::new();
char_map.insert(100, 'A');
char_map.insert(200, '請');
let cmap = generate_cidfont_tounicode_cmap(&char_map);
assert!(cmap.contains("begincmap"));
assert!(cmap.contains("endbfchar"));
assert!(cmap.contains("<0064> <0041>")); assert!(cmap.contains("<00C8> <8ACB>")); }
}
#[cfg(test)]
mod tests_extended {
use super::*;
#[test]
fn test_type0_font_dict_structure() {
let dict = generate_type0_font_dict("NotoSans", 5, 6);
assert!(dict.contains("/Type /Font"));
assert!(dict.contains("/Subtype /Type0"));
assert!(dict.contains("/Encoding /Identity-H"));
assert!(dict.contains("NotoSans"));
assert!(dict.contains("5 0 R")); assert!(dict.contains("6 0 R")); }
#[test]
fn test_type0_font_dict_base_font_name_format() {
let dict = generate_type0_font_dict("MyFont", 10, 11);
assert!(dict.contains("MyFont-Identity-H"));
}
#[test]
fn test_cidfont_dict_structure() {
let widths = vec![500u16; 10];
let dict = generate_cidfont_dict("NotoSans", 3, &widths, 500);
assert!(dict.contains("/Type /Font"));
assert!(dict.contains("/Subtype /CIDFontType2"));
assert!(dict.contains("/Registry (Adobe)"));
assert!(dict.contains("/Ordering (Identity)"));
assert!(dict.contains("NotoSans"));
}
#[test]
fn test_cidfont_dict_contains_descriptor_ref() {
let widths = vec![600u16; 5];
let dict = generate_cidfont_dict("TestFont", 42, &widths, 600);
assert!(dict.contains("42 0 R"));
}
#[test]
fn test_cidfont_dict_default_width() {
let widths: Vec<u16> = vec![];
let dict = generate_cidfont_dict("Font", 1, &widths, 1000);
assert!(dict.contains("/DW 1000"));
}
#[test]
fn test_tounicode_cmap_empty_map() {
let char_map = HashMap::new();
let cmap = generate_cidfont_tounicode_cmap(&char_map);
assert!(cmap.contains("begincmap"));
assert!(cmap.contains("endcmap"));
}
#[test]
fn test_encode_text_utf16be_empty() {
let encoded = encode_text_utf16be("");
assert!(encoded.starts_with("<FEFF"));
assert!(encoded.ends_with('>'));
}
#[test]
fn test_generate_cidtogidmap_stream_empty() {
use std::collections::{BTreeSet, HashMap};
let char_to_glyph: HashMap<char, u16> = HashMap::new();
let used_chars: BTreeSet<char> = BTreeSet::new();
let map = generate_cidtogidmap_stream(&char_to_glyph, &used_chars);
assert_eq!(map, vec![0u8; 2]);
}
#[test]
fn test_generate_cidtogidmap_stream_single_char() {
use std::collections::{BTreeSet, HashMap};
let mut char_to_glyph: HashMap<char, u16> = HashMap::new();
char_to_glyph.insert('A', 36);
let mut used_chars: BTreeSet<char> = BTreeSet::new();
used_chars.insert('A');
let map = generate_cidtogidmap_stream(&char_to_glyph, &used_chars);
assert_eq!(map.len(), 132);
assert_eq!(map[130], 0x00);
assert_eq!(map[131], 0x24);
}
}