use crate::ffi::output::OUTPUTS;
use crate::ffi::pdf_object::types::{PDF_OBJECTS, PdfObj, PdfObjType};
use crate::ffi::{Handle, HandleStore};
use std::collections::HashMap;
use std::ffi::{CStr, CString, c_char};
use std::ptr;
use std::sync::LazyLock;
type ContextHandle = Handle;
type DocumentHandle = Handle;
type PdfObjHandle = Handle;
type FontHandle = Handle;
type CMapHandle = Handle;
type BufferHandle = Handle;
type DeviceHandle = Handle;
type OutputHandle = Handle;
pub const PDF_FD_FIXED_PITCH: i32 = 1 << 0;
pub const PDF_FD_SERIF: i32 = 1 << 1;
pub const PDF_FD_SYMBOLIC: i32 = 1 << 2;
pub const PDF_FD_SCRIPT: i32 = 1 << 3;
pub const PDF_FD_NONSYMBOLIC: i32 = 1 << 5;
pub const PDF_FD_ITALIC: i32 = 1 << 6;
pub const PDF_FD_ALL_CAP: i32 = 1 << 16;
pub const PDF_FD_SMALL_CAP: i32 = 1 << 17;
pub const PDF_FD_FORCE_BOLD: i32 = 1 << 18;
pub const PDF_ENCODING_STANDARD: i32 = 0;
pub const PDF_ENCODING_MAC_ROMAN: i32 = 1;
pub const PDF_ENCODING_WIN_ANSI: i32 = 2;
pub const PDF_ENCODING_MAC_EXPERT: i32 = 3;
pub const PDF_ENCODING_SYMBOL: i32 = 4;
pub const PDF_ENCODING_ZAPF_DINGBATS: i32 = 5;
pub const PDF_CJK_CNS1: i32 = 0;
pub const PDF_CJK_GB1: i32 = 1;
pub const PDF_CJK_JAPAN1: i32 = 2;
pub const PDF_CJK_KOREA1: i32 = 3;
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct HorizontalMetrics {
pub lo: u16,
pub hi: u16,
pub w: i32,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct VerticalMetrics {
pub lo: u16,
pub hi: u16,
pub x: i16,
pub y: i16,
pub w: i16,
}
#[derive(Debug, Clone)]
pub struct FontDesc {
pub refs: i32,
pub size: usize,
pub font: Option<FontHandle>,
pub name: String,
pub flags: i32,
pub italic_angle: f32,
pub ascent: f32,
pub descent: f32,
pub cap_height: f32,
pub x_height: f32,
pub missing_width: f32,
pub encoding: Option<CMapHandle>,
pub to_ttf_cmap: Option<CMapHandle>,
pub cid_to_gid: Vec<u16>,
pub to_unicode: Option<CMapHandle>,
pub cid_to_ucs: Vec<u16>,
pub wmode: i32,
pub default_hmtx: HorizontalMetrics,
pub hmtx: Vec<HorizontalMetrics>,
pub default_vmtx: VerticalMetrics,
pub vmtx: Vec<VerticalMetrics>,
pub is_embedded: bool,
pub t3loading: bool,
pub t3_glyphs: HashMap<i32, Vec<u8>>,
pub t3_matrix: [f32; 6],
pub t3_bbox: [f32; 4],
}
impl Default for FontDesc {
fn default() -> Self {
Self::new()
}
}
impl FontDesc {
pub fn new() -> Self {
Self {
refs: 1,
size: std::mem::size_of::<FontDesc>(),
font: None,
name: String::new(),
flags: 0,
italic_angle: 0.0,
ascent: 0.0,
descent: 0.0,
cap_height: 0.0,
x_height: 0.0,
missing_width: 0.0,
encoding: None,
to_ttf_cmap: None,
cid_to_gid: Vec::new(),
to_unicode: None,
cid_to_ucs: Vec::new(),
wmode: 0,
default_hmtx: HorizontalMetrics::default(),
hmtx: Vec::new(),
default_vmtx: VerticalMetrics::default(),
vmtx: Vec::new(),
is_embedded: false,
t3loading: false,
t3_glyphs: HashMap::new(),
t3_matrix: [0.001, 0.0, 0.0, 0.001, 0.0, 0.0],
t3_bbox: [0.0, 0.0, 1000.0, 1000.0],
}
}
pub fn with_name(name: &str) -> Self {
let mut fd = Self::new();
fd.name = name.to_string();
fd
}
pub fn set_wmode(&mut self, wmode: i32) {
self.wmode = wmode;
}
pub fn set_default_hmtx(&mut self, w: i32) {
self.default_hmtx = HorizontalMetrics { lo: 0, hi: 0, w };
}
pub fn set_default_vmtx(&mut self, y: i16, w: i16) {
self.default_vmtx = VerticalMetrics {
lo: 0,
hi: 0,
x: 0,
y,
w,
};
}
pub fn add_hmtx(&mut self, lo: u16, hi: u16, w: i32) {
self.hmtx.push(HorizontalMetrics { lo, hi, w });
}
pub fn add_vmtx(&mut self, lo: u16, hi: u16, x: i16, y: i16, w: i16) {
self.vmtx.push(VerticalMetrics { lo, hi, x, y, w });
}
pub fn end_hmtx(&mut self) {
self.hmtx.sort_by_key(|m| m.lo);
}
pub fn end_vmtx(&mut self) {
self.vmtx.sort_by_key(|m| m.lo);
}
pub fn lookup_hmtx(&self, cid: i32) -> HorizontalMetrics {
let cid = cid as u16;
for m in &self.hmtx {
if cid >= m.lo && cid <= m.hi {
return HorizontalMetrics {
lo: cid,
hi: cid,
w: m.w, };
}
}
self.default_hmtx
}
pub fn lookup_vmtx(&self, cid: i32) -> VerticalMetrics {
let cid = cid as u16;
for m in &self.vmtx {
if cid >= m.lo && cid <= m.hi {
return *m;
}
}
self.default_vmtx
}
pub fn cid_to_gid(&self, cid: i32) -> i32 {
if cid < 0 {
return 0;
}
if (cid as usize) < self.cid_to_gid.len() {
self.cid_to_gid[cid as usize] as i32
} else {
cid }
}
pub fn cid_to_unicode(&self, cid: i32) -> i32 {
if cid < 0 {
return 0;
}
if (cid as usize) < self.cid_to_ucs.len() {
self.cid_to_ucs[cid as usize] as i32
} else {
cid }
}
pub fn is_fixed_pitch(&self) -> bool {
(self.flags & PDF_FD_FIXED_PITCH) != 0
}
pub fn is_serif(&self) -> bool {
(self.flags & PDF_FD_SERIF) != 0
}
pub fn is_symbolic(&self) -> bool {
(self.flags & PDF_FD_SYMBOLIC) != 0
}
pub fn is_italic(&self) -> bool {
(self.flags & PDF_FD_ITALIC) != 0
}
pub fn update_size(&mut self) {
self.size = std::mem::size_of::<FontDesc>()
+ self.name.len()
+ self.cid_to_gid.len() * 2
+ self.cid_to_ucs.len() * 2
+ self.hmtx.len() * std::mem::size_of::<HorizontalMetrics>()
+ self.vmtx.len() * std::mem::size_of::<VerticalMetrics>();
}
}
pub fn get_encoding_names(encoding: i32) -> Vec<&'static str> {
match encoding {
PDF_ENCODING_MAC_ROMAN => vec![
".notdef",
"space",
"exclam",
"quotedbl",
"numbersign",
"dollar",
"percent",
"ampersand",
"quotesingle",
"parenleft",
"parenright",
"asterisk",
"plus",
"comma",
"hyphen",
"period",
"slash",
"zero",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
"colon",
"semicolon",
"less",
"equal",
"greater",
],
PDF_ENCODING_WIN_ANSI => vec![
".notdef",
"space",
"exclam",
"quotedbl",
"numbersign",
"dollar",
"percent",
"ampersand",
"quotesingle",
"parenleft",
"parenright",
"asterisk",
"plus",
"comma",
"hyphen",
"period",
"slash",
"zero",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
"colon",
"semicolon",
"less",
"equal",
"greater",
],
_ => vec![
".notdef",
"space",
"exclam",
"quotedbl",
"numbersign",
"dollar",
"percent",
"ampersand",
"quoteright",
"parenleft",
"parenright",
"asterisk",
"plus",
"comma",
"hyphen",
"period",
"slash",
"zero",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
"colon",
"semicolon",
"less",
"equal",
"greater",
],
}
}
#[derive(Debug, Clone)]
pub struct SubstituteFont {
pub name: String,
pub mono: bool,
pub serif: bool,
pub bold: bool,
pub italic: bool,
}
impl SubstituteFont {
pub fn lookup(mono: bool, serif: bool, bold: bool, italic: bool) -> Self {
let name = match (mono, serif, bold, italic) {
(true, _, true, true) => "Courier-BoldOblique",
(true, _, true, false) => "Courier-Bold",
(true, _, false, true) => "Courier-Oblique",
(true, _, false, false) => "Courier",
(false, true, true, true) => "Times-BoldItalic",
(false, true, true, false) => "Times-Bold",
(false, true, false, true) => "Times-Italic",
(false, true, false, false) => "Times-Roman",
(false, false, true, true) => "Helvetica-BoldOblique",
(false, false, true, false) => "Helvetica-Bold",
(false, false, false, true) => "Helvetica-Oblique",
(false, false, false, false) => "Helvetica",
};
Self {
name: name.to_string(),
mono,
serif,
bold,
italic,
}
}
}
pub static FONT_DESCS: LazyLock<HandleStore<FontDesc>> = LazyLock::new(HandleStore::new);
#[unsafe(no_mangle)]
pub extern "C" fn pdf_new_font_desc(_ctx: ContextHandle) -> Handle {
let fd = FontDesc::new();
FONT_DESCS.insert(fd)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_keep_font(_ctx: ContextHandle, font: Handle) -> Handle {
if let Some(font_arc) = FONT_DESCS.get(font) {
let mut f = font_arc.lock().unwrap();
f.refs += 1;
}
font
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_drop_font(_ctx: ContextHandle, font: Handle) {
if let Some(font_arc) = FONT_DESCS.get(font) {
let should_remove = {
let mut f = font_arc.lock().unwrap();
f.refs -= 1;
f.refs <= 0
};
if should_remove {
FONT_DESCS.remove(font);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_name(_ctx: ContextHandle, font: Handle) -> *const c_char {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
if let Ok(cstr) = CString::new(f.name.clone()) {
return cstr.into_raw();
}
}
ptr::null()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_font_name(_ctx: ContextHandle, font: Handle, name: *const c_char) {
if name.is_null() {
return;
}
if let Some(font_arc) = FONT_DESCS.get(font) {
let mut f = font_arc.lock().unwrap();
if let Ok(s) = unsafe { CStr::from_ptr(name).to_str() } {
f.name = s.to_string();
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_flags(_ctx: ContextHandle, font: Handle) -> i32 {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
return f.flags;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_font_flags(_ctx: ContextHandle, font: Handle, flags: i32) {
if let Some(font_arc) = FONT_DESCS.get(font) {
let mut f = font_arc.lock().unwrap();
f.flags = flags;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_italic_angle(_ctx: ContextHandle, font: Handle) -> f32 {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
return f.italic_angle;
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_ascent(_ctx: ContextHandle, font: Handle) -> f32 {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
return f.ascent;
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_descent(_ctx: ContextHandle, font: Handle) -> f32 {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
return f.descent;
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_cap_height(_ctx: ContextHandle, font: Handle) -> f32 {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
return f.cap_height;
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_x_height(_ctx: ContextHandle, font: Handle) -> f32 {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
return f.x_height;
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_missing_width(_ctx: ContextHandle, font: Handle) -> f32 {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
return f.missing_width;
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_is_embedded(_ctx: ContextHandle, font: Handle) -> i32 {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
return if f.is_embedded { 1 } else { 0 };
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_wmode(_ctx: ContextHandle, font: Handle) -> i32 {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
return f.wmode;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_font_wmode(_ctx: ContextHandle, font: Handle, wmode: i32) {
if let Some(font_arc) = FONT_DESCS.get(font) {
let mut f = font_arc.lock().unwrap();
f.set_wmode(wmode);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_default_hmtx(_ctx: ContextHandle, font: Handle, w: i32) {
if let Some(font_arc) = FONT_DESCS.get(font) {
let mut f = font_arc.lock().unwrap();
f.set_default_hmtx(w);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_default_vmtx(_ctx: ContextHandle, font: Handle, y: i32, w: i32) {
if let Some(font_arc) = FONT_DESCS.get(font) {
let mut f = font_arc.lock().unwrap();
f.set_default_vmtx(y as i16, w as i16);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_hmtx(_ctx: ContextHandle, font: Handle, lo: i32, hi: i32, w: i32) {
if let Some(font_arc) = FONT_DESCS.get(font) {
let mut f = font_arc.lock().unwrap();
f.add_hmtx(lo as u16, hi as u16, w);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_vmtx(
_ctx: ContextHandle,
font: Handle,
lo: i32,
hi: i32,
x: i32,
y: i32,
w: i32,
) {
if let Some(font_arc) = FONT_DESCS.get(font) {
let mut f = font_arc.lock().unwrap();
f.add_vmtx(lo as u16, hi as u16, x as i16, y as i16, w as i16);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_end_hmtx(_ctx: ContextHandle, font: Handle) {
if let Some(font_arc) = FONT_DESCS.get(font) {
let mut f = font_arc.lock().unwrap();
f.end_hmtx();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_end_vmtx(_ctx: ContextHandle, font: Handle) {
if let Some(font_arc) = FONT_DESCS.get(font) {
let mut f = font_arc.lock().unwrap();
f.end_vmtx();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_lookup_hmtx(
_ctx: ContextHandle,
font: Handle,
cid: i32,
) -> HorizontalMetrics {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
return f.lookup_hmtx(cid);
}
HorizontalMetrics::default()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_lookup_vmtx(_ctx: ContextHandle, font: Handle, cid: i32) -> VerticalMetrics {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
return f.lookup_vmtx(cid);
}
VerticalMetrics::default()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_cid_to_gid(_ctx: ContextHandle, font: Handle, cid: i32) -> i32 {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
return f.cid_to_gid(cid);
}
cid
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_cid_to_unicode(_ctx: ContextHandle, font: Handle, cid: i32) -> i32 {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
return f.cid_to_unicode(cid);
}
cid
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_cid_to_gid(
_ctx: ContextHandle,
font: Handle,
table: *const u16,
len: usize,
) {
if table.is_null() || len == 0 {
return;
}
if let Some(font_arc) = FONT_DESCS.get(font) {
let mut f = font_arc.lock().unwrap();
f.cid_to_gid = unsafe { std::slice::from_raw_parts(table, len).to_vec() };
f.update_size();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_cid_to_ucs(
_ctx: ContextHandle,
font: Handle,
table: *const u16,
len: usize,
) {
if table.is_null() || len == 0 {
return;
}
if let Some(font_arc) = FONT_DESCS.get(font) {
let mut f = font_arc.lock().unwrap();
f.cid_to_ucs = unsafe { std::slice::from_raw_parts(table, len).to_vec() };
f.update_size();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_font(
_ctx: ContextHandle,
_doc: DocumentHandle,
_rdb: Handle,
obj: PdfObjHandle,
) -> Handle {
let mut fd = FontDesc::new();
if let Some(obj_arc) = PDF_OBJECTS.get(obj) {
if let Ok(guard) = obj_arc.lock() {
if let PdfObjType::Dict(ref entries) = guard.obj_type {
for (key, val) in entries {
match key.as_str() {
"BaseFont" => {
if let PdfObjType::Name(ref name) = val.obj_type {
fd.name = name.clone();
}
}
"Subtype" => {
if let PdfObjType::Name(ref subtype) = val.obj_type {
if subtype == "Type3" {
fd.t3loading = true;
}
}
}
"FirstChar" => {
}
"LastChar" => {
}
"Widths" => {
if let PdfObjType::Array(ref widths) = val.obj_type {
let first_char = entries
.iter()
.find(|(k, _)| k == "FirstChar")
.and_then(|(_, v)| {
if let PdfObjType::Int(i) = v.obj_type {
Some(i as u16)
} else {
None
}
})
.unwrap_or(0);
for (idx, w) in widths.iter().enumerate() {
if let PdfObjType::Int(width) = w.obj_type {
let cid = first_char + idx as u16;
fd.add_hmtx(cid, cid, width as i32);
} else if let PdfObjType::Real(width) = w.obj_type {
let cid = first_char + idx as u16;
fd.add_hmtx(cid, cid, width as i32);
}
}
fd.end_hmtx();
}
}
"FontDescriptor" => {
if let PdfObjType::Dict(ref desc_entries) = val.obj_type {
load_font_descriptor_entries(&mut fd, desc_entries);
}
}
"Encoding" => {
if let PdfObjType::Name(ref enc) = val.obj_type {
if enc.contains("WinAnsi") {
fd.flags |= PDF_FD_NONSYMBOLIC;
} else if enc.contains("Symbol") || enc.contains("ZapfDingbats") {
fd.flags |= PDF_FD_SYMBOLIC;
}
}
}
_ => {}
}
}
}
}
}
if fd.name.is_empty() {
fd.name = "Unknown".to_string();
}
if fd.default_hmtx.w == 0 && fd.hmtx.is_empty() {
fd.set_default_hmtx(1000);
}
fd.update_size();
FONT_DESCS.insert(fd)
}
fn load_font_descriptor_entries(fd: &mut FontDesc, entries: &[(String, PdfObj)]) {
for (key, val) in entries {
match key.as_str() {
"FontName" => {
if let PdfObjType::Name(ref n) = val.obj_type {
if fd.name.is_empty() || fd.name == "Unknown" {
fd.name = n.clone();
}
}
}
"Flags" => {
if let PdfObjType::Int(f) = val.obj_type {
fd.flags = f as i32;
}
}
"ItalicAngle" => match val.obj_type {
PdfObjType::Real(f) => fd.italic_angle = f as f32,
PdfObjType::Int(i) => fd.italic_angle = i as f32,
_ => {}
},
"Ascent" => match val.obj_type {
PdfObjType::Real(f) => fd.ascent = f as f32,
PdfObjType::Int(i) => fd.ascent = i as f32,
_ => {}
},
"Descent" => match val.obj_type {
PdfObjType::Real(f) => fd.descent = f as f32,
PdfObjType::Int(i) => fd.descent = i as f32,
_ => {}
},
"CapHeight" => match val.obj_type {
PdfObjType::Real(f) => fd.cap_height = f as f32,
PdfObjType::Int(i) => fd.cap_height = i as f32,
_ => {}
},
"XHeight" => match val.obj_type {
PdfObjType::Real(f) => fd.x_height = f as f32,
PdfObjType::Int(i) => fd.x_height = i as f32,
_ => {}
},
"MissingWidth" => match val.obj_type {
PdfObjType::Real(f) => fd.missing_width = f as f32,
PdfObjType::Int(i) => fd.missing_width = i as f32,
_ => {}
},
"FontFile" | "FontFile2" | "FontFile3" => {
fd.is_embedded = true;
}
_ => {}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_type3_font(
_ctx: ContextHandle,
_doc: DocumentHandle,
_rdb: Handle,
_obj: PdfObjHandle,
) -> Handle {
let mut fd = FontDesc::new();
fd.t3loading = true;
FONT_DESCS.insert(fd)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_type3_glyphs(_ctx: ContextHandle, _doc: DocumentHandle, font: Handle) {
if let Some(font_arc) = FONT_DESCS.get(font) {
let mut f = font_arc.lock().unwrap();
f.t3loading = false;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_hail_mary_font(_ctx: ContextHandle, _doc: DocumentHandle) -> Handle {
let mut fd = FontDesc::with_name("Helvetica");
fd.flags = PDF_FD_NONSYMBOLIC;
FONT_DESCS.insert(fd)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_encoding(estrings: *mut *const c_char, encoding: *const c_char) {
if estrings.is_null() || encoding.is_null() {
return;
}
let enc_str = unsafe { CStr::from_ptr(encoding).to_str().unwrap_or("") };
let enc_type = match enc_str {
"MacRomanEncoding" => PDF_ENCODING_MAC_ROMAN,
"WinAnsiEncoding" => PDF_ENCODING_WIN_ANSI,
"MacExpertEncoding" => PDF_ENCODING_MAC_EXPERT,
_ => PDF_ENCODING_STANDARD,
};
let names = get_encoding_names(enc_type);
for (i, name) in names.iter().enumerate() {
if let Ok(cstr) = CString::new(*name) {
unsafe {
*estrings.add(i) = cstr.into_raw();
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_lookup_substitute_font(
_ctx: ContextHandle,
mono: i32,
serif: i32,
bold: i32,
italic: i32,
len: *mut i32,
) -> *const u8 {
let sf = SubstituteFont::lookup(mono != 0, serif != 0, bold != 0, italic != 0);
let static_name: &'static [u8] = match sf.name.as_str() {
"Courier" => b"Courier",
"Courier-Bold" => b"Courier-Bold",
"Courier-Oblique" => b"Courier-Oblique",
"Courier-BoldOblique" => b"Courier-BoldOblique",
"Times-Roman" => b"Times-Roman",
"Times-Bold" => b"Times-Bold",
"Times-Italic" => b"Times-Italic",
"Times-BoldItalic" => b"Times-BoldItalic",
"Helvetica" => b"Helvetica",
"Helvetica-Bold" => b"Helvetica-Bold",
"Helvetica-Oblique" => b"Helvetica-Oblique",
"Helvetica-BoldOblique" => b"Helvetica-BoldOblique",
_ => b"Helvetica",
};
if !len.is_null() {
unsafe {
*len = static_name.len() as i32;
}
}
static_name.as_ptr()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_clean_font_name(fontname: *const c_char) -> *const c_char {
if fontname.is_null() {
return ptr::null();
}
let name = unsafe { CStr::from_ptr(fontname).to_str().unwrap_or("") };
let cleaned = name
.trim_start_matches("AAAAAA+")
.trim_start_matches("BAAAAA+")
.trim_start_matches("CAAAAA+");
if let Ok(cstr) = CString::new(cleaned) {
return cstr.into_raw();
}
fontname
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_simple_font(
_ctx: ContextHandle,
_doc: DocumentHandle,
font: FontHandle,
encoding: i32,
) -> PdfObjHandle {
let font_data = match FONT_DESCS.get(font) {
Some(arc) => match arc.lock() {
Ok(guard) => guard.clone(),
Err(_) => return 0,
},
None => return 0,
};
let mut dict_entries: Vec<(String, PdfObj)> = Vec::with_capacity(10);
dict_entries.push(("Type".to_string(), PdfObj::new_name("Font")));
let subtype = if font_data.is_embedded {
"TrueType"
} else {
"Type1"
};
dict_entries.push(("Subtype".to_string(), PdfObj::new_name(subtype)));
let base_font = if font_data.name.is_empty() {
"Helvetica"
} else {
&font_data.name
};
dict_entries.push(("BaseFont".to_string(), PdfObj::new_name(base_font)));
let enc_name = match encoding {
PDF_ENCODING_MAC_ROMAN => "MacRomanEncoding",
PDF_ENCODING_WIN_ANSI => "WinAnsiEncoding",
PDF_ENCODING_MAC_EXPERT => "MacExpertEncoding",
_ => "WinAnsiEncoding",
};
dict_entries.push(("Encoding".to_string(), PdfObj::new_name(enc_name)));
let first_char: i64 = 0;
let last_char: i64 = 255;
dict_entries.push(("FirstChar".to_string(), PdfObj::new_int(first_char)));
dict_entries.push(("LastChar".to_string(), PdfObj::new_int(last_char)));
let mut widths = Vec::with_capacity(256);
for cid in 0..=255i32 {
let m = font_data.lookup_hmtx(cid);
widths.push(PdfObj::new_int(m.w as i64));
}
let widths_obj = PdfObj {
obj_type: PdfObjType::Array(widths),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
dict_entries.push(("Widths".to_string(), widths_obj));
let descriptor = build_font_descriptor_dict(&font_data);
dict_entries.push(("FontDescriptor".to_string(), descriptor));
let font_dict = PdfObj {
obj_type: PdfObjType::Dict(dict_entries),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
PDF_OBJECTS.insert(font_dict)
}
fn build_font_descriptor_dict(fd: &FontDesc) -> PdfObj {
let mut entries: Vec<(String, PdfObj)> = Vec::with_capacity(12);
entries.push(("Type".to_string(), PdfObj::new_name("FontDescriptor")));
entries.push((
"FontName".to_string(),
PdfObj::new_name(if fd.name.is_empty() {
"Unknown"
} else {
&fd.name
}),
));
entries.push(("Flags".to_string(), PdfObj::new_int(fd.flags as i64)));
entries.push((
"ItalicAngle".to_string(),
PdfObj::new_real(fd.italic_angle as f64),
));
entries.push(("Ascent".to_string(), PdfObj::new_real(fd.ascent as f64)));
entries.push(("Descent".to_string(), PdfObj::new_real(fd.descent as f64)));
entries.push((
"CapHeight".to_string(),
PdfObj::new_real(fd.cap_height as f64),
));
entries.push(("XHeight".to_string(), PdfObj::new_real(fd.x_height as f64)));
entries.push((
"MissingWidth".to_string(),
PdfObj::new_real(fd.missing_width as f64),
));
let bbox = PdfObj {
obj_type: PdfObjType::Array(vec![
PdfObj::new_real(fd.t3_bbox[0] as f64),
PdfObj::new_real(fd.t3_bbox[1] as f64),
PdfObj::new_real(fd.t3_bbox[2] as f64),
PdfObj::new_real(fd.t3_bbox[3] as f64),
]),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
entries.push(("FontBBox".to_string(), bbox));
PdfObj {
obj_type: PdfObjType::Dict(entries),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_cid_font(
_ctx: ContextHandle,
_doc: DocumentHandle,
font: FontHandle,
) -> PdfObjHandle {
let font_data = match FONT_DESCS.get(font) {
Some(arc) => match arc.lock() {
Ok(guard) => guard.clone(),
Err(_) => return 0,
},
None => return 0,
};
let mut cid_entries: Vec<(String, PdfObj)> = Vec::with_capacity(8);
cid_entries.push(("Type".to_string(), PdfObj::new_name("Font")));
cid_entries.push(("Subtype".to_string(), PdfObj::new_name("CIDFontType2")));
cid_entries.push((
"BaseFont".to_string(),
PdfObj::new_name(if font_data.name.is_empty() {
"Unknown"
} else {
&font_data.name
}),
));
let csi_entries: Vec<(String, PdfObj)> = vec![
("Registry".to_string(), PdfObj::new_string(b"Adobe")),
("Ordering".to_string(), PdfObj::new_string(b"Identity")),
("Supplement".to_string(), PdfObj::new_int(0)),
];
let csi = PdfObj {
obj_type: PdfObjType::Dict(csi_entries),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
cid_entries.push(("CIDSystemInfo".to_string(), csi));
cid_entries.push((
"DW".to_string(),
PdfObj::new_int(font_data.default_hmtx.w as i64),
));
if !font_data.hmtx.is_empty() {
let mut w_array: Vec<PdfObj> = Vec::new();
for m in &font_data.hmtx {
w_array.push(PdfObj::new_int(m.lo as i64));
let count = (m.hi - m.lo + 1) as usize;
let widths: Vec<PdfObj> = (0..count).map(|_| PdfObj::new_int(m.w as i64)).collect();
w_array.push(PdfObj {
obj_type: PdfObjType::Array(widths),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
});
}
cid_entries.push((
"W".to_string(),
PdfObj {
obj_type: PdfObjType::Array(w_array),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
},
));
}
if !font_data.cid_to_gid.is_empty() {
cid_entries.push(("CIDToGIDMap".to_string(), PdfObj::new_name("Identity")));
}
let descriptor = build_font_descriptor_dict(&font_data);
cid_entries.push(("FontDescriptor".to_string(), descriptor));
let cid_font = PdfObj {
obj_type: PdfObjType::Dict(cid_entries),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
let mut type0_entries: Vec<(String, PdfObj)> = Vec::with_capacity(5);
type0_entries.push(("Type".to_string(), PdfObj::new_name("Font")));
type0_entries.push(("Subtype".to_string(), PdfObj::new_name("Type0")));
type0_entries.push((
"BaseFont".to_string(),
PdfObj::new_name(if font_data.name.is_empty() {
"Unknown"
} else {
&font_data.name
}),
));
type0_entries.push(("Encoding".to_string(), PdfObj::new_name("Identity-H")));
type0_entries.push((
"DescendantFonts".to_string(),
PdfObj {
obj_type: PdfObjType::Array(vec![cid_font]),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
},
));
let type0_dict = PdfObj {
obj_type: PdfObjType::Dict(type0_entries),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
PDF_OBJECTS.insert(type0_dict)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_cjk_font(
_ctx: ContextHandle,
_doc: DocumentHandle,
font: FontHandle,
script: i32,
wmode: i32,
serif: i32,
) -> PdfObjHandle {
let font_data = match FONT_DESCS.get(font) {
Some(arc) => match arc.lock() {
Ok(guard) => guard.clone(),
Err(_) => return 0,
},
None => return 0,
};
let (registry, ordering, encoding, font_name) = match script {
PDF_CJK_CNS1 => {
let name = if serif != 0 {
"MingLiU"
} else {
"DFKaiShu-SB-Estd-BF"
};
let enc = if wmode != 0 {
"UniCNS-UTF16-V"
} else {
"UniCNS-UTF16-H"
};
("Adobe", "CNS1", enc, name)
}
PDF_CJK_GB1 => {
let name = if serif != 0 { "SimSun" } else { "SimHei" };
let enc = if wmode != 0 {
"UniGB-UTF16-V"
} else {
"UniGB-UTF16-H"
};
("Adobe", "GB1", enc, name)
}
PDF_CJK_JAPAN1 => {
let name = if serif != 0 { "MS-Mincho" } else { "MS-Gothic" };
let enc = if wmode != 0 {
"UniJIS-UTF16-V"
} else {
"UniJIS-UTF16-H"
};
("Adobe", "Japan1", enc, name)
}
PDF_CJK_KOREA1 => {
let name = if serif != 0 { "Batang" } else { "Dotum" };
let enc = if wmode != 0 {
"UniKS-UTF16-V"
} else {
"UniKS-UTF16-H"
};
("Adobe", "Korea1", enc, name)
}
_ => ("Adobe", "Identity", "Identity-H", "Unknown"),
};
let base_name = if font_data.name.is_empty() {
font_name.to_string()
} else {
font_data.name.clone()
};
let mut cid_entries: Vec<(String, PdfObj)> = Vec::with_capacity(6);
cid_entries.push(("Type".to_string(), PdfObj::new_name("Font")));
cid_entries.push(("Subtype".to_string(), PdfObj::new_name("CIDFontType0")));
cid_entries.push(("BaseFont".to_string(), PdfObj::new_name(&base_name)));
let csi_entries: Vec<(String, PdfObj)> = vec![
(
"Registry".to_string(),
PdfObj::new_string(registry.as_bytes()),
),
(
"Ordering".to_string(),
PdfObj::new_string(ordering.as_bytes()),
),
("Supplement".to_string(), PdfObj::new_int(0)),
];
cid_entries.push((
"CIDSystemInfo".to_string(),
PdfObj {
obj_type: PdfObjType::Dict(csi_entries),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
},
));
let dw = if font_data.default_hmtx.w != 0 {
font_data.default_hmtx.w
} else {
1000
};
cid_entries.push(("DW".to_string(), PdfObj::new_int(dw as i64)));
let descriptor = build_font_descriptor_dict(&font_data);
cid_entries.push(("FontDescriptor".to_string(), descriptor));
let cid_font = PdfObj {
obj_type: PdfObjType::Dict(cid_entries),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
let type0_entries: Vec<(String, PdfObj)> = vec![
("Type".to_string(), PdfObj::new_name("Font")),
("Subtype".to_string(), PdfObj::new_name("Type0")),
("BaseFont".to_string(), PdfObj::new_name(&base_name)),
("Encoding".to_string(), PdfObj::new_name(encoding)),
(
"DescendantFonts".to_string(),
PdfObj {
obj_type: PdfObjType::Array(vec![cid_font]),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
},
),
];
let type0_dict = PdfObj {
obj_type: PdfObjType::Dict(type0_entries),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
PDF_OBJECTS.insert(type0_dict)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_substitute_font(
_ctx: ContextHandle,
_doc: DocumentHandle,
font: FontHandle,
) -> PdfObjHandle {
let font_data = match FONT_DESCS.get(font) {
Some(arc) => match arc.lock() {
Ok(guard) => guard.clone(),
Err(_) => return 0,
},
None => return 0,
};
let is_mono = font_data.is_fixed_pitch();
let is_serif = font_data.is_serif();
let is_italic = font_data.is_italic();
let is_bold = (font_data.flags & PDF_FD_FORCE_BOLD) != 0;
let sf = SubstituteFont::lookup(is_mono, is_serif, is_bold, is_italic);
let dict_entries: Vec<(String, PdfObj)> = vec![
("Type".to_string(), PdfObj::new_name("Font")),
("Subtype".to_string(), PdfObj::new_name("Type1")),
("BaseFont".to_string(), PdfObj::new_name(&sf.name)),
("Encoding".to_string(), PdfObj::new_name("WinAnsiEncoding")),
];
let font_dict = PdfObj {
obj_type: PdfObjType::Dict(dict_entries),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
PDF_OBJECTS.insert(font_dict)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_writing_supported(_ctx: ContextHandle, _font: FontHandle) -> i32 {
1
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_subset_fonts(
_ctx: ContextHandle,
doc: DocumentHandle,
pages_len: i32,
pages: *const i32,
) {
let doc_arc = match crate::ffi::DOCUMENTS.get(doc) {
Some(d) => d,
None => return,
};
let mut guard = match doc_arc.lock() {
Ok(g) => g,
Err(_) => return,
};
let data = guard.data().to_vec();
let target_pages: Vec<i32> = if !pages.is_null() && pages_len > 0 {
unsafe { std::slice::from_raw_parts(pages, pages_len as usize) }.to_vec()
} else {
(0..guard.page_count).collect()
};
let page_pattern = b"/Type /Page";
let mut font_names_in_scope: Vec<String> = Vec::new();
let mut page_idx = 0i32;
let mut pos = 0usize;
while pos + page_pattern.len() <= data.len() {
if &data[pos..pos + page_pattern.len()] == page_pattern
&& data.get(pos + page_pattern.len()) != Some(&b's')
{
if target_pages.contains(&page_idx) {
let search_end = (pos + 4096).min(data.len());
let region = &data[pos..search_end];
let text = String::from_utf8_lossy(region);
let mut idx = 0;
while let Some(bf_pos) = text[idx..].find("/BaseFont") {
let start = idx + bf_pos + 9; if start >= text.len() {
break;
}
let after = text[start..].trim_start();
if let Some(name) = after.strip_prefix('/') {
let end = name
.find(|c: char| c.is_whitespace() || c == '/' || c == '>')
.unwrap_or(name.len());
let font_name = name[..end].to_string();
if !font_name.is_empty() && !font_names_in_scope.contains(&font_name) {
font_names_in_scope.push(font_name);
}
}
idx = start;
}
}
page_idx += 1;
}
pos += 1;
}
let mut modified_data = data;
let mut offset_adjustment: i64 = 0;
for font_name in &font_names_in_scope {
if font_name.len() > 7
&& font_name.as_bytes()[6] == b'+'
&& font_name[..6].chars().all(|c| c.is_ascii_uppercase())
{
continue;
}
let basefont_tag = format!("/BaseFont /{}", font_name);
let search_data = String::from_utf8_lossy(&modified_data);
let is_embedded = search_data.contains("/FontFile");
if !is_embedded {
continue;
}
let prefix = generate_subset_prefix(font_name);
let prefixed_name = format!("{}+{}", prefix, font_name);
let old_tag = format!("/{}", font_name);
let new_tag = format!("/{}", prefixed_name);
let text = String::from_utf8_lossy(&modified_data).to_string();
let replaced = text.replace(&old_tag, &new_tag);
let new_len = replaced.len() as i64;
let old_len = modified_data.len() as i64;
offset_adjustment += new_len - old_len;
modified_data = replaced.into_bytes();
}
if offset_adjustment != 0 {
guard.set_data(modified_data);
}
}
fn generate_subset_prefix(name: &str) -> String {
let mut hash: u32 = 0;
for byte in name.as_bytes() {
hash = hash.wrapping_mul(31).wrapping_add(*byte as u32);
}
let mut prefix = String::with_capacity(6);
let mut h = hash;
for _ in 0..6 {
prefix.push((b'A' + (h % 26) as u8) as char);
h /= 26;
if h == 0 {
h = hash.wrapping_add(1);
}
}
prefix
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_print_font(_ctx: ContextHandle, out: OutputHandle, font: Handle) {
if let Some(font_arc) = FONT_DESCS.get(font) {
let f = font_arc.lock().unwrap();
let info = format!(
"Font: {}\n Flags: 0x{:x}\n Embedded: {}\n WMode: {}\n \
Ascent: {:.1}\n Descent: {:.1}\n CapHeight: {:.1}\n \
ItalicAngle: {:.1}\n MissingWidth: {:.1}\n HMetrics: {}\n \
VMetrics: {}\n",
f.name,
f.flags,
f.is_embedded,
f.wmode,
f.ascent,
f.descent,
f.cap_height,
f.italic_angle,
f.missing_width,
f.hmtx.len(),
f.vmtx.len(),
);
if let Some(out_arc) = OUTPUTS.get(out) {
if let Ok(mut out_guard) = out_arc.lock() {
let _ = out_guard.write_string(&info);
return;
}
}
eprint!("{}", info);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_font_free_string(_ctx: ContextHandle, s: *mut c_char) {
if !s.is_null() {
unsafe {
drop(CString::from_raw(s));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_font_desc_new() {
let fd = FontDesc::new();
assert!(fd.name.is_empty());
assert_eq!(fd.flags, 0);
assert_eq!(fd.refs, 1);
assert!(!fd.is_embedded);
}
#[test]
fn test_font_desc_with_name() {
let fd = FontDesc::with_name("Helvetica");
assert_eq!(fd.name, "Helvetica");
}
#[test]
fn test_horizontal_metrics() {
let mut fd = FontDesc::new();
fd.set_default_hmtx(1000);
fd.add_hmtx(0, 127, 500);
fd.add_hmtx(128, 255, 600);
fd.end_hmtx();
let m = fd.lookup_hmtx(65); assert_eq!(m.w, 500);
let m2 = fd.lookup_hmtx(200);
assert_eq!(m2.w, 600);
let m3 = fd.lookup_hmtx(300); assert_eq!(m3.w, 1000); }
#[test]
fn test_vertical_metrics() {
let mut fd = FontDesc::new();
fd.set_default_vmtx(-500, 1000);
fd.add_vmtx(0, 127, 0, -500, 1000);
fd.end_vmtx();
let m = fd.lookup_vmtx(65);
assert_eq!(m.y, -500);
assert_eq!(m.w, 1000);
}
#[test]
fn test_cid_to_gid() {
let mut fd = FontDesc::new();
fd.cid_to_gid = vec![0, 1, 2, 100, 101, 102];
assert_eq!(fd.cid_to_gid(0), 0);
assert_eq!(fd.cid_to_gid(3), 100);
assert_eq!(fd.cid_to_gid(100), 100); }
#[test]
fn test_cid_to_unicode() {
let mut fd = FontDesc::new();
fd.cid_to_ucs = vec![0x0000, 0x0041, 0x0042, 0x0043];
assert_eq!(fd.cid_to_unicode(1), 0x0041); assert_eq!(fd.cid_to_unicode(2), 0x0042); }
#[test]
fn test_font_flags() {
let mut fd = FontDesc::new();
fd.flags = PDF_FD_SERIF | PDF_FD_ITALIC;
assert!(fd.is_serif());
assert!(fd.is_italic());
assert!(!fd.is_fixed_pitch());
assert!(!fd.is_symbolic());
}
#[test]
fn test_substitute_font() {
let sf = SubstituteFont::lookup(true, false, true, false);
assert_eq!(sf.name, "Courier-Bold");
let sf2 = SubstituteFont::lookup(false, true, false, true);
assert_eq!(sf2.name, "Times-Italic");
let sf3 = SubstituteFont::lookup(false, false, false, false);
assert_eq!(sf3.name, "Helvetica");
}
#[test]
fn test_ffi_lifecycle() {
let ctx = 0;
let font = pdf_new_font_desc(ctx);
assert!(font > 0);
let kept = pdf_keep_font(ctx, font);
assert_eq!(kept, font);
pdf_drop_font(ctx, font);
pdf_drop_font(ctx, font);
}
#[test]
fn test_ffi_properties() {
let ctx = 0;
let font = pdf_new_font_desc(ctx);
let name = CString::new("Times-Roman").unwrap();
pdf_set_font_name(ctx, font, name.as_ptr());
let got_name = pdf_font_name(ctx, font);
assert!(!got_name.is_null());
unsafe {
assert_eq!(CStr::from_ptr(got_name).to_str().unwrap(), "Times-Roman");
pdf_font_free_string(ctx, got_name as *mut c_char);
}
pdf_set_font_flags(ctx, font, PDF_FD_SERIF | PDF_FD_ITALIC);
assert_eq!(pdf_font_flags(ctx, font), PDF_FD_SERIF | PDF_FD_ITALIC);
pdf_drop_font(ctx, font);
}
#[test]
fn test_ffi_wmode() {
let ctx = 0;
let font = pdf_new_font_desc(ctx);
assert_eq!(pdf_font_wmode(ctx, font), 0);
pdf_set_font_wmode(ctx, font, 1);
assert_eq!(pdf_font_wmode(ctx, font), 1);
pdf_drop_font(ctx, font);
}
#[test]
fn test_ffi_metrics() {
let ctx = 0;
let font = pdf_new_font_desc(ctx);
pdf_set_default_hmtx(ctx, font, 1000);
pdf_add_hmtx(ctx, font, 0, 127, 500);
pdf_end_hmtx(ctx, font);
let m = pdf_lookup_hmtx(ctx, font, 65);
assert_eq!(m.w, 500);
pdf_drop_font(ctx, font);
}
#[test]
fn test_ffi_cid_mapping() {
let ctx = 0;
let font = pdf_new_font_desc(ctx);
let gid_table: Vec<u16> = vec![0, 1, 2, 100, 101, 102];
pdf_set_cid_to_gid(ctx, font, gid_table.as_ptr(), gid_table.len());
assert_eq!(pdf_font_cid_to_gid(ctx, font, 3), 100);
pdf_drop_font(ctx, font);
}
#[test]
fn test_ffi_load_font() {
let ctx = 0;
let doc = 0;
let rdb = 0;
let obj = 0;
let font = pdf_load_font(ctx, doc, rdb, obj);
assert!(font > 0);
pdf_drop_font(ctx, font);
}
#[test]
fn test_ffi_type3_font() {
let ctx = 0;
let doc = 0;
let font = pdf_load_type3_font(ctx, doc, 0, 0);
assert!(font > 0);
pdf_load_type3_glyphs(ctx, doc, font);
pdf_drop_font(ctx, font);
}
#[test]
fn test_clean_font_name() {
let name = CString::new("AAAAAA+Helvetica").unwrap();
let cleaned = pdf_clean_font_name(name.as_ptr());
assert!(!cleaned.is_null());
unsafe {
assert_eq!(CStr::from_ptr(cleaned).to_str().unwrap(), "Helvetica");
pdf_font_free_string(0, cleaned as *mut c_char);
}
}
#[test]
fn test_hail_mary_font() {
let ctx = 0;
let doc = 0;
let font = pdf_load_hail_mary_font(ctx, doc);
assert!(font > 0);
let name = pdf_font_name(ctx, font);
unsafe {
assert_eq!(CStr::from_ptr(name).to_str().unwrap(), "Helvetica");
pdf_font_free_string(ctx, name as *mut c_char);
}
pdf_drop_font(ctx, font);
}
#[test]
fn test_get_encoding_names() {
let mac = get_encoding_names(PDF_ENCODING_MAC_ROMAN);
assert!(!mac.is_empty());
assert_eq!(mac[0], ".notdef");
let win = get_encoding_names(PDF_ENCODING_WIN_ANSI);
assert!(!win.is_empty());
let def = get_encoding_names(99);
assert!(!def.is_empty());
assert_eq!(def[8], "quoteright");
}
#[test]
fn test_pdf_set_font_name_null() {
let font = pdf_new_font_desc(0);
pdf_set_font_name(0, font, std::ptr::null());
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_font_name_invalid_handle() {
assert!(pdf_font_name(0, 0).is_null());
}
#[test]
fn test_pdf_font_free_string_null() {
pdf_font_free_string(0, std::ptr::null_mut());
}
#[test]
fn test_pdf_clean_font_name_null() {
assert!(pdf_clean_font_name(std::ptr::null()).is_null());
}
#[test]
fn test_pdf_clean_font_name_prefixes() {
let b = CString::new("BAAAAA+Times-Roman").unwrap();
let cleaned = pdf_clean_font_name(b.as_ptr());
assert!(!cleaned.is_null());
unsafe {
assert_eq!(CStr::from_ptr(cleaned).to_str().unwrap(), "Times-Roman");
pdf_font_free_string(0, cleaned as *mut c_char);
}
let c = CString::new("CAAAAA+Courier").unwrap();
let cleaned2 = pdf_clean_font_name(c.as_ptr());
assert!(!cleaned2.is_null());
unsafe {
assert_eq!(CStr::from_ptr(cleaned2).to_str().unwrap(), "Courier");
pdf_font_free_string(0, cleaned2 as *mut c_char);
}
}
#[test]
fn test_substitute_font_all_combinations() {
assert_eq!(
SubstituteFont::lookup(true, true, true, true).name,
"Courier-BoldOblique"
);
assert_eq!(
SubstituteFont::lookup(true, true, true, false).name,
"Courier-Bold"
);
assert_eq!(
SubstituteFont::lookup(true, true, false, true).name,
"Courier-Oblique"
);
assert_eq!(
SubstituteFont::lookup(true, true, false, false).name,
"Courier"
);
assert_eq!(
SubstituteFont::lookup(true, false, true, true).name,
"Courier-BoldOblique"
);
assert_eq!(
SubstituteFont::lookup(false, true, true, true).name,
"Times-BoldItalic"
);
assert_eq!(
SubstituteFont::lookup(false, true, true, false).name,
"Times-Bold"
);
assert_eq!(
SubstituteFont::lookup(false, false, true, true).name,
"Helvetica-BoldOblique"
);
assert_eq!(
SubstituteFont::lookup(false, false, true, false).name,
"Helvetica-Bold"
);
assert_eq!(
SubstituteFont::lookup(false, false, false, true).name,
"Helvetica-Oblique"
);
}
#[test]
fn test_pdf_lookup_substitute_font() {
let ptr = pdf_lookup_substitute_font(0, 0, 1, 1, 1, std::ptr::null_mut());
assert!(!ptr.is_null());
unsafe {
let s = std::str::from_utf8(std::slice::from_raw_parts(ptr, 16)).unwrap();
assert_eq!(s, "Times-BoldItalic");
}
let mut len = 0i32;
let ptr2 = pdf_lookup_substitute_font(0, 0, 0, 0, 0, &mut len);
assert!(!ptr2.is_null());
assert_eq!(len, 9);
}
#[test]
fn test_pdf_add_simple_font() {
let font = pdf_new_font_desc(0);
let obj = pdf_add_simple_font(0, 0, font, PDF_ENCODING_WIN_ANSI);
assert!(obj > 0);
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_add_simple_font_invalid_handle() {
assert_eq!(pdf_add_simple_font(0, 0, 0, PDF_ENCODING_WIN_ANSI), 0);
}
#[test]
fn test_pdf_add_simple_font_embedded() {
let mut fd = FontDesc::new();
fd.name = "MyFont".to_string();
fd.is_embedded = true;
let font = FONT_DESCS.insert(fd);
let obj = pdf_add_simple_font(0, 0, font, PDF_ENCODING_MAC_ROMAN);
assert!(obj > 0);
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_add_cid_font() {
let font = pdf_new_font_desc(0);
let name = CString::new("CIDFont").unwrap();
pdf_set_font_name(0, font, name.as_ptr());
pdf_set_default_hmtx(0, font, 1000);
let obj = pdf_add_cid_font(0, 0, font);
assert!(obj > 0);
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_add_cid_font_with_hmtx() {
let font = pdf_new_font_desc(0);
pdf_add_hmtx(0, font, 0, 10, 500);
pdf_end_hmtx(0, font);
let obj = pdf_add_cid_font(0, 0, font);
assert!(obj > 0);
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_add_cid_font_invalid_handle() {
assert_eq!(pdf_add_cid_font(0, 0, 0), 0);
}
#[test]
fn test_pdf_add_cjk_font() {
let font = pdf_new_font_desc(0);
let obj = pdf_add_cjk_font(0, 0, font, PDF_CJK_JAPAN1, 0, 1);
assert!(obj > 0);
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_add_cjk_font_all_scripts() {
let font = pdf_new_font_desc(0);
pdf_set_font_name(0, font, CString::new("Test").unwrap().as_ptr());
assert!(pdf_add_cjk_font(0, 0, font, PDF_CJK_CNS1, 1, 0) > 0);
assert!(pdf_add_cjk_font(0, 0, font, PDF_CJK_GB1, 0, 1) > 0);
assert!(pdf_add_cjk_font(0, 0, font, PDF_CJK_KOREA1, 1, 0) > 0);
assert!(pdf_add_cjk_font(0, 0, font, 99, 0, 0) > 0);
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_add_cjk_font_invalid_handle() {
assert_eq!(pdf_add_cjk_font(0, 0, 0, PDF_CJK_JAPAN1, 0, 0), 0);
}
#[test]
fn test_pdf_add_substitute_font() {
let font = pdf_new_font_desc(0);
pdf_set_font_flags(0, font, PDF_FD_SERIF | PDF_FD_ITALIC);
let obj = pdf_add_substitute_font(0, 0, font);
assert!(obj > 0);
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_add_substitute_font_invalid_handle() {
assert_eq!(pdf_add_substitute_font(0, 0, 0), 0);
}
#[test]
fn test_pdf_set_cid_to_gid_null() {
let font = pdf_new_font_desc(0);
pdf_set_cid_to_gid(0, font, std::ptr::null(), 0);
pdf_set_cid_to_gid(0, font, std::ptr::null(), 5);
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_set_cid_to_ucs() {
let font = pdf_new_font_desc(0);
let ucs_table: Vec<u16> = vec![0, 0x0041, 0x0042];
pdf_set_cid_to_ucs(0, font, ucs_table.as_ptr(), ucs_table.len());
assert_eq!(pdf_font_cid_to_unicode(0, font, 1), 0x0041);
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_set_cid_to_ucs_null() {
let font = pdf_new_font_desc(0);
pdf_set_cid_to_ucs(0, font, std::ptr::null(), 0);
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_lookup_hmtx_invalid_handle() {
let m = pdf_lookup_hmtx(0, 0, 65);
assert_eq!(m.w, 0);
}
#[test]
fn test_pdf_lookup_vmtx_invalid_handle() {
let m = pdf_lookup_vmtx(0, 0, 65);
assert_eq!(m.w, 0);
}
#[test]
fn test_pdf_font_cid_to_gid_invalid_handle() {
assert_eq!(pdf_font_cid_to_gid(0, 0, 0), 0);
}
#[test]
fn test_pdf_font_cid_to_unicode_invalid_handle() {
assert_eq!(pdf_font_cid_to_unicode(0, 0, 0), 0);
}
#[test]
fn test_pdf_font_is_embedded() {
let mut fd = FontDesc::new();
fd.is_embedded = true;
let font = FONT_DESCS.insert(fd);
assert_eq!(pdf_font_is_embedded(0, font), 1);
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_font_metrics_invalid_handle() {
assert_eq!(pdf_font_ascent(0, 0), 0.0);
assert_eq!(pdf_font_descent(0, 0), 0.0);
assert_eq!(pdf_font_cap_height(0, 0), 0.0);
assert_eq!(pdf_font_x_height(0, 0), 0.0);
assert_eq!(pdf_font_missing_width(0, 0), 0.0);
assert_eq!(pdf_font_italic_angle(0, 0), 0.0);
assert_eq!(pdf_font_flags(0, 0), 0);
}
#[test]
fn test_font_desc_update_size() {
let mut fd = FontDesc::new();
fd.name = "Test".to_string();
fd.update_size();
assert!(fd.size > 0);
}
#[test]
fn test_cid_to_gid_negative() {
let fd = FontDesc::new();
assert_eq!(fd.cid_to_gid(-1), 0);
}
#[test]
fn test_cid_to_unicode_negative() {
let fd = FontDesc::new();
assert_eq!(fd.cid_to_unicode(-1), 0);
}
#[test]
fn test_pdf_load_font_with_obj() {
use crate::ffi::pdf_object::types::{PDF_OBJECTS, PdfObj, PdfObjType};
let mut entries = vec![
("BaseFont".to_string(), PdfObj::new_name("Times-Roman")),
("Subtype".to_string(), PdfObj::new_name("Type1")),
("FirstChar".to_string(), PdfObj::new_int(0)),
("LastChar".to_string(), PdfObj::new_int(3)),
(
"Widths".to_string(),
PdfObj {
obj_type: PdfObjType::Array(vec![
PdfObj::new_int(500),
PdfObj::new_int(600),
PdfObj::new_int(700),
PdfObj::new_int(800),
]),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
},
),
];
let mut desc_entries = vec![
("FontName".to_string(), PdfObj::new_name("Times-Roman")),
("Flags".to_string(), PdfObj::new_int(0)),
("ItalicAngle".to_string(), PdfObj::new_int(0)),
("Ascent".to_string(), PdfObj::new_real(800.0)),
("Descent".to_string(), PdfObj::new_real(-200.0)),
];
entries.push((
"FontDescriptor".to_string(),
PdfObj {
obj_type: PdfObjType::Dict(desc_entries),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
},
));
entries.push(("Encoding".to_string(), PdfObj::new_name("WinAnsiEncoding")));
let obj = PDF_OBJECTS.insert(PdfObj {
obj_type: PdfObjType::Dict(entries),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
});
let font = pdf_load_font(0, 0, 0, obj);
assert!(font > 0);
let name = pdf_font_name(0, font);
unsafe {
assert_eq!(CStr::from_ptr(name).to_str().unwrap(), "Times-Roman");
pdf_font_free_string(0, name as *mut c_char);
}
assert_eq!(pdf_font_ascent(0, font), 800.0);
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_print_font() {
let font = pdf_new_font_desc(0);
pdf_set_font_name(0, font, CString::new("TestFont").unwrap().as_ptr());
pdf_print_font(0, 0, font);
pdf_drop_font(0, font);
}
#[test]
fn test_pdf_font_writing_supported() {
assert_eq!(pdf_font_writing_supported(0, 0), 1);
}
}