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}