Skip to main content

write_fonts/tables/gpos/
value_record.rs

1//! The ValueRecord type used in the GPOS table
2
3use read_fonts::FontData;
4
5use super::ValueFormat;
6use crate::{
7    from_obj::{FromObjRef, ToOwnedObj},
8    offsets::NullableOffsetMarker,
9    tables::layout::DeviceOrVariationIndex,
10    validate::Validate,
11    write::{FontWrite, TableWriter},
12};
13
14/// A [ValueRecord](https://learn.microsoft.com/en-us/typography/opentype/spec/gpos#valueRecord)
15///
16/// GPOS subtables use ValueRecords to describe all the variables and values
17/// used to adjust the position of a glyph or set of glyphs. A ValueRecord may
18/// define any combination of X and Y values (in design units) to add to
19/// (positive values) or subtract from (negative values) the placement and
20/// advance values provided in the font. In non-variable fonts, a ValueRecord
21/// may also contain an offset to a Device table for each of the specified
22/// values. In a variable font, it may also contain an offset to a
23/// VariationIndex table for each of the specified values.
24#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
25#[non_exhaustive]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct ValueRecord {
28    // Okay so... this demands some explanation.
29    //
30    // In general, we compute the format for a value record by looking at what
31    // fields are present in the record. This works 99% of the time.
32    //
33    // The problem, though, is that in certain cases we need to create empty
34    // value records that have an explicit format. In particular this occurs
35    // in class-based pairpos tables, where it is possible that two classes
36    // have no relationship, but we still need to put a record of the
37    // appropriate size in the array of value records.
38    //
39    // In this case, we cannot infer the correct format, because when
40    // we see any null offsets we will assume that those fields should not
41    // be present in the record, where in fact we want to have explicit
42    // null offsets.
43    //
44    // To handle this, we allow the user to pass an explicit format when
45    // constructing a value record. If this field is present, we will use it
46    // instead of computing the format.
47    explicit_format: Option<ValueFormat>,
48    pub x_placement: Option<i16>,
49    pub y_placement: Option<i16>,
50    pub x_advance: Option<i16>,
51    pub y_advance: Option<i16>,
52    pub x_placement_device: NullableOffsetMarker<DeviceOrVariationIndex>,
53    pub y_placement_device: NullableOffsetMarker<DeviceOrVariationIndex>,
54    pub x_advance_device: NullableOffsetMarker<DeviceOrVariationIndex>,
55    pub y_advance_device: NullableOffsetMarker<DeviceOrVariationIndex>,
56}
57
58impl ValueRecord {
59    pub fn new() -> ValueRecord {
60        ValueRecord::default()
61    }
62
63    pub fn with_x_placement(mut self, val: i16) -> Self {
64        self.x_placement = Some(val);
65        self
66    }
67
68    pub fn with_y_placement(mut self, val: i16) -> Self {
69        self.y_placement = Some(val);
70        self
71    }
72
73    pub fn with_x_advance(mut self, val: i16) -> Self {
74        self.x_advance = Some(val);
75        self
76    }
77
78    pub fn with_y_advance(mut self, val: i16) -> Self {
79        self.y_advance = Some(val);
80        self
81    }
82
83    pub fn with_x_placement_device(mut self, val: impl Into<DeviceOrVariationIndex>) -> Self {
84        self.x_placement_device = val.into().into();
85        self
86    }
87
88    pub fn with_y_placement_device(mut self, val: impl Into<DeviceOrVariationIndex>) -> Self {
89        self.y_placement_device = val.into().into();
90        self
91    }
92
93    pub fn with_x_advance_device(mut self, val: impl Into<DeviceOrVariationIndex>) -> Self {
94        self.x_advance_device = val.into().into();
95        self
96    }
97
98    pub fn with_y_advance_device(mut self, val: impl Into<DeviceOrVariationIndex>) -> Self {
99        self.y_advance_device = val.into().into();
100        self
101    }
102
103    pub fn with_explicit_value_format(mut self, format: ValueFormat) -> Self {
104        self.set_explicit_value_format(format);
105        self
106    }
107
108    /// Set an explicit ValueFormat, overriding the computed format.
109    ///
110    /// Use this method if you wish to write a ValueFormat that includes
111    /// explicit null offsets for any of the device or variation index tables.
112    pub fn set_explicit_value_format(&mut self, format: ValueFormat) {
113        self.explicit_format = Some(format)
114    }
115
116    /// The [ValueFormat] of this record.
117    pub fn format(&self) -> ValueFormat {
118        if let Some(format) = self.explicit_format {
119            return format;
120        }
121
122        macro_rules! flag_if_true {
123            ($field:expr, $flag:expr) => {
124                $field
125                    .is_some()
126                    .then(|| $flag)
127                    .unwrap_or(ValueFormat::empty())
128            };
129        }
130
131        flag_if_true!(self.x_placement, ValueFormat::X_PLACEMENT)
132            | flag_if_true!(self.y_placement, ValueFormat::Y_PLACEMENT)
133            | flag_if_true!(self.x_advance, ValueFormat::X_ADVANCE)
134            | flag_if_true!(self.y_advance, ValueFormat::Y_ADVANCE)
135            | flag_if_true!(self.x_placement_device, ValueFormat::X_PLACEMENT_DEVICE)
136            | flag_if_true!(self.y_placement_device, ValueFormat::Y_PLACEMENT_DEVICE)
137            | flag_if_true!(self.x_advance_device, ValueFormat::X_ADVANCE_DEVICE)
138            | flag_if_true!(self.y_advance_device, ValueFormat::Y_ADVANCE_DEVICE)
139    }
140
141    /// Return the number of bytes required to encode this value record
142    pub fn encoded_size(&self) -> usize {
143        self.format().bits().count_ones() as usize * 2
144    }
145}
146
147impl FontWrite for ValueRecord {
148    fn write_into(&self, writer: &mut TableWriter) {
149        let format = self.format();
150        macro_rules! write_field {
151            ($field:expr, $flag:expr) => {
152                if format.contains($flag) {
153                    $field.unwrap_or_default().write_into(writer);
154                }
155            };
156            ($field:expr, $flag:expr, off) => {
157                if format.contains($flag) {
158                    $field.write_into(writer);
159                }
160            };
161        }
162
163        write_field!(self.x_placement, ValueFormat::X_PLACEMENT);
164        write_field!(self.y_placement, ValueFormat::Y_PLACEMENT);
165        write_field!(self.x_advance, ValueFormat::X_ADVANCE);
166        write_field!(self.y_advance, ValueFormat::Y_ADVANCE);
167        write_field!(
168            self.x_placement_device,
169            ValueFormat::X_PLACEMENT_DEVICE,
170            off
171        );
172        write_field!(
173            self.y_placement_device,
174            ValueFormat::Y_PLACEMENT_DEVICE,
175            off
176        );
177        write_field!(self.x_advance_device, ValueFormat::X_ADVANCE_DEVICE, off);
178        write_field!(self.y_advance_device, ValueFormat::Y_ADVANCE_DEVICE, off);
179    }
180}
181
182impl std::fmt::Debug for ValueRecord {
183    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
184        let mut f = f.debug_struct("ValueRecord");
185        self.x_placement.map(|x| f.field("x_placement", &x));
186        self.y_placement.map(|y| f.field("y_placement", &y));
187        self.x_advance.map(|x| f.field("x_advance", &x));
188        self.y_advance.map(|y| f.field("y_advance", &y));
189        self.x_placement_device
190            .as_ref()
191            .map(|x| f.field("x_placement_device", &x));
192        self.y_placement_device
193            .as_ref()
194            .map(|y| f.field("y_placement_device", &y));
195        self.x_advance_device
196            .as_ref()
197            .map(|x| f.field("x_advance_device", &x));
198        self.y_advance_device
199            .as_ref()
200            .map(|y| f.field("y_advance_device", &y));
201        f.finish()
202    }
203}
204
205impl Validate for ValueRecord {
206    fn validate_impl(&self, _ctx: &mut crate::validate::ValidationCtx) {}
207}
208
209impl FromObjRef<read_fonts::tables::gpos::ValueRecord<'_>> for ValueRecord {
210    fn from_obj_ref(from: &read_fonts::tables::gpos::ValueRecord, _data: FontData) -> Self {
211        // a value record carries the data its device offsets resolve against,
212        // which is the table containing it; that is not necessarily the data
213        // our caller has, so prefer the record's own.
214        let data = from.offset_data();
215        ValueRecord {
216            // we want to always preserve the format of an incoming record;
217            // otherwise there's no way to correctly determine the format later
218            explicit_format: Some(from.format()),
219            x_placement: from.x_placement(),
220            y_placement: from.y_placement(),
221            x_advance: from.x_advance(),
222            y_advance: from.y_advance(),
223            x_placement_device: from.x_placement_device().to_owned_obj(data),
224            y_placement_device: from.y_placement_device().to_owned_obj(data),
225            x_advance_device: from.x_advance_device().to_owned_obj(data),
226            y_advance_device: from.y_advance_device().to_owned_obj(data),
227        }
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use font_types::GlyphId16;
234    use read_fonts::FontRead;
235
236    use crate::tables::{
237        gpos::{SinglePos, SinglePosFormat1, SinglePosFormat2},
238        layout::{builders::CoverageTableBuilder, VariationIndex},
239    };
240
241    use super::*;
242    #[test]
243    fn serialize_explicit_value_record() {
244        let mut my_record = ValueRecord {
245            x_advance: Some(5),
246            ..Default::default()
247        };
248        my_record.set_explicit_value_format(ValueFormat::X_ADVANCE | ValueFormat::X_ADVANCE_DEVICE);
249        let bytes = crate::dump_table(&my_record).unwrap();
250        assert_eq!(bytes.len(), 4);
251        let read_back = read_fonts::tables::gpos::ValueRecord::new(
252            FontData::new(&bytes),
253            0,
254            my_record.format(),
255        );
256        assert!(read_back
257            .device_offset(ValueFormat::X_ADVANCE_DEVICE)
258            .unwrap()
259            .is_null());
260    }
261
262    #[test]
263    fn compile_devices() {
264        let my_record = ValueRecord::new().with_x_advance_device(VariationIndex::new(0xff, 0xee));
265        let a_table = SinglePos::format_1(
266            CoverageTableBuilder::from_glyphs(vec![GlyphId16::new(42)]).build(),
267            my_record,
268        );
269
270        let bytes = crate::dump_table(&a_table).unwrap();
271        let read_back = SinglePosFormat1::read(bytes.as_slice().into()).unwrap();
272        assert!(
273            matches!(read_back.value_record.x_advance_device.as_ref(), Some(DeviceOrVariationIndex::VariationIndex(var_idx)) if var_idx.delta_set_inner_index == 0xee)
274        )
275    }
276
277    #[test]
278    fn roundtrip_preserves_format() {
279        let format = ValueFormat::X_ADVANCE | ValueFormat::X_ADVANCE_DEVICE;
280        let record = ValueRecord::new()
281            .with_x_advance(5)
282            .with_explicit_value_format(format);
283        let bytes = crate::dump_table(&record).unwrap();
284        assert_eq!(bytes.len(), 4);
285
286        let read_back =
287            read_fonts::tables::gpos::ValueRecord::new(bytes.as_slice().into(), 0, format);
288        assert_eq!(read_back.x_advance(), Some(5));
289        assert!(read_back.x_advance_device().is_none());
290
291        let owned: ValueRecord = read_back.to_owned_obj(FontData::EMPTY);
292        assert_eq!(owned.explicit_format, Some(format))
293    }
294
295    #[test]
296    fn roundtrip_in_table() {
297        let format = ValueFormat::X_ADVANCE | ValueFormat::X_ADVANCE_DEVICE;
298
299        let a_table = SinglePos::format_2(
300            [GlyphId16::new(7), GlyphId16::new(9)].into_iter().collect(),
301            vec![
302                ValueRecord::new()
303                    .with_explicit_value_format(format)
304                    .with_x_advance(5),
305                ValueRecord::new()
306                    .with_x_advance(7)
307                    .with_x_advance_device(VariationIndex::new(0xde, 0xad)),
308            ],
309        );
310
311        let bytes = crate::dump_table(&a_table).unwrap();
312        let read_back = SinglePosFormat2::read(bytes.as_slice().into()).unwrap();
313        assert_eq!(read_back.value_records[0].explicit_format, Some(format));
314        assert_eq!(read_back.value_records[1].explicit_format, Some(format));
315        assert!(crate::dump_table(&read_back).is_ok());
316    }
317}