Skip to main content

adobe_font_metrics/
model.rs

1use std::borrow::Cow;
2
3/// A point or displacement in AFM units (1/1000 of the font scale).
4#[derive(Debug, Clone, Copy, Default, PartialEq)]
5pub struct Vector {
6    /// X component.
7    pub x: f32,
8    /// Y component.
9    pub y: f32,
10}
11
12/// A font or glyph bounding box, with ordinary `f32` precision limits.
13#[derive(Debug, Clone, Copy, Default, PartialEq)]
14pub struct BBox {
15    /// Lower-left x coordinate.
16    pub llx: f32,
17    /// Lower-left y coordinate.
18    pub lly: f32,
19    /// Upper-right x coordinate.
20    pub urx: f32,
21    /// Upper-right y coordinate.
22    pub ury: f32,
23}
24
25/// AFM writing direction, independent of the sign or axis of an advance vector.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum Direction {
28    /// Writing direction 0 (normally horizontal).
29    Zero,
30    /// Writing direction 1 (normally vertical).
31    One,
32}
33
34impl Direction {
35    /// Index into the two-element direction and advance arrays.
36    #[must_use]
37    pub const fn index(self) -> usize {
38        match self {
39            Self::Zero => 0,
40            Self::One => 1,
41        }
42    }
43}
44
45/// `MetricsSets` or `StartDirection` selector; 2 applies to both directions.
46#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
47pub enum MetricsSets {
48    /// Direction 0; the default when `MetricsSets` is absent.
49    #[default]
50    Zero,
51    /// Direction 1.
52    One,
53    /// Both directions.
54    Both,
55}
56
57/// Authored metrics for one writing direction. Shared blocks populate both entries.
58#[derive(Debug, Clone, Copy, Default, PartialEq)]
59pub struct DirectionMetrics {
60    /// Underline displacement in AFM units.
61    pub underline_position: Option<f32>,
62    /// Underline stroke thickness in AFM units.
63    pub underline_thickness: Option<f32>,
64    /// Counter-clockwise angle from vertical, in degrees.
65    pub italic_angle: Option<f32>,
66    /// Global character advance, also used when a character omits its advance.
67    pub char_width: Option<Vector>,
68    /// Authored fixed-pitch flag, preserving absence for default resolution.
69    pub is_fixed_pitch: Option<bool>,
70}
71
72impl DirectionMetrics {
73    /// Effective fixed-pitch flag; `CharWidth` implies true when the flag is absent.
74    #[must_use]
75    pub fn fixed_pitch(&self) -> bool {
76        self.is_fixed_pitch
77            .unwrap_or_else(|| self.char_width.is_some())
78    }
79}
80
81/// An encoded character number; hexadecimal digits are retained without angle brackets.
82/// These values are encoding-specific and are not Unicode scalar values.
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub enum CharacterCode<'a> {
85    /// Decimal `C` value; -1 denotes an unencoded character.
86    Decimal(i32),
87    /// Hexadecimal `CH` digits, preserving leading zeroes and arbitrary code length.
88    Hex(Cow<'a, str>),
89}
90
91impl CharacterCode<'_> {
92    /// Return an unsigned code when it fits in `u32`; unencoded/overlarge codes return None.
93    #[must_use]
94    pub fn as_u32(&self) -> Option<u32> {
95        match self {
96            Self::Decimal(code) => u32::try_from(*code).ok(),
97            Self::Hex(digits) => u32::from_str_radix(digits, 16).ok(),
98        }
99    }
100
101    /// Detach the code from its source text.
102    #[must_use]
103    pub fn into_owned(self) -> CharacterCode<'static> {
104        match self {
105            Self::Decimal(code) => CharacterCode::Decimal(code),
106            Self::Hex(digits) => CharacterCode::Hex(Cow::Owned(digits.into_owned())),
107        }
108    }
109}
110
111/// One `L successor ligature` rule. Every rule on a character is retained in order.
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub struct Ligature<'a> {
114    /// Name of the following glyph.
115    pub successor: Cow<'a, str>,
116    /// Name of the resulting ligature glyph.
117    pub ligature: Cow<'a, str>,
118}
119
120impl Ligature<'_> {
121    /// Detach both glyph names from their source.
122    #[must_use]
123    pub fn into_owned(self) -> Ligature<'static> {
124        Ligature {
125            successor: Cow::Owned(self.successor.into_owned()),
126            ligature: Cow::Owned(self.ligature.into_owned()),
127        }
128    }
129}
130
131/// One character record; missing advances stay absent until resolved against the font.
132#[derive(Debug, Clone, PartialEq)]
133pub struct CharacterMetric<'a> {
134    /// Required `C` or `CH` encoding value.
135    pub code: CharacterCode<'a>,
136    /// Optional glyph name (`N`), without encoding interpretation.
137    pub name: Option<Cow<'a, str>>,
138    /// Authored advance vectors for directions 0 and 1, respectively.
139    pub advances: [Option<Vector>; 2],
140    /// Optional glyph bounding box (`B`).
141    pub bbox: Option<BBox>,
142    /// Per-glyph vertical origin displacement (`VV`).
143    pub v_vector: Option<Vector>,
144    /// All authored ligature rules.
145    pub ligatures: Cow<'a, [Ligature<'a>]>,
146}
147
148impl CharacterMetric<'_> {
149    /// Detach the code, name, and every ligature from their source.
150    #[must_use]
151    pub fn into_owned(self) -> CharacterMetric<'static> {
152        CharacterMetric {
153            code: self.code.into_owned(),
154            name: self.name.map(|name| Cow::Owned(name.into_owned())),
155            advances: self.advances,
156            bbox: self.bbox,
157            v_vector: self.v_vector,
158            ligatures: Cow::Owned(
159                self.ligatures
160                    .into_owned()
161                    .into_iter()
162                    .map(Ligature::into_owned)
163                    .collect(),
164            ),
165        }
166    }
167}
168
169/// Pair operands retain the distinction between glyph names and encoded hexadecimal values.
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub enum KerningOperands<'a> {
172    /// Named operands used by `KP`, `KPX`, and `KPY`.
173    Names {
174        /// First glyph name.
175        left: Cow<'a, str>,
176        /// Second glyph name.
177        right: Cow<'a, str>,
178    },
179    /// `KPH` operands, stored as hexadecimal digits without brackets or encoding conversion.
180    Hex {
181        /// First encoded character.
182        left: Cow<'a, str>,
183        /// Second encoded character.
184        right: Cow<'a, str>,
185    },
186}
187
188impl KerningOperands<'_> {
189    /// Detach both operands from their source.
190    #[must_use]
191    pub fn into_owned(self) -> KerningOperands<'static> {
192        match self {
193            Self::Names { left, right } => KerningOperands::Names {
194                left: Cow::Owned(left.into_owned()),
195                right: Cow::Owned(right.into_owned()),
196            },
197            Self::Hex { left, right } => KerningOperands::Hex {
198                left: Cow::Owned(left.into_owned()),
199                right: Cow::Owned(right.into_owned()),
200            },
201        }
202    }
203}
204
205/// A complete pair adjustment in one writing direction.
206#[derive(Debug, Clone, PartialEq)]
207pub struct KerningPair<'a> {
208    /// Named or encoded operands.
209    pub operands: KerningOperands<'a>,
210    /// Both displacement components. `KPX`/`KPY` supply zero for the other axis.
211    pub adjustment: Vector,
212    /// Direction of the enclosing pair section.
213    pub direction: Direction,
214}
215
216impl KerningPair<'_> {
217    /// Detach pair operands from their source.
218    #[must_use]
219    pub fn into_owned(self) -> KerningPair<'static> {
220        KerningPair {
221            operands: self.operands.into_owned(),
222            adjustment: self.adjustment,
223            direction: self.direction,
224        }
225    }
226}
227
228/// One track-kerning curve, retained as its degree and endpoint values.
229#[derive(Debug, Clone, Copy, PartialEq)]
230pub struct TrackKern {
231    /// Relative tracking tightness.
232    pub degree: i32,
233    /// Minimum point size.
234    pub min_point_size: f32,
235    /// Adjustment at the minimum point size.
236    pub min_kern: f32,
237    /// Maximum point size.
238    pub max_point_size: f32,
239    /// Adjustment at the maximum point size.
240    pub max_kern: f32,
241}
242
243/// One `PCC` component in a composite definition.
244#[derive(Debug, Clone, PartialEq)]
245pub struct CompositeComponent<'a> {
246    /// Name of the component glyph.
247    pub name: Cow<'a, str>,
248    /// Component displacement in AFM units.
249    pub offset: Vector,
250}
251
252impl CompositeComponent<'_> {
253    /// Detach the component name from its source.
254    #[must_use]
255    pub fn into_owned(self) -> CompositeComponent<'static> {
256        CompositeComponent {
257            name: Cow::Owned(self.name.into_owned()),
258            offset: self.offset,
259        }
260    }
261}
262
263/// A `CC` construction recipe; it does not synthesize outlines or character metrics.
264#[derive(Debug, Clone, PartialEq)]
265pub struct Composite<'a> {
266    /// Name of the composite glyph.
267    pub name: Cow<'a, str>,
268    /// Ordered component names and offsets.
269    pub components: Cow<'a, [CompositeComponent<'a>]>,
270}
271
272impl Composite<'_> {
273    /// Detach the name and every component from their source.
274    #[must_use]
275    pub fn into_owned(self) -> Composite<'static> {
276        Composite {
277            name: Cow::Owned(self.name.into_owned()),
278            components: Cow::Owned(
279                self.components
280                    .into_owned()
281                    .into_iter()
282                    .map(CompositeComponent::into_owned)
283                    .collect(),
284            ),
285        }
286    }
287}
288
289/// Location of a comment or unrecognized record within the parsed AFM structure.
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291pub enum RecordContext {
292    /// Global font information (including comments before the header).
293    Font,
294    /// An explicit writing-direction section.
295    Direction(MetricsSets),
296    /// Character-metric section outside an individual record.
297    CharacterMetrics,
298    /// A character record, indexed in `FontMetrics::character_metrics`.
299    Character(usize),
300    /// A kerning container outside its subsections.
301    KernData,
302    /// Pair section for the given direction.
303    KernPairs(Direction),
304    /// Track-kerning section.
305    TrackKern,
306    /// Composite section outside an individual record.
307    Composites,
308    /// A composite record, indexed in `FontMetrics::composites`.
309    Composite(usize),
310}
311
312/// Comment or uninterpreted extension data, ordered by appearance in the source.
313#[derive(Debug, Clone, PartialEq, Eq)]
314pub struct SourceRecord<'a> {
315    /// One-based source line.
316    pub line: usize,
317    /// Enclosing section or record.
318    pub context: RecordContext,
319    /// `Comment` or the unrecognized keyword.
320    pub keyword: Cow<'a, str>,
321    /// Trimmed operand text; no interpretation or round-trip formatting guarantee.
322    pub value: Cow<'a, str>,
323}
324
325impl SourceRecord<'_> {
326    /// Detach the keyword and operand text from their source.
327    #[must_use]
328    pub fn into_owned(self) -> SourceRecord<'static> {
329        SourceRecord {
330            line: self.line,
331            context: self.context,
332            keyword: Cow::Owned(self.keyword.into_owned()),
333            value: Cow::Owned(self.value.into_owned()),
334        }
335    }
336}
337
338/// Parsed AFM metric data. Optional authored fields retain absence; accessors resolve defaults.
339#[derive(Debug, Clone, Default, PartialEq)]
340pub struct FontMetrics<'a> {
341    /// AFM format version from the header.
342    pub afm_version: Cow<'a, str>,
343    /// Required font program name.
344    pub font_name: Cow<'a, str>,
345    /// Required font bounding box.
346    pub font_bbox: BBox,
347    /// Declared writing directions; absence implies direction 0.
348    pub metrics_sets: Option<MetricsSets>,
349    /// Full display name.
350    pub full_name: Option<Cow<'a, str>>,
351    /// Typeface family.
352    pub family_name: Option<Cow<'a, str>>,
353    /// Weight description.
354    pub weight: Option<Cow<'a, str>>,
355    /// Font program version, distinct from the AFM format version.
356    pub version: Option<Cow<'a, str>>,
357    /// Font notice or copyright text.
358    pub notice: Option<Cow<'a, str>>,
359    /// Default encoding description.
360    pub encoding_scheme: Option<Cow<'a, str>>,
361    /// Glyph complement description.
362    pub character_set: Option<Cow<'a, str>>,
363    /// Encoding mapping selector.
364    pub mapping_scheme: Option<u32>,
365    /// Escape byte for an escape-mapped font.
366    pub esc_char: Option<u8>,
367    /// Declared total glyph count, independent of the character-metric section count.
368    pub characters: Option<u32>,
369    /// Authored base-font flag; absence implies true.
370    pub is_base_font: Option<bool>,
371    /// Authored CID-keyed font flag.
372    pub is_cid_font: Option<bool>,
373    /// Global displacement between writing origins.
374    pub v_vector: Option<Vector>,
375    /// Authored fixed-origin-vector flag.
376    pub is_fixed_v: Option<bool>,
377    /// Capital height.
378    pub cap_height: Option<f32>,
379    /// Lowercase x height.
380    pub x_height: Option<f32>,
381    /// Ascender height.
382    pub ascender: Option<f32>,
383    /// Descender depth, typically negative.
384    pub descender: Option<f32>,
385    /// Dominant horizontal stem width.
386    pub std_hw: Option<f32>,
387    /// Dominant vertical stem width.
388    pub std_vw: Option<f32>,
389    /// Metrics for directions 0 and 1; shared blocks populate both.
390    pub directions: [DirectionMetrics; 2],
391    /// All character records in source order.
392    pub character_metrics: Cow<'a, [CharacterMetric<'a>]>,
393    /// All named/hexadecimal pairs in source order, with direction identity.
394    pub kerning_pairs: Cow<'a, [KerningPair<'a>]>,
395    /// Track-kerning records in source order.
396    pub track_kerns: Cow<'a, [TrackKern]>,
397    /// Composite construction recipes in source order.
398    pub composites: Cow<'a, [Composite<'a>]>,
399    /// Comments and uninterpreted records, including unmodeled multiple-master arrays.
400    pub source_records: Cow<'a, [SourceRecord<'a>]>,
401}
402
403/// Fully owned AFM data that can outlive the original input.
404pub type OwnedFontMetrics = FontMetrics<'static>;
405
406impl FontMetrics<'_> {
407    /// Metrics for one writing direction.
408    #[must_use]
409    pub const fn direction(&self, direction: Direction) -> &DirectionMetrics {
410        &self.directions[direction.index()]
411    }
412
413    /// Resolve a character advance against the direction's global `CharWidth`.
414    /// Returns None if neither was specified; an explicit zero remains Some.
415    #[must_use]
416    pub fn advance(&self, character: &CharacterMetric<'_>, direction: Direction) -> Option<Vector> {
417        character.advances[direction.index()].or_else(|| self.direction(direction).char_width)
418    }
419
420    /// Resolve a character's vertical origin vector against the global `VVector`.
421    #[must_use]
422    pub fn vertical_origin(&self, character: &CharacterMetric<'_>) -> Option<Vector> {
423        character.v_vector.or(self.v_vector)
424    }
425
426    /// Effective `IsFixedV`, inferred from global `VVector` when absent.
427    #[must_use]
428    pub fn fixed_v(&self) -> bool {
429        self.is_fixed_v.unwrap_or_else(|| self.v_vector.is_some())
430    }
431
432    /// Detach all borrowed strings and nested records from the source.
433    #[must_use]
434    pub fn into_owned(self) -> OwnedFontMetrics {
435        FontMetrics {
436            afm_version: Cow::Owned(self.afm_version.into_owned()),
437            font_name: Cow::Owned(self.font_name.into_owned()),
438            font_bbox: self.font_bbox,
439            metrics_sets: self.metrics_sets,
440            full_name: self.full_name.map(|value| Cow::Owned(value.into_owned())),
441            family_name: self.family_name.map(|value| Cow::Owned(value.into_owned())),
442            weight: self.weight.map(|value| Cow::Owned(value.into_owned())),
443            version: self.version.map(|value| Cow::Owned(value.into_owned())),
444            notice: self.notice.map(|value| Cow::Owned(value.into_owned())),
445            encoding_scheme: self
446                .encoding_scheme
447                .map(|value| Cow::Owned(value.into_owned())),
448            character_set: self
449                .character_set
450                .map(|value| Cow::Owned(value.into_owned())),
451            mapping_scheme: self.mapping_scheme,
452            esc_char: self.esc_char,
453            characters: self.characters,
454            is_base_font: self.is_base_font,
455            is_cid_font: self.is_cid_font,
456            v_vector: self.v_vector,
457            is_fixed_v: self.is_fixed_v,
458            cap_height: self.cap_height,
459            x_height: self.x_height,
460            ascender: self.ascender,
461            descender: self.descender,
462            std_hw: self.std_hw,
463            std_vw: self.std_vw,
464            directions: self.directions,
465            character_metrics: Cow::Owned(
466                self.character_metrics
467                    .into_owned()
468                    .into_iter()
469                    .map(CharacterMetric::into_owned)
470                    .collect(),
471            ),
472            kerning_pairs: Cow::Owned(
473                self.kerning_pairs
474                    .into_owned()
475                    .into_iter()
476                    .map(KerningPair::into_owned)
477                    .collect(),
478            ),
479            track_kerns: Cow::Owned(self.track_kerns.into_owned()),
480            composites: Cow::Owned(
481                self.composites
482                    .into_owned()
483                    .into_iter()
484                    .map(Composite::into_owned)
485                    .collect(),
486            ),
487            source_records: Cow::Owned(
488                self.source_records
489                    .into_owned()
490                    .into_iter()
491                    .map(SourceRecord::into_owned)
492                    .collect(),
493            ),
494        }
495    }
496}