use super::{CidFontKind, CidToGid, Type0Font, cid_charmap};
use crate::encoding::{FaceEncoding, FontEncoding, adobe_char_name, unicode_from_adobe_name};
use crate::glyphs::Charmap;
use crate::ids::GlyphName;
use crate::{CharCode, Gid};
const COURIER_STD_OFFSET: u32 = 31;
const BOX_DRAWINGS_LIGHT_VERTICAL: u32 = 0x2502;
const NO_DIFFS: [Option<GlyphName>; 256] = [const { None }; 256];
pub(super) fn resolve(font: &Type0Font, code: CharCode) -> (Option<Gid>, bool) {
let has_stream = matches!(font.cid_to_gid, CidToGid::Stream(_));
if !font.embedded && (!has_stream || pdfrum_cmap::has_cid2unicode(font.charset)) {
return substituted(font, code);
}
embedded(font, code)
}
fn substituted(font: &Type0Font, code: CharCode) -> (Option<Gid>, bool) {
let cid = font.cid_from_charcode(code);
if font.cid_to_gid == CidToGid::Identity {
return (Some(Gid(cid.0)), false);
}
let mut unicode: u16 = 0;
if cid.0 != 0 && pdfrum_cmap::has_cid2unicode(font.charset) {
unicode = pdfrum_cmap::unicode_from_cid(font.charset, cid).map_or(0, |c| c as u16);
}
if unicode == 0 {
unicode = font.scalar_unicode(code);
}
if unicode == 0 {
unicode = font
.unicode_from_charcode(code)
.first()
.map_or(0, |c| *c as u16);
}
if unicode == 0 {
return courier_std_rescue(font, code);
}
if font.charset == pdfrum_cmap::CidSet::Japan1 {
if unicode == u16::from(b'\\') {
unicode = u16::from(b'/');
} else if unicode == 0xA5 {
unicode = 0x5C;
}
}
if !font.glyphs.is_some() {
return (Some(Gid(unicode)), false);
}
let charmaps = font.glyphs.charmaps();
let n = charmaps.len();
let mut charmap = Charmap::Unicode;
let mut lookup_code = u32::from(unicode);
if !charmaps.iter().any(|c| c.is_unicode()) {
let mut found = None;
for (i, id) in charmaps.iter().enumerate() {
let ret = id
.face_encoding()
.charcode_from_unicode((code.0 & 0xffff) as u16);
if ret != 0 {
found = Some((i, ret));
break;
}
}
match found {
Some((i, ret)) => {
charmap = Charmap::Index(i);
lookup_code = ret;
}
None if n != 0 => {
charmap = Charmap::Index(0);
lookup_code = code.0 & 0xffff;
}
None => {}
}
}
if n != 0 {
let (gid, vertical) = glyph_index(font, charmap, lookup_code);
return if gid == 0 {
(None, false)
} else {
(Some(Gid(gid)), vertical)
};
}
(Some(Gid(unicode)), false)
}
fn courier_std_rescue(font: &Type0Font, code: CharCode) -> (Option<Gid>, bool) {
if !font.adobe_courier_std {
return (nonzero_gid(code.0), false);
}
let code = CharCode(code.0.wrapping_add(COURIER_STD_OFFSET));
let charmaps = font.glyphs.charmaps();
let ms_unicode = charmaps.iter().any(|c| c.is_unicode());
let mac_roman = !ms_unicode && charmaps.contains(&crate::glyphs::CharmapId::MAC_ROMAN);
let encoding = if ms_unicode {
FontEncoding::WinAnsi
} else if mac_roman {
FontEncoding::MacRoman
} else {
FontEncoding::Standard
};
let Some(name) = adobe_char_name(encoding, &NO_DIFFS, code.0) else {
return (nonzero_gid(code.0), false);
};
let name = name.to_vec();
let nu = unicode_from_adobe_name(&name);
if nu == 0 {
return (nonzero_gid(code.0), false);
}
if encoding == FontEncoding::Standard {
let g = font.glyphs.char_index(Charmap::Unicode, u32::from(nu));
return (Some(Gid(g)), false);
}
let index = if encoding == FontEncoding::WinAnsi {
font.glyphs.char_index(Charmap::Unicode, u32::from(nu))
} else {
let mac = FaceEncoding::AppleRoman.charcode_from_unicode(nu);
if mac != 0 {
font.glyphs.char_index(Charmap::Unicode, mac)
} else {
font.glyphs.name_index(&name)
}
};
if index == 0 || index == u16::MAX {
return (nonzero_gid(code.0), false);
}
(Some(Gid(index)), false)
}
fn nonzero_gid(code: u32) -> Option<Gid> {
if code == 0 {
None
} else {
Some(Gid((code & 0xffff) as u16))
}
}
fn embedded(font: &Type0Font, code: CharCode) -> (Option<Gid>, bool) {
if !font.glyphs.is_some() {
return (None, false);
}
let cid = font.cid_from_charcode(code);
if let CidToGid::Stream(table) = &font.cid_to_gid {
let Some(pos) = usize::from(cid.0).checked_mul(2) else {
return (None, false);
};
let (Some(&hi), Some(&lo)) = (table.get(pos), table.get(pos + 1)) else {
return (None, false);
};
return (Some(Gid(u16::from(hi) * 256 + u16::from(lo))), false);
}
if font.kind == CidFontKind::Type1 {
return (Some(Gid(cid.0)), false);
}
if font.embedded && font.cmap.has_no_direct_table() {
return (Some(Gid(cid.0)), false);
}
if font.cmap.coding() == pdfrum_cmap::CidCoding::Unknown {
return (Some(Gid(cid.0)), false);
}
let charmap = cid_charmap(&font.glyphs, font.cmap.coding());
if charmap == Charmap::None {
return (Some(Gid(cid.0)), false);
}
let is_unicode_charmap = match charmap {
Charmap::Index(i) => font
.glyphs
.charmaps()
.get(i)
.is_some_and(|c| c.is_unicode()),
Charmap::Unicode => true,
Charmap::None => false,
};
let lookup = if is_unicode_charmap {
match font.unicode_from_charcode(code).first() {
Some(c) => *c as u32,
None => return (None, false),
}
} else {
code.0
};
let (gid, vertical) = glyph_index(font, charmap, lookup);
(Some(Gid(gid)), vertical)
}
fn glyph_index(font: &Type0Font, charmap: Charmap, code: u32) -> (u16, bool) {
let index = font.glyphs.char_index(charmap, code);
if code == BOX_DRAWINGS_LIGHT_VERTICAL {
return (index, false);
}
if index == 0 || !font.is_vertical() {
return (index, false);
}
match font.gsub().vertical_glyph(index) {
Some(v) => (v, true),
None => (index, false),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_zero_code_yields_no_glyph_while_a_nonzero_one_yields_itself() {
assert_eq!(nonzero_gid(0), None);
assert_eq!(nonzero_gid(31), Some(Gid(31)));
assert_eq!(nonzero_gid(287), Some(Gid(287)));
assert_eq!(nonzero_gid(0x1_0000), Some(Gid(0)));
}
#[test]
fn the_courier_offset_is_exactly_thirty_one() {
assert_eq!(COURIER_STD_OFFSET, 31);
}
#[test]
fn the_never_rotated_character_is_box_drawings_light_vertical() {
assert_eq!(BOX_DRAWINGS_LIGHT_VERTICAL, 0x2502);
}
}