1
#![deny(missing_docs)]
2

            
3
//! Utilities for accessing glyph information such as advance.
4

            
5
use std::borrow::Cow;
6
use std::collections::HashMap;
7
use std::sync::Arc;
8

            
9
use ouroboros::self_referencing;
10
use rustc_hash::FxHashMap;
11

            
12
use crate::binary::read::ReadScope;
13
use crate::error::ParseError;
14
use crate::font::Encoding;
15
use crate::macroman::macroman_to_char;
16
use crate::post::PostTable;
17
use crate::tables::cmap::CmapSubtable;
18
use crate::tables::{HheaTable, HmtxTable, MaxpTable};
19
use crate::GlyphId;
20

            
21
/// Retrieve glyph advance.
22
///
23
/// Since the `hhea` and `vhea` tables share the same format this function will return horizontal
24
/// or vertical advance depending on whether `hhea` or `vhea` is supplied to the `hhea` argument.
25
526122
pub fn advance(
26
526122
    maxp: &MaxpTable,
27
526122
    hhea: &HheaTable,
28
526122
    hmtx_data: &[u8],
29
526122
    glyph: GlyphId,
30
526122
) -> Result<u16, ParseError> {
31
    // Avoid parsing hmtx in this case
32
526122
    if i32::from(glyph) > i32::from(maxp.num_glyphs) - 1 {
33
        return Ok(0);
34
526122
    }
35

            
36
526122
    let glyph = usize::from(glyph);
37
526122
    let num_glyphs = usize::from(maxp.num_glyphs);
38
526122
    let num_metrics = usize::from(hhea.num_h_metrics);
39
526122
    let hmtx = ReadScope::new(hmtx_data).read_dep::<HmtxTable<'_>>((num_glyphs, num_metrics))?;
40

            
41
503712
    if glyph < num_metrics {
42
503712
        Ok(hmtx
43
503712
            .h_metrics
44
503712
            .get_item(glyph)
45
503712
            .map_or(0, |x| x.advance_width))
46
    } else if num_metrics > 0 {
47
        Ok(hmtx
48
            .h_metrics
49
            .get_item(num_metrics - 1)
50
            .map_or(0, |metrics| metrics.advance_width))
51
    } else {
52
        Err(ParseError::BadIndex)
53
    }
54
526122
}
55

            
56
#[self_referencing]
57
struct Post {
58
    data: Box<[u8]>,
59
    #[borrows(data)]
60
    #[not_covariant]
61
    table: PostTable<'this>,
62
}
63

            
64
/// Structure for looking up glyph names.
65
pub struct GlyphNames {
66
    post: Option<Post>,
67
    cmap: Option<CmapMappings>,
68
}
69

            
70
struct CmapMappings {
71
    encoding: Encoding,
72
    mappings: HashMap<u16, u32>,
73
}
74

            
75
impl GlyphNames {
76
    /// Construct a new `GlyphNames` instance.
77
    pub fn new(
78
        cmap_subtable: &Option<(Encoding, CmapSubtable<'_>)>,
79
        post_data: Option<Box<[u8]>>,
80
    ) -> Self {
81
        let post = post_data.and_then(|data| {
82
            PostTryBuilder {
83
                data,
84
                table_builder: |data| ReadScope::new(data).read::<PostTable<'_>>(),
85
            }
86
            .try_build()
87
            .ok()
88
        });
89
        let cmap = cmap_subtable
90
            .as_ref()
91
            .and_then(|(encoding, subtable)| CmapMappings::new(*encoding, subtable));
92
        GlyphNames { post, cmap }
93
    }
94

            
95
    /// Look up the name of `gid` in the `post` and `cmap` tables.
96
    pub fn glyph_name<'a>(&self, gid: GlyphId) -> Cow<'a, str> {
97
        // Glyph 0 is always .notdef
98
        if gid == 0 {
99
            return Cow::from(".notdef");
100
        }
101

            
102
        self.glyph_name_from_post(gid)
103
            .or_else(|| self.glyph_name_from_cmap(gid))
104
            .unwrap_or_else(|| Cow::from(format!("g{}", gid)))
105
    }
106

            
107
    /// Determine the set of unique glyph names for the supplied glyph ids.
108
    pub fn unique_glyph_names<'a>(&self, ids: &[GlyphId]) -> Vec<Cow<'a, str>> {
109
        unique_glyph_names(ids.iter().map(|&gid| self.glyph_name(gid)), ids.len())
110
    }
111

            
112
    fn glyph_name_from_post<'a>(&self, gid: GlyphId) -> Option<Cow<'a, str>> {
113
        let post = self.post.as_ref()?;
114
        post.glyph_name(gid)
115
    }
116

            
117
    fn glyph_name_from_cmap<'a>(&self, gid: GlyphId) -> Option<Cow<'a, str>> {
118
        let cmap = self.cmap.as_ref()?;
119
        cmap.glyph_name(gid)
120
    }
121
}
122

            
123
fn unique_glyph_names<'a>(
124
    names: impl Iterator<Item = Cow<'a, str>>,
125
    capacity: usize,
126
) -> Vec<Cow<'a, str>> {
127
    let mut seen = FxHashMap::with_capacity_and_hasher(capacity, Default::default());
128
    let mut unique_names = Vec::with_capacity(capacity);
129

            
130
    for name in names.map(Arc::new) {
131
        let alt = *seen
132
            .entry(Arc::clone(&name))
133
            .and_modify(|alt| *alt += 1)
134
            .or_insert(0);
135
        let unique_name = if alt == 0 {
136
            name
137
        } else {
138
            // name is not unique, generate a new name for it
139
            Arc::new(Cow::from(format!("{}.alt{:02}", name, alt)))
140
        };
141

            
142
        unique_names.push(unique_name)
143
    }
144
    drop(seen);
145

            
146
    // NOTE(unwrap): Safe as `seen` is the only other thing that holds a reference
147
    // to name and it's been dropped.
148
    unique_names
149
        .into_iter()
150
        .map(|name| Arc::try_unwrap(name).unwrap())
151
        .collect()
152
}
153

            
154
impl Post {
155
    fn glyph_name<'a>(&self, gid: GlyphId) -> Option<Cow<'a, str>> {
156
        self.with_table(|post: &PostTable<'_>| {
157
            match post.glyph_name(gid) {
158
                Ok(Some(glyph_name)) if glyph_name != ".notdef" => {
159
                    // Doesn't seem possible to avoid this allocation
160
                    Some(Cow::from(glyph_name.to_owned()))
161
                }
162
                _ => None,
163
            }
164
        })
165
    }
166
}
167

            
168
impl CmapMappings {
169
    fn new(encoding: Encoding, subtable: &CmapSubtable<'_>) -> Option<CmapMappings> {
170
        let mappings = subtable.mappings().ok()?;
171

            
172
        Some(CmapMappings { encoding, mappings })
173
    }
174

            
175
    fn glyph_name<'a>(&self, gid: GlyphId) -> Option<Cow<'a, str>> {
176
        let &ch = self.mappings.get(&gid)?;
177
        match self.encoding {
178
            Encoding::AppleRoman => glyph_names::glyph_name(macroman_to_unicode(ch)?),
179
            Encoding::Unicode => glyph_names::glyph_name(ch),
180
            Encoding::Symbol => None,
181
            Encoding::Big5 => None, // FIXME
182
        }
183
    }
184
}
185

            
186
fn macroman_to_unicode(ch: u32) -> Option<u32> {
187
    macroman_to_char(ch as u8).map(|ch| ch as u32)
188
}
189

            
190
#[cfg(test)]
191
mod tests {
192
    use super::*;
193

            
194
    #[test]
195
    fn test_unique_glyph_names() {
196
        let names = vec!["A"; 3].into_iter().map(Cow::from);
197
        let unique_names = unique_glyph_names(names, 3);
198
        assert_eq!(
199
            unique_names,
200
            &[Cow::from("A"), Cow::from("A.alt01"), Cow::from("A.alt02")]
201
        );
202
    }
203
}