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