use alloc::string::String;
use alloc::vec::Vec;
use pith_digest::{Error, Result};
use crate::cmap::{CMap, code_of_pub as code_of};
use crate::object::{Obj, Ref, decode_stream, dict_get};
use crate::tables;
#[derive(Copy, Clone, Debug)]
pub(crate) enum Glyph {
S(&'static str),
C(char),
}
pub(crate) struct Font {
pub(crate) cid: bool,
pub(crate) vertical: bool,
pub(crate) touni: Option<CMap>,
pub(crate) code_lens: Vec<usize>,
pub(crate) enc: Option<[Option<Glyph>; 256]>,
}
pub(crate) trait Resolve {
fn deref(&self, r: Ref) -> Result<Obj>;
}
pub(crate) fn build<R: Resolve>(
dict: &[(Vec<u8>, Obj)],
res: &R,
limits: &pith_inflate::Limits,
) -> Result<Font> {
let subtype = match dict_get(dict, b"Subtype") {
Some(Obj::Name(n)) => n.as_slice(),
_ => return Err(Error::BadValue("font /Subtype")),
};
let base = dict_get(dict, b"BaseFont")
.and_then(|o| o.as_bytes())
.unwrap_or(b"")
.to_vec();
let touni = load_tounicode(dict, res, limits)?;
if subtype == b"Type0" {
return build_cid(dict, res, limits, touni);
}
if !matches!(subtype, b"Type1" | b"MMType1" | b"TrueType" | b"Type3") {
return Err(Error::Unsupported("font subtype"));
}
let mut table = simple_base_table(&base, subtype);
if let Some(enc) = dict_get(dict, b"Encoding") {
apply_encoding(&mut table, enc, res, limits)?;
}
Ok(Font {
cid: false,
vertical: false,
touni,
code_lens: alloc::vec![1],
enc: Some(table),
})
}
fn simple_base_table(base: &[u8], subtype: &[u8]) -> [Option<Glyph>; 256] {
if subtype == b"Type3" {
return fill(tables::STANDARD_ENCODING);
}
match base {
b"Symbol" => fill(tables::SYMBOL_ENCODING),
b"ZapfDingbats" => fill(tables::ZAPFDINGBATS_ENCODING),
_ => fill(tables::WINANSI_ENCODING),
}
}
fn fill(t: &'static [(u8, &'static str)]) -> [Option<Glyph>; 256] {
let mut out = [None; 256];
for &(c, s) in t {
out[c as usize] = Some(Glyph::S(s));
}
out
}
fn apply_encoding<R: Resolve>(
table: &mut [Option<Glyph>; 256],
enc: &Obj,
res: &R,
_limits: &pith_inflate::Limits,
) -> Result<()> {
let enc = match enc {
Obj::Ref(r) => res.deref(*r)?,
other => other.clone(),
};
match &enc {
Obj::Name(n) => {
*table = named_table(n.as_slice()).ok_or(Error::Unsupported("encoding name"))?;
}
Obj::Dict(d) => {
if let Some(b) = dict_get(d, b"BaseEncoding") {
let n = b.as_bytes().ok_or(Error::BadValue("/BaseEncoding"))?;
*table = named_table(n).ok_or(Error::Unsupported("BaseEncoding name"))?;
}
if let Some(Obj::Arr(diff)) = dict_get(d, b"Differences") {
let mut code = 0usize;
for o in diff {
match o {
Obj::Num(v) => {
let c = crate::lex::f64_as_i64(*v)
.ok_or(Error::BadValue("/Differences number"))?;
if !(0..=255).contains(&c) {
return Err(Error::BadValue("/Differences number"));
}
code = c as usize;
}
Obj::Name(n) => {
if code > 255 {
return Err(Error::BadValue("/Differences range"));
}
table[code] = glyph_lookup(n.as_slice());
code += 1;
}
_ => return Err(Error::BadValue("/Differences element")),
}
}
}
}
_ => return Err(Error::BadValue("/Encoding type")),
}
Ok(())
}
fn named_table(name: &[u8]) -> Option<[Option<Glyph>; 256]> {
Some(match name {
b"StandardEncoding" => fill(tables::STANDARD_ENCODING),
b"WinAnsiEncoding" => fill(tables::WINANSI_ENCODING),
b"MacRomanEncoding" => fill(tables::MACROMAN_ENCODING),
b"SymbolEncoding" => fill(tables::SYMBOL_ENCODING),
b"ZapfDingbatsEncoding" => fill(tables::ZAPFDINGBATS_ENCODING),
b"PDFDocEncoding" => fill(tables::PDFDOC_ENCODING),
_ => return None,
})
}
fn glyph_lookup(name: &[u8]) -> Option<Glyph> {
if let Some(rest) = name.strip_prefix(b"uni") {
if rest.len() >= 4 {
let mut v: u32 = 0;
let mut ok = true;
for &b in rest.iter().take(4) {
match crate::lex::hex_val(b) {
Some(d) => v = v * 16 + u32::from(d),
None => {
ok = false;
break;
}
}
}
if ok {
if let Some(c) = char::from_u32(v) {
return Some(Glyph::C(c));
}
}
}
}
if let Some(rest) = name.strip_prefix(b"u") {
if rest.len() >= 4 && rest.len() <= 6 {
let mut v: u32 = 0;
let mut ok = true;
for &b in rest {
match crate::lex::hex_val(b) {
Some(d) => v = v * 16 + u32::from(d),
None => {
ok = false;
break;
}
}
}
if ok {
if let Some(c) = char::from_u32(v) {
return Some(Glyph::C(c));
}
}
}
}
tables::glyph_unicode(name).map(Glyph::S)
}
fn build_cid<R: Resolve>(
dict: &[(Vec<u8>, Obj)],
res: &R,
limits: &pith_inflate::Limits,
touni: Option<CMap>,
) -> Result<Font> {
match dict_get(dict, b"DescendantFonts") {
Some(Obj::Arr(a)) if !a.is_empty() => {
let d = match &a[0] {
Obj::Ref(r) => res.deref(*r)?,
other => other.clone(),
};
match d.get(b"Subtype") {
Some(Obj::Name(n))
if n.as_slice() == b"CIDFontType0" || n.as_slice() == b"CIDFontType2" => {}
Some(_) => return Err(Error::Unsupported("descendant font subtype")),
None => return Err(Error::BadValue("CID font descendant")),
}
}
_ => return Err(Error::BadValue("CID font /DescendantFonts")),
}
let mut code_lens: Vec<usize> = alloc::vec![2];
let mut vertical = false;
match dict_get(dict, b"Encoding") {
None | Some(Obj::Null) => {}
Some(Obj::Name(n)) => match n.as_slice() {
b"Identity-H" => {}
b"Identity-V" => vertical = true,
_ => {
}
},
Some(enc @ Obj::Ref(_)) | Some(enc @ Obj::Stream { .. }) => {
let enc = match enc {
Obj::Ref(r) => res.deref(*r)?,
other => other.clone(),
};
let raw =
decode_stream(&enc, limits).map_err(|_| Error::BadValue("CMap stream decode"))?;
let cmap = CMap::parse(&raw).map_err(|_| Error::BadValue("CMap stream parse"))?;
vertical = cmap.is_vertical();
code_lens = cmap_lens(&cmap);
if code_lens.is_empty() {
code_lens.push(2);
}
}
Some(_) => return Err(Error::BadValue("/Encoding for CID font")),
}
if let Some(t) = touni.as_ref() {
for &l in &cmap_lens(t) {
if !code_lens.contains(&l) {
code_lens.push(l);
}
}
code_lens.sort_unstable_by(|a, b| b.cmp(a));
}
if touni.is_none() {
return Err(Error::Unsupported("CID font without /ToUnicode"));
}
Ok(Font {
cid: true,
vertical,
touni,
code_lens,
enc: None,
})
}
fn cmap_lens(c: &CMap) -> Vec<usize> {
c.lens().to_vec()
}
fn load_tounicode<R: Resolve>(
dict: &[(Vec<u8>, Obj)],
res: &R,
limits: &pith_inflate::Limits,
) -> Result<Option<CMap>> {
let o = match dict_get(dict, b"ToUnicode") {
None | Some(Obj::Null) => return Ok(None),
Some(o) => o.clone(),
};
let o = match o {
Obj::Ref(r) => res.deref(r)?,
other => other,
};
if !matches!(o, Obj::Stream { .. }) {
return Err(Error::BadValue("/ToUnicode is not a stream"));
}
let raw = decode_stream(&o, limits).map_err(|_| Error::BadValue("/ToUnicode stream decode"))?;
let cmap = CMap::parse(&raw).map_err(|_| Error::BadValue("/ToUnicode parse"))?;
Ok(Some(cmap))
}
impl Font {
pub(crate) fn decode(&self, bytes: &[u8]) -> String {
let mut out = String::new();
if self.cid {
let mut pos = 0usize;
while pos < bytes.len() {
let mut matched = false;
if let Some(t) = self.touni.as_ref() {
for &l in &self.code_lens {
if l <= bytes.len() - pos {
let code = code_of(&bytes[pos..pos + l]);
if let Some(s) = t.get(code, l) {
out.push_str(s);
pos += l;
matched = true;
break;
}
}
}
}
if !matched {
let step = self
.code_lens
.last()
.copied()
.unwrap_or(1)
.min(bytes.len() - pos);
out.push(char::REPLACEMENT_CHARACTER);
pos += step;
}
}
return out;
}
for &b in bytes {
if let Some(t) = self.touni.as_ref() {
if let Some(s) = t.get(u64::from(b), 1) {
out.push_str(s);
continue;
}
}
match self.enc.as_ref().and_then(|t| t[b as usize]) {
Some(Glyph::S(s)) => out.push_str(s),
Some(Glyph::C(c)) => out.push(c),
None => out.push(char::REPLACEMENT_CHARACTER),
}
}
out
}
}
#[allow(dead_code)]
pub(crate) fn debug_lens(f: &Font) -> &[usize] {
&f.code_lens
}