Skip to main content

read_fonts/ps/cff/
fd_select.rs

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