/src/fontations/write-fonts/src/tables/glyf/composite.rs
Line | Count | Source |
1 | | //! Composite glyphs (containing other glyphs as components) |
2 | | |
3 | | use crate::{ |
4 | | from_obj::{FromObjRef, FromTableRef, ToOwnedTable}, |
5 | | FontWrite, |
6 | | }; |
7 | | |
8 | | use read_fonts::{tables::glyf::CompositeGlyphFlags, types::GlyphId16, FontRead}; |
9 | | |
10 | | use super::Bbox; |
11 | | |
12 | | pub use read_fonts::tables::glyf::{Anchor, Transform}; |
13 | | |
14 | | /// A glyph consisting of multiple component sub-glyphs |
15 | | #[derive(Clone, Debug, PartialEq, Eq)] |
16 | | pub struct CompositeGlyph { |
17 | | pub bbox: Bbox, |
18 | | components: Vec<Component>, |
19 | | _instructions: Vec<u8>, |
20 | | } |
21 | | |
22 | | /// A single component glyph (part of a [`CompositeGlyph`]). |
23 | | #[derive(Clone, Debug, PartialEq, Eq)] |
24 | | pub struct Component { |
25 | | pub glyph: GlyphId16, |
26 | | pub anchor: Anchor, |
27 | | pub flags: ComponentFlags, |
28 | | pub transform: Transform, |
29 | | } |
30 | | |
31 | | /// Options that can be manually set for a given component. |
32 | | /// |
33 | | /// This provides an easier interface for setting those flags that are not |
34 | | /// calculated based on other properties of the glyph. For more information |
35 | | /// on these flags, see [Component Glyph Flags](flags-spec) in the spec. |
36 | | /// |
37 | | /// These eventually are combined with calculated flags into the |
38 | | /// [`CompositeGlyphFlags`] bitset. |
39 | | /// |
40 | | /// [flags-spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/glyf#compositeGlyphFlags |
41 | | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] |
42 | | pub struct ComponentFlags { |
43 | | /// Round xy values to the nearest grid line |
44 | | pub round_xy_to_grid: bool, |
45 | | /// Use the advance/lsb/rsb values of this component for the whole |
46 | | /// composite glyph |
47 | | pub use_my_metrics: bool, |
48 | | /// The composite should have this component's offset scaled |
49 | | pub scaled_component_offset: bool, |
50 | | /// The composite should *not* have this component's offset scaled |
51 | | pub unscaled_component_offset: bool, |
52 | | /// If set, the components of the composite glyph overlap. |
53 | | pub overlap_compound: bool, |
54 | | } |
55 | | |
56 | | impl FromObjRef<read_fonts::tables::glyf::CompositeGlyph<'_>> for CompositeGlyph { |
57 | 0 | fn from_obj_ref( |
58 | 0 | from: &read_fonts::tables::glyf::CompositeGlyph, |
59 | 0 | _data: read_fonts::FontData, |
60 | 0 | ) -> Self { |
61 | 0 | let bbox = Bbox { |
62 | 0 | x_min: from.x_min(), |
63 | 0 | y_min: from.y_min(), |
64 | 0 | x_max: from.x_max(), |
65 | 0 | y_max: from.y_max(), |
66 | 0 | }; |
67 | 0 | let components = from |
68 | 0 | .components() |
69 | 0 | .map(|c| Component { |
70 | 0 | glyph: c.glyph, |
71 | 0 | anchor: c.anchor, |
72 | 0 | flags: c.flags.into(), |
73 | 0 | transform: c.transform, |
74 | 0 | }) |
75 | 0 | .collect(); |
76 | | Self { |
77 | 0 | bbox, |
78 | 0 | components, |
79 | 0 | _instructions: from |
80 | 0 | .instructions() |
81 | 0 | .map(|v| v.to_owned()) |
82 | 0 | .unwrap_or_default(), |
83 | | } |
84 | 0 | } |
85 | | } |
86 | | |
87 | | impl FromTableRef<read_fonts::tables::glyf::CompositeGlyph<'_>> for CompositeGlyph {} |
88 | | |
89 | | impl<'a> FontRead<'a> for CompositeGlyph { |
90 | 0 | fn read(data: read_fonts::FontData<'a>) -> Result<Self, read_fonts::ReadError> { |
91 | 0 | read_fonts::tables::glyf::CompositeGlyph::read(data).map(|g| g.to_owned_table()) |
92 | 0 | } |
93 | | } |
94 | | |
95 | | impl Component { |
96 | | /// Create a new component. |
97 | 0 | pub fn new( |
98 | 0 | glyph: GlyphId16, |
99 | 0 | anchor: Anchor, |
100 | 0 | transform: Transform, |
101 | 0 | flags: impl Into<ComponentFlags>, |
102 | 0 | ) -> Self { |
103 | 0 | Component { |
104 | 0 | glyph, |
105 | 0 | anchor, |
106 | 0 | flags: flags.into(), |
107 | 0 | transform, |
108 | 0 | } |
109 | 0 | } |
110 | | /// Compute the flags for this glyph, excepting `MORE_COMPONENTS` and |
111 | | /// `WE_HAVE_INSTRUCTIONS`, which must be set manually |
112 | 0 | fn compute_flag(&self) -> CompositeGlyphFlags { |
113 | 0 | self.anchor.compute_flags() | self.transform.compute_flags() | self.flags.into() |
114 | 0 | } |
115 | | |
116 | | /// like `FontWrite` but lets us pass in the flags that must be determined |
117 | | /// externally (WE_HAVE_INSTRUCTIONS and MORE_COMPONENTS) |
118 | 0 | fn write_into(&self, writer: &mut crate::TableWriter, extra_flags: CompositeGlyphFlags) { |
119 | 0 | let flags = self.compute_flag() | extra_flags; |
120 | 0 | flags.bits().write_into(writer); |
121 | 0 | self.glyph.write_into(writer); |
122 | 0 | self.anchor.write_into(writer); |
123 | 0 | self.transform.write_into(writer); |
124 | 0 | } |
125 | | } |
126 | | |
127 | | /// An error that occurs if a `CompositeGlyph` is constructed with no components. |
128 | | #[derive(Clone, Copy, Debug)] |
129 | | #[non_exhaustive] |
130 | | pub struct NoComponents; |
131 | | |
132 | | impl std::fmt::Display for NoComponents { |
133 | 0 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
134 | 0 | write!(f, "A composite glyph must contain at least one component") |
135 | 0 | } |
136 | | } |
137 | | |
138 | | impl std::error::Error for NoComponents {} |
139 | | |
140 | | impl CompositeGlyph { |
141 | | /// Create a new composite glyph, with the provided component. |
142 | | /// |
143 | | /// The 'bbox' argument is the bounding box of the glyph after the transform |
144 | | /// has been applied. |
145 | | /// |
146 | | /// Additional components can be added with [`add_component`][Self::add_component] |
147 | 0 | pub fn new(component: Component, bbox: impl Into<Bbox>) -> Self { |
148 | 0 | Self { |
149 | 0 | bbox: bbox.into(), |
150 | 0 | components: vec![component], |
151 | 0 | _instructions: Default::default(), |
152 | 0 | } |
153 | 0 | } |
154 | | |
155 | | /// Add a new component to this glyph |
156 | | /// |
157 | | /// The 'bbox' argument is the bounding box of the glyph after the transform |
158 | | /// has been applied. |
159 | 0 | pub fn add_component(&mut self, component: Component, bbox: impl Into<Bbox>) { |
160 | 0 | self.components.push(component); |
161 | 0 | self.bbox = self.bbox.union(bbox.into()); |
162 | 0 | } |
163 | | |
164 | | /// Construct a `CompositeGlyph` from an iterator of `Component` and `Bbox`es. |
165 | | /// |
166 | | /// This returns an error if the iterator is empty; a CompositeGlyph must always |
167 | | /// contain at least one component. |
168 | 0 | pub fn try_from_iter( |
169 | 0 | source: impl IntoIterator<Item = (Component, Bbox)>, |
170 | 0 | ) -> Result<Self, NoComponents> { |
171 | 0 | let mut components = Vec::new(); |
172 | 0 | let mut union_box: Option<Bbox> = None; |
173 | | |
174 | 0 | for (component, bbox) in source { |
175 | 0 | components.push(component); |
176 | 0 | union_box.get_or_insert(bbox).union(bbox); |
177 | 0 | } |
178 | | |
179 | 0 | if components.is_empty() { |
180 | 0 | Err(NoComponents) |
181 | | } else { |
182 | 0 | Ok(CompositeGlyph { |
183 | 0 | bbox: union_box.unwrap(), |
184 | 0 | components, |
185 | 0 | _instructions: Default::default(), |
186 | 0 | }) |
187 | | } |
188 | 0 | } |
189 | | |
190 | 0 | pub fn components(&self) -> &[Component] { |
191 | 0 | &self.components |
192 | 0 | } |
193 | | } |
194 | | |
195 | | impl FontWrite for CompositeGlyph { |
196 | 0 | fn write_into(&self, writer: &mut crate::TableWriter) { |
197 | | const N_CONTOURS: i16 = -1; |
198 | 0 | N_CONTOURS.write_into(writer); |
199 | 0 | self.bbox.write_into(writer); |
200 | 0 | let (last, rest) = self |
201 | 0 | .components |
202 | 0 | .split_last() |
203 | 0 | .expect("empty composites checked in validation"); |
204 | 0 | for comp in rest { |
205 | 0 | comp.write_into(writer, CompositeGlyphFlags::MORE_COMPONENTS); |
206 | 0 | } |
207 | 0 | let last_flags = if self._instructions.is_empty() { |
208 | 0 | CompositeGlyphFlags::empty() |
209 | | } else { |
210 | 0 | CompositeGlyphFlags::WE_HAVE_INSTRUCTIONS |
211 | | }; |
212 | 0 | last.write_into(writer, last_flags); |
213 | | |
214 | 0 | if !self._instructions.is_empty() { |
215 | 0 | (self._instructions.len() as u16).write_into(writer); |
216 | 0 | self._instructions.write_into(writer); |
217 | 0 | } |
218 | 0 | writer.pad_to_2byte_aligned(); |
219 | 0 | } |
220 | | } |
221 | | |
222 | | impl crate::validate::Validate for CompositeGlyph { |
223 | 0 | fn validate_impl(&self, ctx: &mut crate::codegen_prelude::ValidationCtx) { |
224 | 0 | if self.components.is_empty() { |
225 | 0 | ctx.report("composite glyph must have components"); |
226 | 0 | } |
227 | 0 | if self._instructions.len() > u16::MAX as usize { |
228 | 0 | ctx.report("instructions len overflows"); |
229 | 0 | } |
230 | 0 | } |
231 | | } |
232 | | |
233 | | impl FontWrite for Anchor { |
234 | 0 | fn write_into(&self, writer: &mut crate::TableWriter) { |
235 | 0 | let two_bytes = self |
236 | 0 | .compute_flags() |
237 | 0 | .contains(CompositeGlyphFlags::ARG_1_AND_2_ARE_WORDS); |
238 | 0 | match self { |
239 | 0 | Anchor::Offset { x, y } if !two_bytes => [*x as i8, *y as i8].write_into(writer), |
240 | 0 | Anchor::Offset { x, y } => [*x, *y].write_into(writer), |
241 | 0 | Anchor::Point { base, component } if !two_bytes => { |
242 | 0 | [*base as u8, *component as u8].write_into(writer) |
243 | | } |
244 | 0 | Anchor::Point { base, component } => [*base, *component].write_into(writer), |
245 | | } |
246 | 0 | } |
247 | | } |
248 | | |
249 | | impl FontWrite for Transform { |
250 | 0 | fn write_into(&self, writer: &mut crate::TableWriter) { |
251 | 0 | let flags = self.compute_flags(); |
252 | 0 | if flags.contains(CompositeGlyphFlags::WE_HAVE_A_TWO_BY_TWO) { |
253 | 0 | [self.xx, self.yx, self.xy, self.yy].write_into(writer); |
254 | 0 | } else if flags.contains(CompositeGlyphFlags::WE_HAVE_AN_X_AND_Y_SCALE) { |
255 | 0 | [self.xx, self.yy].write_into(writer); |
256 | 0 | } else if flags.contains(CompositeGlyphFlags::WE_HAVE_A_SCALE) { |
257 | 0 | self.xx.write_into(writer) |
258 | 0 | } |
259 | 0 | } |
260 | | } |
261 | | |
262 | | impl From<CompositeGlyphFlags> for ComponentFlags { |
263 | 0 | fn from(src: CompositeGlyphFlags) -> ComponentFlags { |
264 | 0 | ComponentFlags { |
265 | 0 | round_xy_to_grid: src.contains(CompositeGlyphFlags::ROUND_XY_TO_GRID), |
266 | 0 | use_my_metrics: src.contains(CompositeGlyphFlags::USE_MY_METRICS), |
267 | 0 | scaled_component_offset: src.contains(CompositeGlyphFlags::SCALED_COMPONENT_OFFSET), |
268 | 0 | unscaled_component_offset: src.contains(CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET), |
269 | 0 | overlap_compound: src.contains(CompositeGlyphFlags::OVERLAP_COMPOUND), |
270 | 0 | } |
271 | 0 | } |
272 | | } |
273 | | |
274 | | impl From<ComponentFlags> for CompositeGlyphFlags { |
275 | 0 | fn from(value: ComponentFlags) -> Self { |
276 | 0 | (if value.round_xy_to_grid { |
277 | 0 | CompositeGlyphFlags::ROUND_XY_TO_GRID |
278 | | } else { |
279 | 0 | Default::default() |
280 | 0 | }) | if value.use_my_metrics { |
281 | 0 | CompositeGlyphFlags::USE_MY_METRICS |
282 | | } else { |
283 | 0 | Default::default() |
284 | 0 | } | if value.scaled_component_offset { |
285 | 0 | CompositeGlyphFlags::SCALED_COMPONENT_OFFSET |
286 | | } else { |
287 | 0 | Default::default() |
288 | 0 | } | if value.unscaled_component_offset { |
289 | 0 | CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET |
290 | | } else { |
291 | 0 | Default::default() |
292 | 0 | } | if value.overlap_compound { |
293 | 0 | CompositeGlyphFlags::OVERLAP_COMPOUND |
294 | | } else { |
295 | 0 | Default::default() |
296 | | } |
297 | 0 | } |
298 | | } |
299 | | |
300 | | #[cfg(test)] |
301 | | mod tests { |
302 | | |
303 | | use read_fonts::{ |
304 | | tables::glyf as read_glyf, types::GlyphId, FontData, FontRead, FontRef, TableProvider, |
305 | | }; |
306 | | |
307 | | use super::*; |
308 | | |
309 | | #[test] |
310 | | fn roundtrip_composite() { |
311 | | let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap(); |
312 | | let loca = font.loca(None).unwrap(); |
313 | | let glyf = font.glyf().unwrap(); |
314 | | let read_glyf::Glyph::Composite(orig) = |
315 | | loca.get_glyf(GlyphId::new(2), &glyf).unwrap().unwrap() |
316 | | else { |
317 | | panic!("not a composite glyph") |
318 | | }; |
319 | | |
320 | | let bbox = Bbox { |
321 | | x_min: orig.x_min(), |
322 | | y_min: orig.y_min(), |
323 | | x_max: orig.x_max(), |
324 | | y_max: orig.y_max(), |
325 | | }; |
326 | | let mut iter = orig |
327 | | .components() |
328 | | .map(|comp| Component::new(comp.glyph, comp.anchor, comp.transform, comp.flags)); |
329 | | let mut composite = CompositeGlyph::new(iter.next().unwrap(), bbox); |
330 | | composite.add_component(iter.next().unwrap(), bbox); |
331 | | composite._instructions = orig.instructions().unwrap_or_default().to_vec(); |
332 | | assert!(iter.next().is_none()); |
333 | | let bytes = crate::dump_table(&composite).unwrap(); |
334 | | let ours = read_fonts::tables::glyf::CompositeGlyph::read(FontData::new(&bytes)).unwrap(); |
335 | | |
336 | | let our_comps = ours.components().collect::<Vec<_>>(); |
337 | | let orig_comps = orig.components().collect::<Vec<_>>(); |
338 | | assert_eq!(our_comps.len(), orig_comps.len()); |
339 | | assert_eq!(our_comps.len(), 2); |
340 | | assert_eq!(&our_comps[0], &orig_comps[0]); |
341 | | assert_eq!(&our_comps[1], &orig_comps[1]); |
342 | | assert_eq!(ours.instructions(), orig.instructions()); |
343 | | assert_eq!(orig.offset_data().len(), bytes.len()); |
344 | | |
345 | | assert_eq!(orig.offset_data().as_ref(), bytes); |
346 | | } |
347 | | } |