/src/fontations/write-fonts/src/tables/loca.rs
Line | Count | Source |
1 | | //! The [loca (Index to Location)][loca] table |
2 | | //! |
3 | | //! [loca]: https://docs.microsoft.com/en-us/typography/opentype/spec/loca |
4 | | |
5 | | use read_fonts::TopLevelTable; |
6 | | use types::Tag; |
7 | | |
8 | | use crate::{ |
9 | | validate::{Validate, ValidationCtx}, |
10 | | FontWrite, |
11 | | }; |
12 | | |
13 | | /// The [loca] table. |
14 | | /// |
15 | | /// [loca]: https://docs.microsoft.com/en-us/typography/opentype/spec/loca |
16 | | #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] |
17 | | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
18 | | pub struct Loca { |
19 | | // we just store u32, and then convert to u16 if needed in the `FontWrite` impl |
20 | | pub(crate) offsets: Vec<u32>, |
21 | | loca_format: LocaFormat, |
22 | | } |
23 | | |
24 | | /// Whether or not the 'loca' table uses short or long offsets. |
25 | | /// |
26 | | /// This flag is stored in the 'head' table's [indexToLocFormat][locformat] field. |
27 | | /// See the ['loca' spec][spec] for more information. |
28 | | /// |
29 | | /// [locformat]: super::head::Head::index_to_loc_format |
30 | | /// [spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/loca |
31 | | #[repr(u8)] |
32 | | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] |
33 | | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
34 | | pub enum LocaFormat { |
35 | | #[default] |
36 | | Short = 0, |
37 | | Long = 1, |
38 | | } |
39 | | |
40 | | impl TopLevelTable for Loca { |
41 | | const TAG: Tag = Tag::new(b"loca"); |
42 | | } |
43 | | |
44 | | impl Loca { |
45 | | /// Create a new loca table from 32-bit offsets. |
46 | | /// |
47 | | /// The loca format will be calculated based on the raw values. |
48 | | /// |
49 | | /// You generally do not construct this directly; it is constructed alongside |
50 | | /// the corresponding 'glyf' table using the |
51 | | /// [GlyfLocaBuilder](super::glyf::GlyfLocaBuilder). |
52 | 0 | pub fn new(offsets: Vec<u32>) -> Self { |
53 | 0 | let loca_format = LocaFormat::new(&offsets); |
54 | | |
55 | 0 | Loca { |
56 | 0 | offsets, |
57 | 0 | loca_format, |
58 | 0 | } |
59 | 0 | } |
60 | | |
61 | 0 | pub fn format(&self) -> LocaFormat { |
62 | 0 | self.loca_format |
63 | 0 | } |
64 | | } |
65 | | |
66 | | impl LocaFormat { |
67 | 0 | fn new(loca: &[u32]) -> LocaFormat { |
68 | | // https://github.com/fonttools/fonttools/blob/1c283756a5e39d69459eea80ed12792adc4922dd/Lib/fontTools/ttLib/tables/_l_o_c_a.py#L37 |
69 | | const MAX_SHORT_LOCA_VALUE: u32 = 0x20000; |
70 | 0 | if loca.last().copied().unwrap_or_default() < MAX_SHORT_LOCA_VALUE |
71 | 0 | && loca.iter().all(|offset| offset % 2 == 0) |
72 | | { |
73 | 0 | LocaFormat::Short |
74 | | } else { |
75 | 0 | LocaFormat::Long |
76 | | } |
77 | 0 | } |
78 | | } |
79 | | |
80 | | impl FontWrite for Loca { |
81 | 0 | fn write_into(&self, writer: &mut crate::TableWriter) { |
82 | 0 | match self.loca_format { |
83 | 0 | LocaFormat::Long => self.offsets.write_into(writer), |
84 | 0 | LocaFormat::Short => self |
85 | 0 | .offsets |
86 | 0 | .iter() |
87 | 0 | .for_each(|off| ((off >> 1) as u16).write_into(writer)), |
88 | | } |
89 | 0 | } |
90 | | } |
91 | | |
92 | | impl Validate for Loca { |
93 | 0 | fn validate_impl(&self, _ctx: &mut ValidationCtx) {} |
94 | | } |
95 | | |
96 | | #[cfg(test)] |
97 | | mod tests { |
98 | | use super::*; |
99 | | |
100 | | #[test] |
101 | | fn no_glyphs_is_short() { |
102 | | assert_eq!(LocaFormat::Short, LocaFormat::new(&Vec::new())); |
103 | | } |
104 | | |
105 | | #[test] |
106 | | fn some_glyphs_is_short() { |
107 | | assert_eq!(LocaFormat::Short, LocaFormat::new(&[24, 48, 112])); |
108 | | } |
109 | | |
110 | | #[test] |
111 | | fn unpadded_glyphs_is_long() { |
112 | | assert_eq!(LocaFormat::Long, LocaFormat::new(&[24, 7, 112])); |
113 | | } |
114 | | |
115 | | #[test] |
116 | | fn big_glyphs_is_long() { |
117 | | assert_eq!( |
118 | | LocaFormat::Long, |
119 | | LocaFormat::new(&(0..=32).map(|i| i * 0x1000).collect::<Vec<_>>()) |
120 | | ); |
121 | | } |
122 | | } |