@@ -62,12 +62,180 @@ impl VariationAxis {
6262 }
6363}
6464
65+ /// An [instance record](https://docs.microsoft.com/en-us/typography/opentype/spec/fvar#instancerecord).
66+ #[ derive( Clone , Copy , Debug ) ]
67+ pub struct Instance < ' a > {
68+ /// The name ID for entries in the 'name' table that provide subfamily names for this instance.
69+ pub subfamily_name_id : u16 ,
70+ /// Reserved for future use — set to 0.
71+ pub flags : u16 ,
72+ /// The coordinate array for this instance (length = axisCount).
73+ pub coordinates : LazyArray16 < ' a , Fixed > ,
74+ /// The name ID for entries in the 'name' table that provide PostScript names for this instance.
75+ pub post_script_name_id : Option < u16 > ,
76+ }
77+
78+ impl < ' a > Instance < ' a > {
79+ /// Returns an iterator over the coordinate values as `f32`.
80+ ///
81+ /// This is a convenience method that converts the internal `Fixed` representation
82+ /// to floating-point values.
83+ #[ inline]
84+ pub fn coordinates_f32 ( & self ) -> impl Iterator < Item = f32 > + ' a {
85+ self . coordinates . into_iter ( ) . map ( |fixed| fixed. 0 )
86+ }
87+
88+ #[ inline]
89+ fn parse_from_record (
90+ record : & ' a [ u8 ] ,
91+ axis_count : u16 ,
92+ has_post_script_name_id : bool ,
93+ ) -> Option < Self > {
94+ let mut s = Stream :: new ( record) ;
95+ let subfamily_name_id = s. read :: < u16 > ( ) ?;
96+ let flags = s. read :: < u16 > ( ) ?;
97+ let coordinates = s. read_array16 :: < Fixed > ( axis_count) ?;
98+ let post_script_name_id = if has_post_script_name_id {
99+ Some ( s. read :: < u16 > ( ) ?)
100+ } else {
101+ None
102+ } ;
103+ Some ( Self {
104+ subfamily_name_id,
105+ flags,
106+ coordinates,
107+ post_script_name_id,
108+ } )
109+ }
110+ }
111+
112+ /// A view over the `InstanceRecord` array.
113+ #[ derive( Clone , Copy , Debug ) ]
114+ pub struct Instances < ' a > {
115+ data : & ' a [ u8 ] ,
116+ record_len : u16 ,
117+ axis_count : u16 ,
118+ count : u16 ,
119+ }
120+
121+ /// An iterator over [instance records](Instance).
122+ #[ derive( Clone , Copy , Debug ) ]
123+ pub struct InstancesIter < ' a > {
124+ instances : Instances < ' a > ,
125+ index : u16 ,
126+ }
127+
128+ impl < ' a > Iterator for InstancesIter < ' a > {
129+ type Item = Instance < ' a > ;
130+
131+ fn next ( & mut self ) -> Option < Self :: Item > {
132+ if self . index < self . instances . count {
133+ let instance = self . instances . get ( self . index ) ?;
134+ self . index += 1 ;
135+ Some ( instance)
136+ } else {
137+ None
138+ }
139+ }
140+
141+ fn size_hint ( & self ) -> ( usize , Option < usize > ) {
142+ let remaining = ( self . instances . count - self . index ) as usize ;
143+ ( remaining, Some ( remaining) )
144+ }
145+ }
146+
147+ impl < ' a > ExactSizeIterator for InstancesIter < ' a > {
148+ fn len ( & self ) -> usize {
149+ ( self . instances . count - self . index ) as usize
150+ }
151+ }
152+
153+ impl < ' a > IntoIterator for Instances < ' a > {
154+ type Item = Instance < ' a > ;
155+ type IntoIter = InstancesIter < ' a > ;
156+
157+ fn into_iter ( self ) -> Self :: IntoIter {
158+ InstancesIter {
159+ instances : self ,
160+ index : 0 ,
161+ }
162+ }
163+ }
164+
165+ impl < ' a > Instances < ' a > {
166+ /// Returns an iterator over the instance records.
167+ ///
168+ /// # Examples
169+ ///
170+ /// ```
171+ /// # use ttf_parser::fvar::Table;
172+ /// # let data = &[/* font data */];
173+ /// # if let Some(table) = Table::parse(data) {
174+ /// for instance in table.instances.iter() {
175+ /// println!("Subfamily name ID: {}", instance.subfamily_name_id);
176+ /// }
177+ /// # }
178+ /// ```
179+ #[ inline]
180+ pub fn iter ( & self ) -> InstancesIter < ' a > {
181+ ( * self ) . into_iter ( )
182+ }
183+
184+ #[ inline]
185+ fn new ( data : & ' a [ u8 ] , record_len : u16 , axis_count : u16 , count : u16 ) -> Self {
186+ Self {
187+ data,
188+ record_len,
189+ axis_count,
190+ count,
191+ }
192+ }
193+
194+ /// Total number of instance records.
195+ #[ inline]
196+ pub fn len ( & self ) -> u16 {
197+ self . count
198+ }
199+
200+ /// Returns true when there are no instance records.
201+ #[ inline]
202+ pub fn is_empty ( & self ) -> bool {
203+ self . count == 0
204+ }
205+
206+ /// Returns `true` when the `postScriptNameID` field is present in records.
207+ #[ inline]
208+ pub fn has_post_script_name_id ( & self ) -> bool {
209+ // The base size is 4 bytes (subfamilyNameID + flags) + 4 bytes per axis coordinate.
210+ // If record_len is at least base + 2, the optional postScriptNameID field is present.
211+ let axis_count = usize:: from ( self . axis_count ) ;
212+ let base = 4 + 4 * axis_count;
213+ usize:: from ( self . record_len ) >= base + 2
214+ }
215+
216+ /// Returns the instance at the given index.
217+ ///
218+ /// Returns `None` if the index is out of bounds.
219+ pub fn get ( & self , index : u16 ) -> Option < Instance < ' a > > {
220+ if index >= self . count {
221+ return None ;
222+ }
223+ let len = usize:: from ( self . record_len ) ;
224+ let start = usize:: from ( index) * len;
225+ let end = start + len;
226+ let record = self . data . get ( start..end) ?;
227+ Instance :: parse_from_record ( record, self . axis_count , self . has_post_script_name_id ( ) )
228+ }
229+ }
230+
65231/// A [Font Variations Table](
66232/// https://docs.microsoft.com/en-us/typography/opentype/spec/fvar).
67233#[ derive( Clone , Copy , Debug ) ]
68234pub struct Table < ' a > {
69235 /// A list of variation axes.
70236 pub axes : LazyArray16 < ' a , VariationAxis > ,
237+ /// A list of instance records.
238+ pub instances : Instances < ' a > ,
71239}
72240
73241impl < ' a > Table < ' a > {
@@ -82,6 +250,13 @@ impl<'a> Table<'a> {
82250 let axes_array_offset = s. read :: < Offset16 > ( ) ?;
83251 s. skip :: < u16 > ( ) ; // reserved
84252 let axis_count = s. read :: < u16 > ( ) ?;
253+ let axis_size = s. read :: < u16 > ( ) ?;
254+ let instance_count = s. read :: < u16 > ( ) ?;
255+ let instance_size = s. read :: < u16 > ( ) ?;
256+
257+ if axis_size as usize != VariationAxis :: SIZE {
258+ return None ;
259+ }
85260
86261 // 'If axisCount is zero, then the font is not functional as a variable font,
87262 // and must be treated as a non-variable font;
@@ -91,6 +266,24 @@ impl<'a> Table<'a> {
91266 let mut s = Stream :: new_at ( data, axes_array_offset. to_usize ( ) ) ?;
92267 let axes = s. read_array16 :: < VariationAxis > ( axis_count. get ( ) ) ?;
93268
94- Some ( Table { axes } )
269+ // Instance records follow the axes array immediately.
270+ let instances_offset = axes_array_offset
271+ . to_usize ( )
272+ . checked_add ( usize:: from ( axis_count. get ( ) ) * VariationAxis :: SIZE ) ?;
273+
274+ // Validate instance record size: must be base or base + 2 (for psNameID).
275+ let base = 4usize . checked_add ( 4usize . checked_mul ( usize:: from ( axis_count. get ( ) ) ) ?) ?;
276+ let inst_size = usize:: from ( instance_size) ;
277+ if inst_size < base {
278+ return None ;
279+ }
280+
281+ let total_instances_len =
282+ usize:: from ( instance_count) . checked_mul ( usize:: from ( instance_size) ) ?;
283+ let mut inst_stream = Stream :: new_at ( data, instances_offset) ?;
284+ let inst_data = inst_stream. read_bytes ( total_instances_len) ?;
285+ let instances = Instances :: new ( inst_data, instance_size, axis_count. get ( ) , instance_count) ;
286+
287+ Some ( Table { axes, instances } )
95288 }
96289}
0 commit comments