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        ValueRecord {
212            // we want to always preserve the format of an incoming record;
213            // otherwise there's no way to correctly determine the format later
214            explicit_format: Some(from.format),
215            x_placement: from.x_placement(),
216            y_placement: from.y_placement(),
217            x_advance: from.x_advance(),
218            y_advance: from.y_advance(),
219            x_placement_device: from.x_placement_device(data).to_owned_obj(data),
220            y_placement_device: from.y_placement_device(data).to_owned_obj(data),
221            x_advance_device: from.x_advance_device(data).to_owned_obj(data),
222            y_advance_device: from.y_advance_device(data).to_owned_obj(data),
223        }
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use font_types::GlyphId16;
230    use read_fonts::FontRead;
231
232    use crate::tables::{
233        gpos::{SinglePos, SinglePosFormat1, SinglePosFormat2},
234        layout::{builders::CoverageTableBuilder, VariationIndex},
235    };
236
237    use super::*;
238    #[test]
239    fn serialize_explicit_value_record() {
240        let mut my_record = ValueRecord {
241            x_advance: Some(5),
242            ..Default::default()
243        };
244        my_record.set_explicit_value_format(ValueFormat::X_ADVANCE | ValueFormat::X_ADVANCE_DEVICE);
245        let bytes = crate::dump_table(&my_record).unwrap();
246        assert_eq!(bytes.len(), 4);
247        let read_back =
248            read_fonts::tables::gpos::ValueRecord::read(FontData::new(&bytes), my_record.format())
249                .unwrap();
250        assert!(read_back.x_advance_device.get().is_null());
251    }
252
253    #[test]
254    fn compile_devices() {
255        let my_record = ValueRecord::new().with_x_advance_device(VariationIndex::new(0xff, 0xee));
256        let a_table = SinglePos::format_1(
257            CoverageTableBuilder::from_glyphs(vec![GlyphId16::new(42)]).build(),
258            my_record,
259        );
260
261        let bytes = crate::dump_table(&a_table).unwrap();
262        let read_back = SinglePosFormat1::read(bytes.as_slice().into()).unwrap();
263        assert!(
264            matches!(read_back.value_record.x_advance_device.as_ref(), Some(DeviceOrVariationIndex::VariationIndex(var_idx)) if var_idx.delta_set_inner_index == 0xee)
265        )
266    }
267
268    #[test]
269    fn roundtrip_preserves_format() {
270        let format = ValueFormat::X_ADVANCE | ValueFormat::X_ADVANCE_DEVICE;
271        let record = ValueRecord::new()
272            .with_x_advance(5)
273            .with_explicit_value_format(format);
274        let bytes = crate::dump_table(&record).unwrap();
275        assert_eq!(bytes.len(), 4);
276
277        let read_back =
278            read_fonts::tables::gpos::ValueRecord::read(bytes.as_slice().into(), format).unwrap();
279        assert_eq!(read_back.x_advance(), Some(5));
280        assert!(read_back.x_advance_device(FontData::EMPTY).is_none());
281
282        let owned: ValueRecord = read_back.to_owned_obj(FontData::EMPTY);
283        assert_eq!(owned.explicit_format, Some(format))
284    }
285
286    #[test]
287    fn roundtrip_in_table() {
288        let format = ValueFormat::X_ADVANCE | ValueFormat::X_ADVANCE_DEVICE;
289
290        let a_table = SinglePos::format_2(
291            [GlyphId16::new(7), GlyphId16::new(9)].into_iter().collect(),
292            vec![
293                ValueRecord::new()
294                    .with_explicit_value_format(format)
295                    .with_x_advance(5),
296                ValueRecord::new()
297                    .with_x_advance(7)
298                    .with_x_advance_device(VariationIndex::new(0xde, 0xad)),
299            ],
300        );
301
302        let bytes = crate::dump_table(&a_table).unwrap();
303        let read_back = SinglePosFormat2::read(bytes.as_slice().into()).unwrap();
304        assert_eq!(read_back.value_records[0].explicit_format, Some(format));
305        assert_eq!(read_back.value_records[1].explicit_format, Some(format));
306        assert!(crate::dump_table(&read_back).is_ok());
307    }
308}