read_fonts/tables/postscript/
fd_select.rs

1//! Parsing for CFF FDSelect tables.
2
3use types::GlyphId;
4
5use super::FdSelect;
6
7impl FdSelect<'_> {
8    /// Returns the associated font DICT index for the given glyph identifier.
9    pub fn font_index(&self, glyph_id: GlyphId) -> Option<u16> {
10        match self {
11            // See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-11-fdselect-format-0>
12            Self::Format0(fds) => fds
13                .fds()
14                .get(glyph_id.to_u32() as usize)
15                .map(|fd| *fd as u16),
16            // See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-12-fdselect-format-3>
17            Self::Format3(fds) => {
18                let ranges = fds.ranges();
19                let gid = glyph_id.to_u32();
20                let ix = match ranges.binary_search_by(|range| (range.first() as u32).cmp(&gid)) {
21                    Ok(ix) => ix,
22                    Err(ix) => ix.saturating_sub(1),
23                };
24                Some(ranges.get(ix)?.fd() as u16)
25            }
26            // See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-14-fdselect-format-4>
27            Self::Format4(fds) => {
28                let ranges = fds.ranges();
29                let gid = glyph_id.to_u32();
30                let ix = match ranges.binary_search_by(|range| range.first().cmp(&gid)) {
31                    Ok(ix) => ix,
32                    Err(ix) => ix.saturating_sub(1),
33                };
34                Some(ranges.get(ix)?.fd())
35            }
36        }
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use font_test_data::bebuffer::BeBuffer;
43
44    use super::{FdSelect, GlyphId};
45    use crate::FontRead;
46    use std::ops::Range;
47
48    #[test]
49    fn select_font_index() {
50        let map = &[
51            (0..10, 0),
52            (10..32, 4),
53            (32..34, 1),
54            (34..128, 12),
55            (128..1024, 2),
56        ];
57        for data in make_fd_selects(map) {
58            let fd_select = FdSelect::read(data.data().into()).unwrap();
59            for (range, font_index) in map {
60                for gid in range.clone() {
61                    assert_eq!(
62                        fd_select.font_index(GlyphId::from(gid)).unwrap() as u8,
63                        *font_index
64                    )
65                }
66            }
67        }
68    }
69
70    /// Builds FDSelect structures in all three formats for the given
71    /// Range<GID> -> font index mapping.
72    fn make_fd_selects(map: &[(Range<u16>, u8)]) -> [BeBuffer; 3] {
73        let glyph_count = map.last().unwrap().0.end;
74        let format0 = {
75            let mut buf = BeBuffer::new();
76            buf = buf.push(0u8);
77            let mut fds = vec![0u8; glyph_count as usize];
78            for (range, font_index) in map {
79                for gid in range.clone() {
80                    fds[gid as usize] = *font_index;
81                }
82            }
83            buf = buf.extend(fds);
84            buf
85        };
86        let format3 = {
87            let mut buf = BeBuffer::new();
88            buf = buf.push(3u8);
89            buf = buf.push(map.len() as u16);
90            for (range, font_index) in map {
91                buf = buf.push(range.start);
92                buf = buf.push(*font_index);
93            }
94            buf = buf.push(glyph_count);
95            buf
96        };
97        let format4 = {
98            let mut buf = BeBuffer::new();
99            buf = buf.push(4u8);
100            buf = buf.push(map.len() as u32);
101            for (range, font_index) in map {
102                buf = buf.push(range.start as u32);
103                buf = buf.push(*font_index as u16);
104            }
105            buf = buf.push(glyph_count as u32);
106            buf
107        };
108        [format0, format3, format4]
109    }
110}