Skip to main content

pdfrum_font/
ids.rs

1//! The small newtypes and flag set every other module is written in terms of.
2
3use std::fmt;
4
5pub use pdfrum_cmap::{CharCode, Cid};
6
7/// A glyph index into a font program.
8///
9/// Zero is a legitimate value — it is the `.notdef` glyph, which PDFium
10/// deliberately distinguishes from "no glyph at all" (that is `None`, the C++'s
11/// `-1`). Every ladder in this crate returns `Option<Gid>` for exactly that
12/// reason.
13///
14/// Whose numbering this is depends on the loaded program: `skrifa`'s
15/// `GlyphId` for an sfnt or bare-CFF face, and `/CharStrings` declaration
16/// order for a Type 1 one. [`pdfrum_type1::Gid`] names that second space in
17/// its own crate and stays a separate type; the `From` impls below are the
18/// conversion, and they live here because this is the one crate that holds
19/// both index spaces.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
21pub struct Gid(pub u16);
22
23impl From<pdfrum_type1::Gid> for Gid {
24    fn from(g: pdfrum_type1::Gid) -> Self {
25        Self(g.0)
26    }
27}
28
29impl From<Gid> for pdfrum_type1::Gid {
30    fn from(g: Gid) -> Self {
31        Self(g.0)
32    }
33}
34
35/// Identifies one loaded font within a [`FontCache`](crate::FontCache), so a
36/// glyph cache entry cannot be mistaken for another font's.
37///
38/// Opaque and monotonically assigned; the numeric value means nothing beyond
39/// "not the same font as a different value".
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub struct FontId(pub u64);
42
43/// A glyph name from an `/Encoding` `/Differences` array or a predefined
44/// character set.
45///
46/// Glyph names are compared byte-exactly against `.notdef` and `space` in the
47/// Type 1 ladder and are looked up in the Adobe Glyph List, so they stay bytes
48/// rather than becoming `str`: a `/Differences` entry may name anything.
49#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub struct GlyphName(Box<[u8]>);
51
52impl GlyphName {
53    /// Wrap a name's bytes.
54    #[must_use]
55    pub fn new(bytes: impl Into<Box<[u8]>>) -> Self {
56        Self(bytes.into())
57    }
58
59    /// The name's bytes, as they appeared in the file.
60    #[must_use]
61    pub fn as_bytes(&self) -> &[u8] {
62        &self.0
63    }
64
65    /// The name as UTF-8, when it is valid UTF-8. Every real glyph name is
66    /// ASCII; a name that is not is simply not in any table we consult.
67    #[must_use]
68    pub fn as_str(&self) -> Option<&str> {
69        std::str::from_utf8(&self.0).ok()
70    }
71}
72
73impl fmt::Debug for GlyphName {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        match self.as_str() {
76            Some(s) => write!(f, "GlyphName({s:?})"),
77            None => write!(f, "GlyphName({:?})", self.0),
78        }
79    }
80}
81
82impl From<&str> for GlyphName {
83    fn from(s: &str) -> Self {
84        Self::new(s.as_bytes().to_vec())
85    }
86}
87
88/// The `/FontDescriptor` `/Flags` bit set (ISO 32000-1 table 123), plus
89/// PDFium's own `USE_EXTERN_ATTR` bit.
90///
91/// A hand-rolled newtype rather than a `bitflags` dependency, for the reason
92/// `bitflags` would get wrong: **unknown bits round-trip**. Files set reserved
93/// bits, and `SYMBOLIC` and `NON_SYMBOLIC` co-occur in the wild, so
94/// [`FontFlags::from_bits`] keeps the whole word and [`FontFlags::bits`]
95/// hands it back unchanged.
96///
97/// ```
98/// use pdfrum_font::FontFlags;
99///
100/// let f = FontFlags::SERIF | FontFlags::ITALIC;
101/// assert!(f.contains(FontFlags::SERIF));
102/// assert!(!f.without(FontFlags::SERIF).contains(FontFlags::SERIF));
103///
104/// // A reserved bit survives the trip.
105/// assert_eq!(FontFlags::from_bits(1 << 30).bits(), 1 << 30);
106/// ```
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
108pub struct FontFlags(u32);
109
110impl FontFlags {
111    /// All glyphs have the same width.
112    pub const FIXED_PITCH: Self = Self(1 << 0);
113    /// Glyphs have serifs.
114    pub const SERIF: Self = Self(1 << 1);
115    /// The font uses its own built-in encoding rather than a standard one.
116    pub const SYMBOLIC: Self = Self(1 << 2);
117    /// Glyphs resemble cursive handwriting.
118    pub const SCRIPT: Self = Self(1 << 3);
119    /// The font uses the Adobe standard Latin character set.
120    pub const NON_SYMBOLIC: Self = Self(1 << 5);
121    /// Glyphs have dominant vertical strokes that are slanted.
122    pub const ITALIC: Self = Self(1 << 6);
123    /// No lowercase letters.
124    pub const ALL_CAP: Self = Self(1 << 16);
125    /// Lowercase letters have the shapes of uppercase ones at reduced size.
126    pub const SMALL_CAP: Self = Self(1 << 17);
127    /// Bold glyphs are painted with extra pixels at small sizes.
128    pub const FORCE_BOLD: Self = Self(1 << 18);
129    /// **Not** a PDF flag. PDFium sets this bit when the descriptor carried a
130    /// complete enough metric set to be trusted, and the substitution ladder
131    /// discards the caller's weight and slant entirely when it is absent.
132    pub const USE_EXTERN_ATTR: Self = Self(1 << 19);
133
134    /// No bit set.
135    pub const NONE: Self = Self(0);
136
137    /// The default when a font has no `/FontDescriptor` at all.
138    pub const DEFAULT: Self = Self::NON_SYMBOLIC;
139
140    /// The raw `/Flags` word, including any bit this type does not name.
141    #[must_use]
142    pub const fn bits(self) -> u32 {
143        self.0
144    }
145
146    /// The word as written in the file. **Unknown bits are retained**: a
147    /// reserved bit a damaged file sets is kept, not dropped.
148    #[must_use]
149    pub const fn from_bits(bits: u32) -> Self {
150        Self(bits)
151    }
152
153    /// Whether every bit of `other` is set here.
154    ///
155    /// [`FontFlags::NONE`] is contained in everything, so `contains` is the
156    /// wrong question to ask about "no flags at all" — use `== FontFlags::NONE`.
157    #[must_use]
158    pub const fn contains(self, other: Self) -> bool {
159        self.0 & other.0 == other.0
160    }
161
162    /// Both sets of bits.
163    #[must_use]
164    pub const fn union(self, other: Self) -> Self {
165        Self(self.0 | other.0)
166    }
167
168    /// A copy with `other`'s bits set. An alias for [`FontFlags::union`].
169    #[must_use]
170    pub const fn with(self, other: Self) -> Self {
171        self.union(other)
172    }
173
174    /// The bits of `self` that are not in `other`.
175    #[must_use]
176    pub const fn without(self, other: Self) -> Self {
177        Self(self.0 & !other.0)
178    }
179
180    /// Whether no bit at all is set.
181    #[must_use]
182    pub const fn is_empty(self) -> bool {
183        self.0 == 0
184    }
185
186    /// Symbolic fonts use their own encoding vector.
187    #[must_use]
188    pub const fn is_symbolic(self) -> bool {
189        self.contains(Self::SYMBOLIC)
190    }
191
192    /// Non-symbolic fonts use the Adobe standard Latin set.
193    #[must_use]
194    pub const fn is_non_symbolic(self) -> bool {
195        self.contains(Self::NON_SYMBOLIC)
196    }
197
198    /// Italic, per the descriptor's own flag rather than its `/ItalicAngle`.
199    #[must_use]
200    pub const fn is_italic(self) -> bool {
201        self.contains(Self::ITALIC)
202    }
203
204    /// Every glyph the same width.
205    #[must_use]
206    pub const fn is_fixed_pitch(self) -> bool {
207        self.contains(Self::FIXED_PITCH)
208    }
209
210    /// The descriptor's metrics are complete enough to trust.
211    #[must_use]
212    pub const fn uses_extern_attr(self) -> bool {
213        self.contains(Self::USE_EXTERN_ATTR)
214    }
215
216    /// No lowercase letters — triggers the all-caps glyph aliasing of the former working note.
217    #[must_use]
218    pub const fn is_all_cap(self) -> bool {
219        self.contains(Self::ALL_CAP)
220    }
221}
222
223impl std::ops::BitOr for FontFlags {
224    type Output = Self;
225
226    fn bitor(self, rhs: Self) -> Self {
227        self.union(rhs)
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn flag_predicates_read_the_right_bits() {
237        let f = FontFlags::SYMBOLIC | FontFlags::ITALIC;
238        assert!(f.is_symbolic());
239        assert!(f.is_italic());
240        assert!(!f.is_non_symbolic());
241        assert!(!f.uses_extern_attr());
242        assert_eq!(FontFlags::DEFAULT.bits(), 32);
243    }
244
245    #[test]
246    fn with_and_without_are_inverses() {
247        let f = FontFlags::NONE.with(FontFlags::ALL_CAP);
248        assert!(f.is_all_cap());
249        assert!(!f.without(FontFlags::ALL_CAP).is_all_cap());
250    }
251
252    #[test]
253    fn unknown_bits_round_trip() {
254        // Bit 30 is reserved; a file that sets it keeps it.
255        let reserved = 1 << 30;
256        let f = FontFlags::from_bits(reserved | FontFlags::SERIF.bits());
257        assert_eq!(f.bits(), reserved | FontFlags::SERIF.bits());
258        assert!(f.contains(FontFlags::SERIF));
259        assert!(!f.contains(FontFlags::ITALIC));
260    }
261
262    #[test]
263    fn symbolic_and_non_symbolic_co_occur() {
264        // The spec says they are exclusive; files disagree, and both
265        // predicates must answer for what is written.
266        let f = FontFlags::SYMBOLIC | FontFlags::NON_SYMBOLIC;
267        assert!(f.is_symbolic());
268        assert!(f.is_non_symbolic());
269        assert_eq!(f.bits(), (1 << 2) | (1 << 5));
270    }
271
272    #[test]
273    fn contains_holds_for_a_subset_and_the_empty_set() {
274        let f = FontFlags::SERIF | FontFlags::ITALIC | FontFlags::ALL_CAP;
275        assert!(f.contains(FontFlags::SERIF | FontFlags::ALL_CAP));
276        assert!(f.contains(FontFlags::NONE));
277        assert!(!f.contains(FontFlags::SERIF | FontFlags::SMALL_CAP));
278        assert!(FontFlags::NONE.is_empty());
279        assert!(!f.is_empty());
280    }
281
282    #[test]
283    fn glyph_names_keep_their_bytes() {
284        let n = GlyphName::from("quotesingle");
285        assert_eq!(n.as_bytes(), b"quotesingle");
286        assert_eq!(n.as_str(), Some("quotesingle"));
287        // A name that is not UTF-8 is still a name; it just matches no table.
288        let raw = GlyphName::new(vec![0xff, 0xfe]);
289        assert_eq!(raw.as_str(), None);
290    }
291
292    #[test]
293    fn gid_round_trips_through_the_type1_newtype() {
294        let g = Gid(42);
295        assert_eq!(Gid::from(pdfrum_type1::Gid::from(g)), g);
296    }
297}