1 //! PostScript string identifiers.
2 
3 /// PostScript string identifier (SID).
4 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
5 pub struct StringId(u16);
6 
7 impl StringId {
8     /// Creates an identifier from a 16-bit unsigned integer.
new(raw: u16) -> Self9     pub const fn new(raw: u16) -> Self {
10         Self(raw)
11     }
12 
13     /// Returns the underlying identifier as a 16-bit unsigned integer.
to_u16(self) -> u1614     pub const fn to_u16(self) -> u16 {
15         self.0
16     }
17 
18     /// Resolves the identifier as a standard string.
19     ///
20     /// If the identifer represents a standard string, returns `Ok(string)`,
21     /// otherwise returns `Err(index)` with the index that should be used to
22     /// retrieve the string from the CFF string INDEX.
23     ///
24     /// The standard string set is available in the section
25     /// "Appendix A - Standard Strings" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf>.
standard_string(self) -> Result<Latin1String<'static>, usize>26     pub fn standard_string(self) -> Result<Latin1String<'static>, usize> {
27         let ix = self.0 as usize;
28         if let Some(string) = STANDARD_STRINGS.get(ix) {
29             // The standard strings are all ASCII so it's safe to intepret them
30             // as Latin-1. This is verified in a unit test.
31             Ok(Latin1String::new(string.as_bytes()))
32         } else {
33             Err(ix - STANDARD_STRINGS.len())
34         }
35     }
36 }
37 
38 impl From<i32> for StringId {
from(value: i32) -> Self39     fn from(value: i32) -> Self {
40         Self::new(value as u16)
41     }
42 }
43 
44 /// Reference to a Latin-1 encoded string.
45 ///
46 /// Strings stored in all PostScript defined fonts are usually ASCII but are
47 /// technically encoded in Latin-1. This type wraps the raw string data to
48 /// prevent attempts to decode as UTF-8.
49 ///
50 /// This implements `PartialEq<&str>` to support easy comparison with UTF-8
51 /// strings.
52 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
53 pub struct Latin1String<'a> {
54     chars: &'a [u8],
55 }
56 
57 impl<'a> Latin1String<'a> {
58     /// Creates a new Latin-1 encoded string reference from the given bytes,
59     /// with each representing a character.
new(chars: &'a [u8]) -> Self60     pub const fn new(chars: &'a [u8]) -> Self {
61         Self { chars }
62     }
63 
64     /// Returns an iterator over the characters of the string.
65     ///
66     /// This simply converts each byte to `char`.
chars(&self) -> impl Iterator<Item = char> + Clone + 'a67     pub fn chars(&self) -> impl Iterator<Item = char> + Clone + 'a {
68         self.chars.iter().map(|b| *b as char)
69     }
70 }
71 
72 impl PartialEq<&str> for Latin1String<'_> {
eq(&self, other: &&str) -> bool73     fn eq(&self, other: &&str) -> bool {
74         self.chars().eq(other.chars())
75     }
76 }
77 
78 impl std::fmt::Display for Latin1String<'_> {
fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result79     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
80         for ch in self.chars() {
81             write!(f, "{}", ch)?;
82         }
83         Ok(())
84     }
85 }
86 
87 /// The PostScript standard string set.
88 ///
89 /// See "Appendix A - Standard Strings" in <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf>
90 pub const STANDARD_STRINGS: &[&str] = &[
91     ".notdef",
92     "space",
93     "exclam",
94     "quotedbl",
95     "numbersign",
96     "dollar",
97     "percent",
98     "ampersand",
99     "quoteright",
100     "parenleft",
101     "parenright",
102     "asterisk",
103     "plus",
104     "comma",
105     "hyphen",
106     "period",
107     "slash",
108     "zero",
109     "one",
110     "two",
111     "three",
112     "four",
113     "five",
114     "six",
115     "seven",
116     "eight",
117     "nine",
118     "colon",
119     "semicolon",
120     "less",
121     "equal",
122     "greater",
123     "question",
124     "at",
125     "A",
126     "B",
127     "C",
128     "D",
129     "E",
130     "F",
131     "G",
132     "H",
133     "I",
134     "J",
135     "K",
136     "L",
137     "M",
138     "N",
139     "O",
140     "P",
141     "Q",
142     "R",
143     "S",
144     "T",
145     "U",
146     "V",
147     "W",
148     "X",
149     "Y",
150     "Z",
151     "bracketleft",
152     "backslash",
153     "bracketright",
154     "asciicircum",
155     "underscore",
156     "quoteleft",
157     "a",
158     "b",
159     "c",
160     "d",
161     "e",
162     "f",
163     "g",
164     "h",
165     "i",
166     "j",
167     "k",
168     "l",
169     "m",
170     "n",
171     "o",
172     "p",
173     "q",
174     "r",
175     "s",
176     "t",
177     "u",
178     "v",
179     "w",
180     "x",
181     "y",
182     "z",
183     "braceleft",
184     "bar",
185     "braceright",
186     "asciitilde",
187     "exclamdown",
188     "cent",
189     "sterling",
190     "fraction",
191     "yen",
192     "florin",
193     "section",
194     "currency",
195     "quotesingle",
196     "quotedblleft",
197     "guillemotleft",
198     "guilsinglleft",
199     "guilsinglright",
200     "fi",
201     "fl",
202     "endash",
203     "dagger",
204     "daggerdbl",
205     "periodcentered",
206     "paragraph",
207     "bullet",
208     "quotesinglbase",
209     "quotedblbase",
210     "quotedblright",
211     "guillemotright",
212     "ellipsis",
213     "perthousand",
214     "questiondown",
215     "grave",
216     "acute",
217     "circumflex",
218     "tilde",
219     "macron",
220     "breve",
221     "dotaccent",
222     "dieresis",
223     "ring",
224     "cedilla",
225     "hungarumlaut",
226     "ogonek",
227     "caron",
228     "emdash",
229     "AE",
230     "ordfeminine",
231     "Lslash",
232     "Oslash",
233     "OE",
234     "ordmasculine",
235     "ae",
236     "dotlessi",
237     "lslash",
238     "oslash",
239     "oe",
240     "germandbls",
241     "onesuperior",
242     "logicalnot",
243     "mu",
244     "trademark",
245     "Eth",
246     "onehalf",
247     "plusminus",
248     "Thorn",
249     "onequarter",
250     "divide",
251     "brokenbar",
252     "degree",
253     "thorn",
254     "threequarters",
255     "twosuperior",
256     "registered",
257     "minus",
258     "eth",
259     "multiply",
260     "threesuperior",
261     "copyright",
262     "Aacute",
263     "Acircumflex",
264     "Adieresis",
265     "Agrave",
266     "Aring",
267     "Atilde",
268     "Ccedilla",
269     "Eacute",
270     "Ecircumflex",
271     "Edieresis",
272     "Egrave",
273     "Iacute",
274     "Icircumflex",
275     "Idieresis",
276     "Igrave",
277     "Ntilde",
278     "Oacute",
279     "Ocircumflex",
280     "Odieresis",
281     "Ograve",
282     "Otilde",
283     "Scaron",
284     "Uacute",
285     "Ucircumflex",
286     "Udieresis",
287     "Ugrave",
288     "Yacute",
289     "Ydieresis",
290     "Zcaron",
291     "aacute",
292     "acircumflex",
293     "adieresis",
294     "agrave",
295     "aring",
296     "atilde",
297     "ccedilla",
298     "eacute",
299     "ecircumflex",
300     "edieresis",
301     "egrave",
302     "iacute",
303     "icircumflex",
304     "idieresis",
305     "igrave",
306     "ntilde",
307     "oacute",
308     "ocircumflex",
309     "odieresis",
310     "ograve",
311     "otilde",
312     "scaron",
313     "uacute",
314     "ucircumflex",
315     "udieresis",
316     "ugrave",
317     "yacute",
318     "ydieresis",
319     "zcaron",
320     "exclamsmall",
321     "Hungarumlautsmall",
322     "dollaroldstyle",
323     "dollarsuperior",
324     "ampersandsmall",
325     "Acutesmall",
326     "parenleftsuperior",
327     "parenrightsuperior",
328     "twodotenleader",
329     "onedotenleader",
330     "zerooldstyle",
331     "oneoldstyle",
332     "twooldstyle",
333     "threeoldstyle",
334     "fouroldstyle",
335     "fiveoldstyle",
336     "sixoldstyle",
337     "sevenoldstyle",
338     "eightoldstyle",
339     "nineoldstyle",
340     "commasuperior",
341     "threequartersemdash",
342     "periodsuperior",
343     "questionsmall",
344     "asuperior",
345     "bsuperior",
346     "centsuperior",
347     "dsuperior",
348     "esuperior",
349     "isuperior",
350     "lsuperior",
351     "msuperior",
352     "nsuperior",
353     "osuperior",
354     "rsuperior",
355     "ssuperior",
356     "tsuperior",
357     "ff",
358     "ffi",
359     "ffl",
360     "parenleftinferior",
361     "parenrightinferior",
362     "Circumflexsmall",
363     "hyphensuperior",
364     "Gravesmall",
365     "Asmall",
366     "Bsmall",
367     "Csmall",
368     "Dsmall",
369     "Esmall",
370     "Fsmall",
371     "Gsmall",
372     "Hsmall",
373     "Ismall",
374     "Jsmall",
375     "Ksmall",
376     "Lsmall",
377     "Msmall",
378     "Nsmall",
379     "Osmall",
380     "Psmall",
381     "Qsmall",
382     "Rsmall",
383     "Ssmall",
384     "Tsmall",
385     "Usmall",
386     "Vsmall",
387     "Wsmall",
388     "Xsmall",
389     "Ysmall",
390     "Zsmall",
391     "colonmonetary",
392     "onefitted",
393     "rupiah",
394     "Tildesmall",
395     "exclamdownsmall",
396     "centoldstyle",
397     "Lslashsmall",
398     "Scaronsmall",
399     "Zcaronsmall",
400     "Dieresissmall",
401     "Brevesmall",
402     "Caronsmall",
403     "Dotaccentsmall",
404     "Macronsmall",
405     "figuredash",
406     "hypheninferior",
407     "Ogoneksmall",
408     "Ringsmall",
409     "Cedillasmall",
410     "questiondownsmall",
411     "oneeighth",
412     "threeeighths",
413     "fiveeighths",
414     "seveneighths",
415     "onethird",
416     "twothirds",
417     "zerosuperior",
418     "foursuperior",
419     "fivesuperior",
420     "sixsuperior",
421     "sevensuperior",
422     "eightsuperior",
423     "ninesuperior",
424     "zeroinferior",
425     "oneinferior",
426     "twoinferior",
427     "threeinferior",
428     "fourinferior",
429     "fiveinferior",
430     "sixinferior",
431     "seveninferior",
432     "eightinferior",
433     "nineinferior",
434     "centinferior",
435     "dollarinferior",
436     "periodinferior",
437     "commainferior",
438     "Agravesmall",
439     "Aacutesmall",
440     "Acircumflexsmall",
441     "Atildesmall",
442     "Adieresissmall",
443     "Aringsmall",
444     "AEsmall",
445     "Ccedillasmall",
446     "Egravesmall",
447     "Eacutesmall",
448     "Ecircumflexsmall",
449     "Edieresissmall",
450     "Igravesmall",
451     "Iacutesmall",
452     "Icircumflexsmall",
453     "Idieresissmall",
454     "Ethsmall",
455     "Ntildesmall",
456     "Ogravesmall",
457     "Oacutesmall",
458     "Ocircumflexsmall",
459     "Otildesmall",
460     "Odieresissmall",
461     "OEsmall",
462     "Oslashsmall",
463     "Ugravesmall",
464     "Uacutesmall",
465     "Ucircumflexsmall",
466     "Udieresissmall",
467     "Yacutesmall",
468     "Thornsmall",
469     "Ydieresissmall",
470     "001.000",
471     "001.001",
472     "001.002",
473     "001.003",
474     "Black",
475     "Bold",
476     "Book",
477     "Light",
478     "Medium",
479     "Regular",
480     "Roman",
481     "Semibold",
482 ];
483 
484 #[cfg(test)]
485 mod tests {
486     use super::{Latin1String, StringId, STANDARD_STRINGS};
487 
488     #[test]
lets_latin1()489     fn lets_latin1() {
490         let latin1 = Latin1String::new(&[223, 214, 209, 208]);
491         let utf8 = "ßÖÑÐ";
492         assert_ne!(latin1.chars, utf8.as_bytes());
493         assert_eq!(latin1, utf8);
494     }
495 
496     #[test]
standard_strings()497     fn standard_strings() {
498         for (i, &std_string) in STANDARD_STRINGS.iter().enumerate() {
499             let sid = StringId::new(i as _);
500             let latin1 = sid.standard_string().unwrap();
501             // Ensure we can compare directly with &str
502             assert_eq!(latin1, std_string);
503             // Ensure our to_string() conversion works (via the Display impl)
504             assert_eq!(latin1.to_string(), std_string);
505         }
506     }
507 
508     #[test]
not_a_standard_string()509     fn not_a_standard_string() {
510         let sid = StringId::new(STANDARD_STRINGS.len() as _);
511         assert!(sid.standard_string().is_err());
512         assert_eq!(sid.standard_string().unwrap_err(), 0);
513     }
514 }
515