#[cfg(feature = "agl")]
use super::agl;
use alloc::vec::Vec;
use types::GlyphId;
const VARIANT_BIT: u32 = 0x80000000;
#[derive(Clone, Default, Debug)]
pub struct UnicodeCharmap {
mapping: Vec<(u32, GlyphId)>,
}
impl UnicodeCharmap {
#[cfg(feature = "agl")]
pub fn from_glyph_names<'a>(pairs: impl Iterator<Item = (GlyphId, &'a str)>) -> Self {
#[derive(Copy, Clone, PartialEq)]
enum State {
Unchecked,
Include,
Exclude,
}
let mut extra_glyphs = [(State::Unchecked, GlyphId::NOTDEF); 10];
let mut mapping = Vec::new();
for (gid, name) in pairs {
if let Some(n) = EXTRA_GLYPH_LIST
.iter()
.position(|(_, extra_name)| name == *extra_name)
{
let extra = &mut extra_glyphs[n];
if extra.0 == State::Unchecked {
extra.0 = State::Include;
extra.1 = gid;
}
}
let ch = unicode_value(name);
if ch & !VARIANT_BIT == 0 {
continue;
}
if let Some(n) = EXTRA_GLYPH_LIST
.iter()
.position(|(extra_ch, _)| ch == *extra_ch)
{
extra_glyphs[n].0 = State::Exclude;
}
mapping.push((ch, gid));
}
for ((extra_ch, _), (state, gid)) in EXTRA_GLYPH_LIST.iter().zip(extra_glyphs) {
if state == State::Include {
mapping.push((*extra_ch, gid));
}
}
mapping.shrink_to_fit();
mapping.sort_by_key(|(ch, _)| (ch & !VARIANT_BIT, *ch));
Self { mapping }
}
pub fn map(&self, ch: impl Into<u32>) -> Option<GlyphId> {
self.search(ch.into(), false).0
}
pub(crate) fn next_char(&self, ch: u32) -> Option<(u32, GlyphId)> {
let ch = ch.checked_add(1)?;
match self.search(ch, true) {
(Some(glyph), _) => Some((ch, glyph)),
(None, min) => self
.mapping
.get(min)
.map(|(next, glyph)| (next & !VARIANT_BIT, *glyph)),
}
}
fn search(&self, ch: u32, wrapping_index: bool) -> (Option<GlyphId>, usize) {
let entries = &self.mapping;
let (mut min, mut max) = (0, entries.len());
let mut mid = min + ((max - min) >> 1);
let mut result = None;
while min < max {
let Some(&(value, glyph)) = entries.get(mid) else {
break;
};
if value == ch {
return (Some(glyph), min);
}
let base = value & !VARIANT_BIT;
if base == ch {
result = Some(glyph);
}
if base < ch {
min = mid + 1;
} else {
max = mid;
}
let step = ch.wrapping_sub(base);
mid = if wrapping_index || cfg!(target_pointer_width = "32") {
(mid as u32).wrapping_add(step) as usize
} else {
mid.saturating_add(step as usize)
};
if mid >= max || mid < min {
mid = min + ((max - min) >> 1);
}
}
(result, min)
}
pub fn iter(&self) -> impl Iterator<Item = (u32, GlyphId)> + Clone + '_ {
self.mapping.iter().copied()
}
}
#[cfg(feature = "agl")]
fn unicode_value(name: &str) -> u32 {
fn hex_digits(bytes: &[u8], max: usize) -> (u32, usize) {
let mut value = 0;
let mut count = 0;
for &b in bytes.iter().take(max) {
let digit = match b {
b'0'..=b'9' => b - b'0',
b'A'..=b'F' => b - b'A' + 10,
_ => break,
};
value = (value << 4) | digit as u32;
count += 1;
}
(value, count)
}
fn ended(value: u32, rest: &[u8]) -> Option<u32> {
match rest.first() {
None => Some(value),
Some(b'.') => Some(value | VARIANT_BIT),
_ => None,
}
}
let bytes = name.as_bytes();
if let Some(digits) = bytes.strip_prefix(b"uni") {
let (value, count) = hex_digits(digits, 4);
if let Some(value) = (count == 4)
.then(|| ended(value, &digits[count..]))
.flatten()
{
return value;
}
}
if let Some(digits) = bytes.strip_prefix(b"u") {
let (value, count) = hex_digits(digits, 6);
if let Some(value) = (count >= 4)
.then(|| ended(value, &digits[count..]))
.flatten()
{
return value;
}
}
let base = name.split('.').next().unwrap_or_default();
if base.is_empty() {
return 0;
}
let value = agl::lookup_char(base).map_or(0, |ch| ch as u32);
if base.len() < name.len() {
value | VARIANT_BIT
} else {
value
}
}
#[cfg(feature = "agl")]
#[rustfmt::skip]
const EXTRA_GLYPH_LIST: [(u32, &str); 10] = [
(0x0394, "Delta"),
(0x03A9, "Omega"),
(0x2215, "fraction"),
(0x00AD, "hyphen"),
(0x02C9, "macron"),
(0x03BC, "mu"),
(0x2219, "periodcentered"),
(0x00A0, "space"),
(0x021A, "Tcommaaccent"),
(0x021B, "tcommaaccent"),
];
#[cfg(test)]
#[cfg(feature = "agl")]
mod tests {
use super::super::type1::Type1Font;
use super::*;
#[test]
fn cmap() {
let cmap = UnicodeCharmap::from_glyph_names(
[
(1, "A"),
(2, "uni0042"), (333, "C.alt"),
(4, "D"),
(51, "Lcedilla"),
(22, "Cdot"),
(8, "aacute"),
(7, "union"),
]
.map(|(gid, name)| (GlyphId::new(gid), name))
.iter()
.copied(),
);
assert_eq!(cmap.map('A'), Some(GlyphId::new(1)));
assert_eq!(cmap.map('B'), Some(GlyphId::new(2)));
assert_eq!(cmap.map('C'), Some(GlyphId::new(333)));
assert_eq!(cmap.map('D'), Some(GlyphId::new(4)));
assert_eq!(cmap.map('Ä»'), Some(GlyphId::new(51)));
assert_eq!(cmap.map('ÄŠ'), Some(GlyphId::new(22)));
assert_eq!(cmap.map('á'), Some(GlyphId::new(8)));
assert_eq!(cmap.map('∪'), Some(GlyphId::new(7)));
}
#[test]
fn cmap_from_type1() {
let font = Type1Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFB).unwrap();
let cmap = UnicodeCharmap::from_glyph_names(font.glyph_names());
let expected = [
('H' as u32, 1),
('H' as u32 | VARIANT_BIT, 8),
('f' as u32, 2),
('i' as u32, 3),
('x' as u32, 4),
];
let result = cmap
.iter()
.map(|(ch, gid)| (ch, gid.to_u32()))
.collect::<Vec<_>>();
assert_eq!(result, expected);
assert_eq!(cmap.map('a'), None);
for (ch, gid) in expected {
if ch & VARIANT_BIT != 0 {
assert_eq!(cmap.map(ch), None);
} else {
assert_eq!(
cmap.map(ch),
Some(GlyphId::new(gid)),
"cmap failed for {ch} -> {gid}"
);
}
}
}
#[test]
fn extra_glyphs_override() {
let cmap = UnicodeCharmap::from_glyph_names([(GlyphId::new(1), "hyphen")].into_iter());
assert_eq!(cmap.mapping.len(), 2);
assert_eq!(cmap.map('\u{00AD}'), Some(GlyphId::new(1)));
assert_eq!(cmap.map('\u{002D}'), Some(GlyphId::new(1)));
}
#[test]
fn extra_glyphs_no_override() {
let cmap = UnicodeCharmap::from_glyph_names([(GlyphId::new(1), "softhyphen")].into_iter());
assert_eq!(cmap.mapping.len(), 1);
assert_eq!(cmap.map('\u{00AD}'), Some(GlyphId::new(1)));
}
#[test]
fn names_convert_as_freetype_converts_them() {
const V: u32 = VARIANT_BIT;
for (name, value) in [
("uni0041", 0x41),
("uni0041.alt", 0x41 | V),
("u0041.alt.sc", 0x41 | V),
("A.", 0x41 | V),
("A..B", 0x41 | V),
("union", 0x222A),
("a1", 0x2701),
("a191.alt", 0x27BE | V),
("uniD800", 0xD800),
("u110000", 0x110000),
("uFFFFFF", 0xFFFFFF),
("u1F600", 0x1F600),
("uni0000", 0),
("u0000", 0),
("u0000041", 0),
("uni00410042", 0),
("uni0041x", 0),
("f_i", 0),
("dalethatafpatah", 0),
("eigujarati", 0),
(".notdef", 0),
] {
assert_eq!(unicode_value(name), value, "{name}");
}
}
fn walk(cmap: &UnicodeCharmap) -> Vec<(u32, u32)> {
let mut entries = Vec::new();
let mut code = 0;
while let Some((next, glyph)) = cmap.next_char(code) {
if glyph == GlyphId::NOTDEF {
break;
}
entries.push((next, glyph.to_u32()));
code = next;
}
entries
}
#[test]
fn search_and_walk_match_freetype() {
const V: u32 = VARIANT_BIT;
type Case<'a> = (&'a str, &'a [(u32, u32)], &'a [(u32, u32)]);
let cases: [Case; 9] = [
(".notdef A A", &[(0x41, 2)], &[(0x41, 1)]),
(".notdef D D D D D", &[(0x44, 3)], &[(0x44, 1)]),
(".notdef C.alt C.swash C.alt", &[(0x43, 1)], &[(0x43, 1)]),
(
".notdef A.alt A.sc A",
&[(0x41, 3), (0x41 | V, 1)],
&[(0x41, 3)],
),
(
".notdef A.alt A.alt A.alt A.alt B",
&[(0x41, 1), (0x41 | V, 3), (0x42, 5)],
&[(0x41, 1), (0x42, 5)],
),
("space A B", &[(0x41, 1), (0x42, 2)], &[]),
(
".notdef uniD800 u110000 uni0000 A",
&[(0x41, 4), (0xD800, 1), (0x110000, 2), (0, 0)],
&[(0x41, 4), (0xD800, 1), (0x110000, 2)],
),
(
".notdef hyphen x a1 spade",
&[(0x2D, 1), (0xAD, 1), (0x2701, 3), (0x2660, 4)],
&[(0x2D, 1), (0x78, 2), (0xAD, 1), (0x2660, 4), (0x2701, 3)],
),
(
".notdef A uni0041 B uni0042 C uni0043 G uni0047 H",
if cfg!(target_pointer_width = "32") {
&[
(0x41, 2),
(0x42, 4),
(0x43, 5),
(0x44, 0),
(0x47, 8),
(0x48, 9),
]
} else {
&[
(0x41, 2),
(0x42, 3),
(0x43, 5),
(0x44, 0),
(0x47, 7),
(0x48, 9),
]
},
&[(0x41, 1), (0x42, 4), (0x43, 5), (0x47, 7), (0x48, 9)],
),
];
for (names, mapped, walked) in cases {
let cmap = UnicodeCharmap::from_glyph_names(
names
.split(' ')
.enumerate()
.map(|(glyph, name)| (GlyphId::new(glyph as u32), name)),
);
for &(ch, glyph) in mapped {
assert_eq!(
cmap.map(ch).map_or(0, |glyph| glyph.to_u32()),
glyph,
"{names}: {ch:x}"
);
}
assert_eq!(walk(&cmap), walked, "{names}");
}
}
}