├── .github └── workflows │ └── build.yml ├── .gitignore ├── Cargo.toml ├── LICENSE.md ├── README.md ├── benches ├── compact1.rs ├── support │ └── mod.rs └── type2.rs ├── src ├── compact1 │ ├── character_set.rs │ ├── encoding.rs │ ├── font_set │ │ ├── character_id_keyed.rs │ │ ├── character_name_keyed.rs │ │ └── mod.rs │ ├── header.rs │ ├── index │ │ ├── character_strings.rs │ │ ├── dictionaries.rs │ │ ├── mod.rs │ │ ├── names.rs │ │ ├── strings.rs │ │ └── subroutines.rs │ ├── mod.rs │ ├── number.rs │ ├── offset.rs │ └── operation.rs ├── lib.rs ├── type1 │ └── mod.rs └── type2 │ ├── mod.rs │ ├── number.rs │ ├── operation.rs │ └── program.rs └── tests ├── compact1.rs ├── fixtures ├── Hirakatana-Regular.otf ├── NotoSansJP-Regular.otf └── SourceSerifPro-Regular.otf ├── support └── mod.rs └── type2.rs /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | check: 14 | runs-on: macos-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - run: rustup toolchain install stable --profile=minimal --component clippy --component rustfmt 18 | - run: cargo clippy -- -D warnings 19 | - run: cargo fmt --all -- --check 20 | 21 | bench: 22 | strategy: 23 | matrix: 24 | os: [macos-latest, ubuntu-latest] 25 | runs-on: ${{ matrix.os }} 26 | steps: 27 | - uses: actions/checkout@v4 28 | - run: rustup toolchain install nightly --profile=minimal 29 | - run: cargo +nightly bench 30 | 31 | test: 32 | strategy: 33 | matrix: 34 | os: [macos-latest, ubuntu-latest] 35 | runs-on: ${{ matrix.os }} 36 | steps: 37 | - uses: actions/checkout@v4 38 | - run: rustup toolchain install stable --profile=minimal 39 | - run: cargo test 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "postscript" 3 | version = "0.19.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | authors = ["Ivan Ukhov "] 7 | description = "The package provides a parser of PostScript fonts." 8 | documentation = "https://docs.rs/postscript" 9 | homepage = "https://github.com/bodoni/postscript" 10 | repository = "https://github.com/bodoni/postscript" 11 | readme = "README.md" 12 | categories = ["parsing"] 13 | keywords = ["font", "postscript", "typeface", "typography"] 14 | exclude = ["tests/fixtures/*"] 15 | 16 | [dependencies] 17 | typeface = "0.5" 18 | 19 | [dev-dependencies] 20 | random = "0.14" 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | The project is dual licensed under the terms of the Apache License, Version 2.0, 4 | and the MIT License. You may obtain copies of the two licenses at 5 | 6 | * https://www.apache.org/licenses/LICENSE-2.0 and 7 | * https://opensource.org/licenses/MIT, respectively. 8 | 9 | The following two notices apply to every file of the project. 10 | 11 | ## The Apache License 12 | 13 | ``` 14 | Copyright 2015–2025 The postscript Developers 15 | 16 | Licensed under the Apache License, Version 2.0 (the “License”); you may not use 17 | this file except in compliance with the License. You may obtain a copy of the 18 | License at 19 | 20 | http://www.apache.org/licenses/LICENSE-2.0 21 | 22 | Unless required by applicable law or agreed to in writing, software distributed 23 | under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR 24 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 25 | specific language governing permissions and limitations under the License. 26 | ``` 27 | 28 | ## The MIT License 29 | 30 | ``` 31 | Copyright 2015–2025 The postscript Developers 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy of 34 | this software and associated documentation files (the “Software”), to deal in 35 | the Software without restriction, including without limitation the rights to 36 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 37 | the Software, and to permit persons to whom the Software is furnished to do so, 38 | subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all 41 | copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 45 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 46 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 47 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 48 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 49 | ``` 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PostScript [![Package][package-img]][package-url] [![Documentation][documentation-img]][documentation-url] [![Build][build-img]][build-url] 2 | 3 | The package provides a parser of PostScript fonts. It is recommended to use a 4 | higher-level abstraction called [`opentype`][opentype], which internally relies 5 | on this package. 6 | 7 | ## Contribution 8 | 9 | Your contribution is highly appreciated. Do not hesitate to open an issue or a 10 | pull request. Note that any contribution submitted for inclusion in the project 11 | will be licensed according to the terms given in [LICENSE.md](LICENSE.md). 12 | 13 | [opentype]: https://github.com/bodoni/opentype 14 | 15 | [build-img]: https://github.com/bodoni/postscript/workflows/build/badge.svg 16 | [build-url]: https://github.com/bodoni/postscript/actions/workflows/build.yml 17 | [documentation-img]: https://docs.rs/postscript/badge.svg 18 | [documentation-url]: https://docs.rs/postscript 19 | [package-img]: https://img.shields.io/crates/v/postscript.svg 20 | [package-url]: https://crates.io/crates/postscript 21 | -------------------------------------------------------------------------------- /benches/compact1.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate postscript; 4 | extern crate random; 5 | extern crate test; 6 | 7 | #[macro_use] 8 | mod support; 9 | 10 | mod encoding { 11 | use postscript::compact1::Encoding; 12 | use random::Source; 13 | use test::{black_box, Bencher}; 14 | 15 | #[bench] 16 | fn get(bencher: &mut Bencher) { 17 | let mut source = random::default(42); 18 | let codes = source 19 | .iter::() 20 | .take(1000) 21 | .map(|number| (number as u16) % 256) 22 | .collect::>(); 23 | let encoding = Encoding::Standard; 24 | bencher.iter(|| { 25 | for &code in &codes { 26 | black_box(encoding.get(code)); 27 | } 28 | }); 29 | } 30 | } 31 | 32 | mod operation { 33 | use postscript::compact1::Operator; 34 | use random::Source; 35 | use test::{black_box, Bencher}; 36 | 37 | #[bench] 38 | fn operator_default(bencher: &mut Bencher) { 39 | let mut source = random::default(42); 40 | let operators = generate_operators(&mut source, 1000); 41 | bencher.iter(|| { 42 | for &operator in &operators { 43 | black_box(operator.default()); 44 | } 45 | }); 46 | } 47 | 48 | #[bench] 49 | fn operator_get(bencher: &mut Bencher) { 50 | let mut source = random::default(42); 51 | let codes = generate_codes(&mut source, 1000); 52 | bencher.iter(|| { 53 | for &code in &codes { 54 | black_box(ok!(Operator::from(code))); 55 | } 56 | }); 57 | } 58 | 59 | fn generate_codes(source: &mut T, count: usize) -> Vec { 60 | let mut codes = vec![]; 61 | while codes.len() != count { 62 | if (source.read::() as i64) > 0 { 63 | loop { 64 | let number = source.read::(); 65 | match (number % (0x15 + 1)) as u16 { 66 | 0x0c => continue, 67 | code => codes.push(code), 68 | } 69 | break; 70 | } 71 | } else { 72 | loop { 73 | let number = source.read::(); 74 | match 0x0c00 | (number % (0x26 + 1)) as u16 { 75 | 0x0c0f..=0x0c10 | 0x0c18..=0x0c1d => continue, 76 | code => codes.push(code), 77 | } 78 | break; 79 | } 80 | } 81 | } 82 | codes 83 | } 84 | 85 | fn generate_operators(source: &mut T, count: usize) -> Vec { 86 | generate_codes(source, count) 87 | .iter() 88 | .map(|&code| ok!(Operator::from(code))) 89 | .collect() 90 | } 91 | } 92 | 93 | mod noto_sans { 94 | use test::Bencher; 95 | 96 | use crate::support::{setup_font_set, Fixture}; 97 | 98 | #[bench] 99 | fn font_set(bencher: &mut Bencher) { 100 | bencher.iter(|| { 101 | setup_font_set(Fixture::NotoSansJP); 102 | }); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /benches/support/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::path::PathBuf; 3 | 4 | use postscript::compact1::FontSet; 5 | use postscript::value::Read; 6 | 7 | macro_rules! ok(($result:expr) => ($result.unwrap())); 8 | 9 | #[allow(dead_code)] 10 | pub enum Fixture { 11 | NotoSansJP, 12 | SourceSerifPro, 13 | } 14 | 15 | impl Fixture { 16 | pub fn path(&self) -> PathBuf { 17 | match *self { 18 | Fixture::NotoSansJP => "tests/fixtures/NotoSansJP-Regular.otf".into(), 19 | Fixture::SourceSerifPro => "tests/fixtures/SourceSerifPro-Regular.otf".into(), 20 | } 21 | } 22 | 23 | pub fn offset(&self) -> u64 { 24 | match *self { 25 | Fixture::NotoSansJP => 337316, 26 | Fixture::SourceSerifPro => 17732, 27 | } 28 | } 29 | } 30 | 31 | pub fn setup(fixture: Fixture) -> File { 32 | use std::io::{Seek, SeekFrom}; 33 | 34 | let mut file = ok!(File::open(fixture.path())); 35 | ok!(file.seek(SeekFrom::Start(fixture.offset()))); 36 | file 37 | } 38 | 39 | pub fn setup_font_set(fixture: Fixture) -> FontSet { 40 | let mut file = setup(fixture); 41 | ok!(FontSet::read(&mut file)) 42 | } 43 | -------------------------------------------------------------------------------- /benches/type2.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate postscript; 4 | extern crate test; 5 | 6 | #[macro_use] 7 | mod support; 8 | 9 | mod source_serif { 10 | use postscript::compact1::font_set::Record; 11 | use postscript::type2::Program; 12 | use test::Bencher; 13 | 14 | use crate::support::{setup_font_set, Fixture}; 15 | 16 | #[bench] 17 | fn program(bencher: &mut Bencher) { 18 | let set = setup_font_set(Fixture::SourceSerifPro); 19 | let global = &set.subroutines; 20 | let local = match &set.records[0] { 21 | Record::CharacterNameKeyed(ref record) => &*record.subroutines, 22 | _ => unreachable!(), 23 | }; 24 | bencher.iter(|| { 25 | for code in set.character_strings[0].iter() { 26 | let mut program = Program::new(code, global, local); 27 | while let Some(..) = ok!(program.next()) {} 28 | } 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/compact1/character_set.rs: -------------------------------------------------------------------------------- 1 | //! The character sets. 2 | 3 | use crate::compact1::{GlyphID, StringID}; 4 | use crate::Result; 5 | 6 | /// A character set. 7 | #[derive(Clone, Debug)] 8 | pub enum CharacterSet { 9 | ISOAdobe, 10 | Expert, 11 | ExpertSubset, 12 | Format0(CharacterSet0), 13 | Format1(CharacterSet1), 14 | Format2(CharacterSet2), 15 | } 16 | 17 | /// A character set in format 0. 18 | #[derive(Clone, Debug)] 19 | pub struct CharacterSet0 { 20 | pub format: u8, // format 21 | pub glyphs: Vec, // glyph 22 | } 23 | 24 | /// A character set in format 1. 25 | #[derive(Clone, Debug)] 26 | pub struct CharacterSet1 { 27 | pub format: u8, // format 28 | pub ranges: Vec, // Range1 29 | } 30 | 31 | /// A character set in format 2. 32 | #[derive(Clone, Debug)] 33 | pub struct CharacterSet2 { 34 | pub format: u8, // format 35 | pub ranges: Vec, // Range2 36 | } 37 | 38 | table! { 39 | /// A range of a character set in format 1. 40 | #[derive(Copy)] 41 | pub Range1 { 42 | first_string_id (StringID), // first 43 | left_count (u8 ), // nLeft 44 | } 45 | } 46 | 47 | table! { 48 | /// A range of a character set in format 2. 49 | #[derive(Copy)] 50 | pub Range2 { 51 | first_string_id (StringID), // first 52 | left_count (u16 ), // nLeft 53 | } 54 | } 55 | 56 | impl CharacterSet { 57 | /// Return the name of a glyph. 58 | #[inline] 59 | pub fn get(&self, glyph_id: GlyphID) -> Option<&'static str> { 60 | match self { 61 | CharacterSet::ISOAdobe => get_iso_adobe(glyph_id), 62 | CharacterSet::Expert => get_expert(glyph_id), 63 | CharacterSet::ExpertSubset => get_expert_subset(glyph_id), 64 | CharacterSet::Format0(ref char_set) => char_set.get(glyph_id), 65 | CharacterSet::Format1(ref char_set) => char_set.get(glyph_id), 66 | CharacterSet::Format2(ref char_set) => char_set.get(glyph_id), 67 | } 68 | } 69 | } 70 | 71 | impl crate::walue::Read<'static> for CharacterSet { 72 | type Parameter = usize; 73 | 74 | fn read(tape: &mut T, glyph_count: usize) -> Result { 75 | Ok(match tape.peek::()? { 76 | 0 => CharacterSet::Format0(tape.take_given(glyph_count)?), 77 | 1 => CharacterSet::Format1(tape.take_given(glyph_count)?), 78 | 2 => CharacterSet::Format2(tape.take_given(glyph_count)?), 79 | format => raise!("found an unknown format of character sets ({format})"), 80 | }) 81 | } 82 | } 83 | 84 | impl CharacterSet0 { 85 | #[inline] 86 | fn get(&self, _: GlyphID) -> Option<&'static str> { 87 | None 88 | } 89 | } 90 | 91 | impl crate::walue::Read<'static> for CharacterSet0 { 92 | type Parameter = usize; 93 | 94 | fn read(tape: &mut T, glyph_count: usize) -> Result { 95 | let format = tape.take::()?; 96 | if format != 0 { 97 | raise!("found a malformed character set"); 98 | } 99 | Ok(CharacterSet0 { 100 | format, 101 | glyphs: tape.take_given(glyph_count - 1)?, 102 | }) 103 | } 104 | } 105 | 106 | impl CharacterSet1 { 107 | #[inline] 108 | fn get(&self, _: GlyphID) -> Option<&'static str> { 109 | None 110 | } 111 | } 112 | 113 | impl crate::walue::Read<'static> for CharacterSet1 { 114 | type Parameter = usize; 115 | 116 | fn read(tape: &mut T, glyph_count: usize) -> Result { 117 | let format = tape.take::()?; 118 | if format != 1 { 119 | raise!("found a malformed character set"); 120 | } 121 | let mut ranges = vec![]; 122 | #[allow(clippy::identity_op)] 123 | let mut found_count = 0 + 1; 124 | while found_count < glyph_count { 125 | let range = tape.take::()?; 126 | found_count += 1 + range.left_count as usize; 127 | ranges.push(range); 128 | } 129 | if found_count != glyph_count { 130 | raise!("found a malformed character set"); 131 | } 132 | Ok(CharacterSet1 { format, ranges }) 133 | } 134 | } 135 | 136 | impl CharacterSet2 { 137 | #[inline] 138 | fn get(&self, _: GlyphID) -> Option<&'static str> { 139 | None 140 | } 141 | } 142 | 143 | impl crate::walue::Read<'static> for CharacterSet2 { 144 | type Parameter = usize; 145 | 146 | fn read(tape: &mut T, glyph_count: usize) -> Result { 147 | macro_rules! reject(() => (raise!("found a malformed character set"))); 148 | let format = tape.take::()?; 149 | if format != 2 { 150 | reject!(); 151 | } 152 | let mut ranges = vec![]; 153 | #[allow(clippy::identity_op)] 154 | let mut found_count = 0 + 1; 155 | while found_count < glyph_count { 156 | let range = tape.take::()?; 157 | found_count += 1 + range.left_count as usize; 158 | ranges.push(range); 159 | } 160 | if found_count != glyph_count { 161 | reject!(); 162 | } 163 | Ok(CharacterSet2 { format, ranges }) 164 | } 165 | } 166 | 167 | fn get_iso_adobe(glyph_id: GlyphID) -> Option<&'static str> { 168 | Some(match glyph_id { 169 | 1 => "space", 170 | 2 => "exclam", 171 | 3 => "quotedbl", 172 | 4 => "numbersign", 173 | 5 => "dollar", 174 | 6 => "percent", 175 | 7 => "ampersand", 176 | 8 => "quoteright", 177 | 9 => "parenleft", 178 | 10 => "parenright", 179 | 11 => "asterisk", 180 | 12 => "plus", 181 | 13 => "comma", 182 | 14 => "hyphen", 183 | 15 => "period", 184 | 16 => "slash", 185 | 17 => "zero", 186 | 18 => "one", 187 | 19 => "two", 188 | 20 => "three", 189 | 21 => "four", 190 | 22 => "five", 191 | 23 => "six", 192 | 24 => "seven", 193 | 25 => "eight", 194 | 26 => "nine", 195 | 27 => "colon", 196 | 28 => "semicolon", 197 | 29 => "less", 198 | 30 => "equal", 199 | 31 => "greater", 200 | 32 => "question", 201 | 33 => "at", 202 | 34 => "A", 203 | 35 => "B", 204 | 36 => "C", 205 | 37 => "D", 206 | 38 => "E", 207 | 39 => "F", 208 | 40 => "G", 209 | 41 => "H", 210 | 42 => "I", 211 | 43 => "J", 212 | 44 => "K", 213 | 45 => "L", 214 | 46 => "M", 215 | 47 => "N", 216 | 48 => "O", 217 | 49 => "P", 218 | 50 => "Q", 219 | 51 => "R", 220 | 52 => "S", 221 | 53 => "T", 222 | 54 => "U", 223 | 55 => "V", 224 | 56 => "W", 225 | 57 => "X", 226 | 58 => "Y", 227 | 59 => "Z", 228 | 60 => "bracketleft", 229 | 61 => "backslash", 230 | 62 => "bracketright", 231 | 63 => "asciicircum", 232 | 64 => "underscore", 233 | 65 => "quoteleft", 234 | 66 => "a", 235 | 67 => "b", 236 | 68 => "c", 237 | 69 => "d", 238 | 70 => "e", 239 | 71 => "f", 240 | 72 => "g", 241 | 73 => "h", 242 | 74 => "i", 243 | 75 => "j", 244 | 76 => "k", 245 | 77 => "l", 246 | 78 => "m", 247 | 79 => "n", 248 | 80 => "o", 249 | 81 => "p", 250 | 82 => "q", 251 | 83 => "r", 252 | 84 => "s", 253 | 85 => "t", 254 | 86 => "u", 255 | 87 => "v", 256 | 88 => "w", 257 | 89 => "x", 258 | 90 => "y", 259 | 91 => "z", 260 | 92 => "braceleft", 261 | 93 => "bar", 262 | 94 => "braceright", 263 | 95 => "asciitilde", 264 | 96 => "exclamdown", 265 | 97 => "cent", 266 | 98 => "sterling", 267 | 99 => "fraction", 268 | 100 => "yen", 269 | 101 => "florin", 270 | 102 => "section", 271 | 103 => "currency", 272 | 104 => "quotesingle", 273 | 105 => "quotedblleft", 274 | 106 => "guillemotleft", 275 | 107 => "guilsinglleft", 276 | 108 => "guilsinglright", 277 | 109 => "fi", 278 | 110 => "fl", 279 | 111 => "endash", 280 | 112 => "dagger", 281 | 113 => "daggerdbl", 282 | 114 => "periodcentered", 283 | 115 => "paragraph", 284 | 116 => "bullet", 285 | 117 => "quotesinglbase", 286 | 118 => "quotedblbase", 287 | 119 => "quotedblright", 288 | 120 => "guillemotright", 289 | 121 => "ellipsis", 290 | 122 => "perthousand", 291 | 123 => "questiondown", 292 | 124 => "grave", 293 | 125 => "acute", 294 | 126 => "circumflex", 295 | 127 => "tilde", 296 | 128 => "macron", 297 | 129 => "breve", 298 | 130 => "dotaccent", 299 | 131 => "dieresis", 300 | 132 => "ring", 301 | 133 => "cedilla", 302 | 134 => "hungarumlaut", 303 | 135 => "ogonek", 304 | 136 => "caron", 305 | 137 => "emdash", 306 | 138 => "AE", 307 | 139 => "ordfeminine", 308 | 140 => "Lslash", 309 | 141 => "Oslash", 310 | 142 => "OE", 311 | 143 => "ordmasculine", 312 | 144 => "ae", 313 | 145 => "dotlessi", 314 | 146 => "lslash", 315 | 147 => "oslash", 316 | 148 => "oe", 317 | 149 => "germandbls", 318 | 150 => "onesuperior", 319 | 151 => "logicalnot", 320 | 152 => "mu", 321 | 153 => "trademark", 322 | 154 => "Eth", 323 | 155 => "onehalf", 324 | 156 => "plusminus", 325 | 157 => "Thorn", 326 | 158 => "onequarter", 327 | 159 => "divide", 328 | 160 => "brokenbar", 329 | 161 => "degree", 330 | 162 => "thorn", 331 | 163 => "threequarters", 332 | 164 => "twosuperior", 333 | 165 => "registered", 334 | 166 => "minus", 335 | 167 => "eth", 336 | 168 => "multiply", 337 | 169 => "threesuperior", 338 | 170 => "copyright", 339 | 171 => "Aacute", 340 | 172 => "Acircumflex", 341 | 173 => "Adieresis", 342 | 174 => "Agrave", 343 | 175 => "Aring", 344 | 176 => "Atilde", 345 | 177 => "Ccedilla", 346 | 178 => "Eacute", 347 | 179 => "Ecircumflex", 348 | 180 => "Edieresis", 349 | 181 => "Egrave", 350 | 182 => "Iacute", 351 | 183 => "Icircumflex", 352 | 184 => "Idieresis", 353 | 185 => "Igrave", 354 | 186 => "Ntilde", 355 | 187 => "Oacute", 356 | 188 => "Ocircumflex", 357 | 189 => "Odieresis", 358 | 190 => "Ograve", 359 | 191 => "Otilde", 360 | 192 => "Scaron", 361 | 193 => "Uacute", 362 | 194 => "Ucircumflex", 363 | 195 => "Udieresis", 364 | 196 => "Ugrave", 365 | 197 => "Yacute", 366 | 198 => "Ydieresis", 367 | 199 => "Zcaron", 368 | 200 => "aacute", 369 | 201 => "acircumflex", 370 | 202 => "adieresis", 371 | 203 => "agrave", 372 | 204 => "aring", 373 | 205 => "atilde", 374 | 206 => "ccedilla", 375 | 207 => "eacute", 376 | 208 => "ecircumflex", 377 | 209 => "edieresis", 378 | 210 => "egrave", 379 | 211 => "iacute", 380 | 212 => "icircumflex", 381 | 213 => "idieresis", 382 | 214 => "igrave", 383 | 215 => "ntilde", 384 | 216 => "oacute", 385 | 217 => "ocircumflex", 386 | 218 => "odieresis", 387 | 219 => "ograve", 388 | 220 => "otilde", 389 | 221 => "scaron", 390 | 222 => "uacute", 391 | 223 => "ucircumflex", 392 | 224 => "udieresis", 393 | 225 => "ugrave", 394 | 226 => "yacute", 395 | 227 => "ydieresis", 396 | 228 => "zcaron", 397 | _ => return None, 398 | }) 399 | } 400 | 401 | fn get_expert(glyph_id: GlyphID) -> Option<&'static str> { 402 | Some(match glyph_id { 403 | 1 => "space", 404 | 229 => "exclamsmall", 405 | 230 => "Hungarumlautsmall", 406 | 231 => "dollaroldstyle", 407 | 232 => "dollarsuperior", 408 | 233 => "ampersandsmall", 409 | 234 => "Acutesmall", 410 | 235 => "parenleftsuperior", 411 | 236 => "parenrightsuperior", 412 | 237 => "twodotenleader", 413 | 238 => "onedotenleader", 414 | 13 => "comma", 415 | 14 => "hyphen", 416 | 15 => "period", 417 | 99 => "fraction", 418 | 239 => "zerooldstyle", 419 | 240 => "oneoldstyle", 420 | 241 => "twooldstyle", 421 | 242 => "threeoldstyle", 422 | 243 => "fouroldstyle", 423 | 244 => "fiveoldstyle", 424 | 245 => "sixoldstyle", 425 | 246 => "sevenoldstyle", 426 | 247 => "eightoldstyle", 427 | 248 => "nineoldstyle", 428 | 27 => "colon", 429 | 28 => "semicolon", 430 | 249 => "commasuperior", 431 | 250 => "threequartersemdash", 432 | 251 => "periodsuperior", 433 | 252 => "questionsmall", 434 | 253 => "asuperior", 435 | 254 => "bsuperior", 436 | 255 => "centsuperior", 437 | 256 => "dsuperior", 438 | 257 => "esuperior", 439 | 258 => "isuperior", 440 | 259 => "lsuperior", 441 | 260 => "msuperior", 442 | 261 => "nsuperior", 443 | 262 => "osuperior", 444 | 263 => "rsuperior", 445 | 264 => "ssuperior", 446 | 265 => "tsuperior", 447 | 266 => "ff", 448 | 109 => "fi", 449 | 110 => "fl", 450 | 267 => "ffi", 451 | 268 => "ffl", 452 | 269 => "parenleftinferior", 453 | 270 => "parenrightinferior", 454 | 271 => "Circumflexsmall", 455 | 272 => "hyphensuperior", 456 | 273 => "Gravesmall", 457 | 274 => "Asmall", 458 | 275 => "Bsmall", 459 | 276 => "Csmall", 460 | 277 => "Dsmall", 461 | 278 => "Esmall", 462 | 279 => "Fsmall", 463 | 280 => "Gsmall", 464 | 281 => "Hsmall", 465 | 282 => "Ismall", 466 | 283 => "Jsmall", 467 | 284 => "Ksmall", 468 | 285 => "Lsmall", 469 | 286 => "Msmall", 470 | 287 => "Nsmall", 471 | 288 => "Osmall", 472 | 289 => "Psmall", 473 | 290 => "Qsmall", 474 | 291 => "Rsmall", 475 | 292 => "Ssmall", 476 | 293 => "Tsmall", 477 | 294 => "Usmall", 478 | 295 => "Vsmall", 479 | 296 => "Wsmall", 480 | 297 => "Xsmall", 481 | 298 => "Ysmall", 482 | 299 => "Zsmall", 483 | 300 => "colonmonetary", 484 | 301 => "onefitted", 485 | 302 => "rupiah", 486 | 303 => "Tildesmall", 487 | 304 => "exclamdownsmall", 488 | 305 => "centoldstyle", 489 | 306 => "Lslashsmall", 490 | 307 => "Scaronsmall", 491 | 308 => "Zcaronsmall", 492 | 309 => "Dieresissmall", 493 | 310 => "Brevesmall", 494 | 311 => "Caronsmall", 495 | 312 => "Dotaccentsmall", 496 | 313 => "Macronsmall", 497 | 314 => "figuredash", 498 | 315 => "hypheninferior", 499 | 316 => "Ogoneksmall", 500 | 317 => "Ringsmall", 501 | 318 => "Cedillasmall", 502 | 158 => "onequarter", 503 | 155 => "onehalf", 504 | 163 => "threequarters", 505 | 319 => "questiondownsmall", 506 | 320 => "oneeighth", 507 | 321 => "threeeighths", 508 | 322 => "fiveeighths", 509 | 323 => "seveneighths", 510 | 324 => "onethird", 511 | 325 => "twothirds", 512 | 326 => "zerosuperior", 513 | 150 => "onesuperior", 514 | 164 => "twosuperior", 515 | 169 => "threesuperior", 516 | 327 => "foursuperior", 517 | 328 => "fivesuperior", 518 | 329 => "sixsuperior", 519 | 330 => "sevensuperior", 520 | 331 => "eightsuperior", 521 | 332 => "ninesuperior", 522 | 333 => "zeroinferior", 523 | 334 => "oneinferior", 524 | 335 => "twoinferior", 525 | 336 => "threeinferior", 526 | 337 => "fourinferior", 527 | 338 => "fiveinferior", 528 | 339 => "sixinferior", 529 | 340 => "seveninferior", 530 | 341 => "eightinferior", 531 | 342 => "nineinferior", 532 | 343 => "centinferior", 533 | 344 => "dollarinferior", 534 | 345 => "periodinferior", 535 | 346 => "commainferior", 536 | 347 => "Agravesmall", 537 | 348 => "Aacutesmall", 538 | 349 => "Acircumflexsmall", 539 | 350 => "Atildesmall", 540 | 351 => "Adieresissmall", 541 | 352 => "Aringsmall", 542 | 353 => "AEsmall", 543 | 354 => "Ccedillasmall", 544 | 355 => "Egravesmall", 545 | 356 => "Eacutesmall", 546 | 357 => "Ecircumflexsmall", 547 | 358 => "Edieresissmall", 548 | 359 => "Igravesmall", 549 | 360 => "Iacutesmall", 550 | 361 => "Icircumflexsmall", 551 | 362 => "Idieresissmall", 552 | 363 => "Ethsmall", 553 | 364 => "Ntildesmall", 554 | 365 => "Ogravesmall", 555 | 366 => "Oacutesmall", 556 | 367 => "Ocircumflexsmall", 557 | 368 => "Otildesmall", 558 | 369 => "Odieresissmall", 559 | 370 => "OEsmall", 560 | 371 => "Oslashsmall", 561 | 372 => "Ugravesmall", 562 | 373 => "Uacutesmall", 563 | 374 => "Ucircumflexsmall", 564 | 375 => "Udieresissmall", 565 | 376 => "Yacutesmall", 566 | 377 => "Thornsmall", 567 | 378 => "Ydieresissmall", 568 | _ => return None, 569 | }) 570 | } 571 | 572 | fn get_expert_subset(glyph_id: GlyphID) -> Option<&'static str> { 573 | Some(match glyph_id { 574 | 1 => "space", 575 | 231 => "dollaroldstyle", 576 | 232 => "dollarsuperior", 577 | 235 => "parenleftsuperior", 578 | 236 => "parenrightsuperior", 579 | 237 => "twodotenleader", 580 | 238 => "onedotenleader", 581 | 13 => "comma", 582 | 14 => "hyphen", 583 | 15 => "period", 584 | 99 => "fraction", 585 | 239 => "zerooldstyle", 586 | 240 => "oneoldstyle", 587 | 241 => "twooldstyle", 588 | 242 => "threeoldstyle", 589 | 243 => "fouroldstyle", 590 | 244 => "fiveoldstyle", 591 | 245 => "sixoldstyle", 592 | 246 => "sevenoldstyle", 593 | 247 => "eightoldstyle", 594 | 248 => "nineoldstyle", 595 | 27 => "colon", 596 | 28 => "semicolon", 597 | 249 => "commasuperior", 598 | 250 => "threequartersemdash", 599 | 251 => "periodsuperior", 600 | 253 => "asuperior", 601 | 254 => "bsuperior", 602 | 255 => "centsuperior", 603 | 256 => "dsuperior", 604 | 257 => "esuperior", 605 | 258 => "isuperior", 606 | 259 => "lsuperior", 607 | 260 => "msuperior", 608 | 261 => "nsuperior", 609 | 262 => "osuperior", 610 | 263 => "rsuperior", 611 | 264 => "ssuperior", 612 | 265 => "tsuperior", 613 | 266 => "ff", 614 | 109 => "fi", 615 | 110 => "fl", 616 | 267 => "ffi", 617 | 268 => "ffl", 618 | 269 => "parenleftinferior", 619 | 270 => "parenrightinferior", 620 | 272 => "hyphensuperior", 621 | 300 => "colonmonetary", 622 | 301 => "onefitted", 623 | 302 => "rupiah", 624 | 305 => "centoldstyle", 625 | 314 => "figuredash", 626 | 315 => "hypheninferior", 627 | 158 => "onequarter", 628 | 155 => "onehalf", 629 | 163 => "threequarters", 630 | 320 => "oneeighth", 631 | 321 => "threeeighths", 632 | 322 => "fiveeighths", 633 | 323 => "seveneighths", 634 | 324 => "onethird", 635 | 325 => "twothirds", 636 | 326 => "zerosuperior", 637 | 150 => "onesuperior", 638 | 164 => "twosuperior", 639 | 169 => "threesuperior", 640 | 327 => "foursuperior", 641 | 328 => "fivesuperior", 642 | 329 => "sixsuperior", 643 | 330 => "sevensuperior", 644 | 331 => "eightsuperior", 645 | 332 => "ninesuperior", 646 | 333 => "zeroinferior", 647 | 334 => "oneinferior", 648 | 335 => "twoinferior", 649 | 336 => "threeinferior", 650 | 337 => "fourinferior", 651 | 338 => "fiveinferior", 652 | 339 => "sixinferior", 653 | 340 => "seveninferior", 654 | 341 => "eightinferior", 655 | 342 => "nineinferior", 656 | 343 => "centinferior", 657 | 344 => "dollarinferior", 658 | 345 => "periodinferior", 659 | 346 => "commainferior", 660 | _ => return None, 661 | }) 662 | } 663 | -------------------------------------------------------------------------------- /src/compact1/encoding.rs: -------------------------------------------------------------------------------- 1 | //! The glyph encodings. 2 | 3 | use crate::compact1::{GlyphID, StringID}; 4 | use crate::Result; 5 | 6 | /// A glyph encoding. 7 | #[derive(Clone, Debug)] 8 | pub enum Encoding { 9 | Standard, 10 | Expert, 11 | Format0(Encoding0), 12 | Format1(Encoding1), 13 | FormatSupplemental(EncodingSupplemental), 14 | } 15 | 16 | table! { 17 | /// An encoding in format 0. 18 | pub Encoding0 { // Format 0 19 | format (u8) = { 0 }, // format 20 | code_count (u8), // nCodes 21 | 22 | codes (Vec) |this, tape| { // code 23 | tape.take_given(this.code_count as usize) 24 | }, 25 | } 26 | } 27 | 28 | table! { 29 | /// An encoding in format 1. 30 | pub Encoding1 { // Format 1 31 | format (u8) = { 1 }, // format 32 | range_count (u8), // nRanges 33 | 34 | ranges (Vec) |this, tape| { // Range1 35 | tape.take_given(this.range_count as usize) 36 | }, 37 | } 38 | } 39 | 40 | table! { 41 | /// An encoding in the supplemental format. 42 | pub EncodingSupplemental { // Supplemental Encoding Data 43 | format (u8), 44 | supplement_count (u8), // nSups 45 | 46 | supplements (Vec) |this, tape| { // Supplement 47 | tape.take_given(this.supplement_count as usize) 48 | }, 49 | } 50 | } 51 | 52 | table! { 53 | /// A range of an encoding in format 1. 54 | #[derive(Copy)] 55 | pub Range1 { 56 | first_code (u8), // first 57 | left_count (u8), // nLeft 58 | } 59 | } 60 | 61 | table! { 62 | /// A supplement of an encoding in the supplemental format. 63 | #[derive(Copy)] 64 | pub Supplement { 65 | code (u8 ), // code 66 | glyph (StringID), // glyph 67 | } 68 | } 69 | 70 | impl Encoding { 71 | /// Return the string identifier of a glyph. 72 | pub fn get(&self, glyph_id: GlyphID) -> Option { 73 | match self { 74 | Encoding::Standard => get_standard(glyph_id), 75 | Encoding::Expert => get_expert(glyph_id), 76 | Encoding::Format0(ref encoding) => encoding.get(glyph_id), 77 | Encoding::Format1(ref encoding) => encoding.get(glyph_id), 78 | Encoding::FormatSupplemental(ref encoding) => encoding.get(glyph_id), 79 | } 80 | } 81 | } 82 | 83 | impl crate::value::Read for Encoding { 84 | fn read(tape: &mut T) -> Result { 85 | Ok(match tape.peek::()? { 86 | 0 => Encoding::Format0(tape.take()?), 87 | 1 => Encoding::Format1(tape.take()?), 88 | format if format & 0x80 > 0 => Encoding::FormatSupplemental(tape.take()?), 89 | format => raise!("found an unknown format of encodings ({format})"), 90 | }) 91 | } 92 | } 93 | 94 | impl Encoding0 { 95 | #[inline] 96 | fn get(&self, _: GlyphID) -> Option { 97 | None 98 | } 99 | } 100 | 101 | impl Encoding1 { 102 | #[inline] 103 | fn get(&self, _: GlyphID) -> Option { 104 | None 105 | } 106 | } 107 | 108 | impl EncodingSupplemental { 109 | #[inline] 110 | fn get(&self, _: GlyphID) -> Option { 111 | None 112 | } 113 | } 114 | 115 | macro_rules! get( 116 | ($one:ident { $($glyph_id:pat => $string_id:expr => $name:expr,)+ }) => ( 117 | Some(match $one { 118 | $($glyph_id => $string_id,)+ 119 | _ => return None, 120 | }) 121 | ); 122 | ); 123 | 124 | fn get_standard(glyph_id: GlyphID) -> Option { 125 | get!(glyph_id { 126 | 0 => 0 => ".notdef", 127 | 1 => 0 => ".notdef", 128 | 2 => 0 => ".notdef", 129 | 3 => 0 => ".notdef", 130 | 4 => 0 => ".notdef", 131 | 5 => 0 => ".notdef", 132 | 6 => 0 => ".notdef", 133 | 7 => 0 => ".notdef", 134 | 8 => 0 => ".notdef", 135 | 9 => 0 => ".notdef", 136 | 10 => 0 => ".notdef", 137 | 11 => 0 => ".notdef", 138 | 12 => 0 => ".notdef", 139 | 13 => 0 => ".notdef", 140 | 14 => 0 => ".notdef", 141 | 15 => 0 => ".notdef", 142 | 16 => 0 => ".notdef", 143 | 17 => 0 => ".notdef", 144 | 18 => 0 => ".notdef", 145 | 19 => 0 => ".notdef", 146 | 20 => 0 => ".notdef", 147 | 21 => 0 => ".notdef", 148 | 22 => 0 => ".notdef", 149 | 23 => 0 => ".notdef", 150 | 24 => 0 => ".notdef", 151 | 25 => 0 => ".notdef", 152 | 26 => 0 => ".notdef", 153 | 27 => 0 => ".notdef", 154 | 28 => 0 => ".notdef", 155 | 29 => 0 => ".notdef", 156 | 30 => 0 => ".notdef", 157 | 31 => 0 => ".notdef", 158 | 32 => 1 => "space", 159 | 33 => 2 => "exclam", 160 | 34 => 3 => "quotedbl", 161 | 35 => 4 => "numbersign", 162 | 36 => 5 => "dollar", 163 | 37 => 6 => "percent", 164 | 38 => 7 => "ampersand", 165 | 39 => 8 => "quoteright", 166 | 40 => 9 => "parenleft", 167 | 41 => 10 => "parenright", 168 | 42 => 11 => "asterisk", 169 | 43 => 12 => "plus", 170 | 44 => 13 => "comma", 171 | 45 => 14 => "hyphen", 172 | 46 => 15 => "period", 173 | 47 => 16 => "slash", 174 | 48 => 17 => "zero", 175 | 49 => 18 => "one", 176 | 50 => 19 => "two", 177 | 51 => 20 => "three", 178 | 52 => 21 => "four", 179 | 53 => 22 => "five", 180 | 54 => 23 => "six", 181 | 55 => 24 => "seven", 182 | 56 => 25 => "eight", 183 | 57 => 26 => "nine", 184 | 58 => 27 => "colon", 185 | 59 => 28 => "semicolon", 186 | 60 => 29 => "less", 187 | 61 => 30 => "equal", 188 | 62 => 31 => "greater", 189 | 63 => 32 => "question", 190 | 64 => 33 => "at", 191 | 65 => 34 => "A", 192 | 66 => 35 => "B", 193 | 67 => 36 => "C", 194 | 68 => 37 => "D", 195 | 69 => 38 => "E", 196 | 70 => 39 => "F", 197 | 71 => 40 => "G", 198 | 72 => 41 => "H", 199 | 73 => 42 => "I", 200 | 74 => 43 => "J", 201 | 75 => 44 => "K", 202 | 76 => 45 => "L", 203 | 77 => 46 => "M", 204 | 78 => 47 => "N", 205 | 79 => 48 => "O", 206 | 80 => 49 => "P", 207 | 81 => 50 => "Q", 208 | 82 => 51 => "R", 209 | 83 => 52 => "S", 210 | 84 => 53 => "T", 211 | 85 => 54 => "U", 212 | 86 => 55 => "V", 213 | 87 => 56 => "W", 214 | 88 => 57 => "X", 215 | 89 => 58 => "Y", 216 | 90 => 59 => "Z", 217 | 91 => 60 => "bracketleft", 218 | 92 => 61 => "backslash", 219 | 93 => 62 => "bracketright", 220 | 94 => 63 => "asciicircum", 221 | 95 => 64 => "underscore", 222 | 96 => 65 => "quoteleft", 223 | 97 => 66 => "a", 224 | 98 => 67 => "b", 225 | 99 => 68 => "c", 226 | 100 => 69 => "d", 227 | 101 => 70 => "e", 228 | 102 => 71 => "f", 229 | 103 => 72 => "g", 230 | 104 => 73 => "h", 231 | 105 => 74 => "i", 232 | 106 => 75 => "j", 233 | 107 => 76 => "k", 234 | 108 => 77 => "l", 235 | 109 => 78 => "m", 236 | 110 => 79 => "n", 237 | 111 => 80 => "o", 238 | 112 => 81 => "p", 239 | 113 => 82 => "q", 240 | 114 => 83 => "r", 241 | 115 => 84 => "s", 242 | 116 => 85 => "t", 243 | 117 => 86 => "u", 244 | 118 => 87 => "v", 245 | 119 => 88 => "w", 246 | 120 => 89 => "x", 247 | 121 => 90 => "y", 248 | 122 => 91 => "z", 249 | 123 => 92 => "braceleft", 250 | 124 => 93 => "bar", 251 | 125 => 94 => "braceright", 252 | 126 => 95 => "asciitilde", 253 | 127 => 0 => ".notdef", 254 | 128 => 0 => ".notdef", 255 | 129 => 0 => ".notdef", 256 | 130 => 0 => ".notdef", 257 | 131 => 0 => ".notdef", 258 | 132 => 0 => ".notdef", 259 | 133 => 0 => ".notdef", 260 | 134 => 0 => ".notdef", 261 | 135 => 0 => ".notdef", 262 | 136 => 0 => ".notdef", 263 | 137 => 0 => ".notdef", 264 | 138 => 0 => ".notdef", 265 | 139 => 0 => ".notdef", 266 | 140 => 0 => ".notdef", 267 | 141 => 0 => ".notdef", 268 | 142 => 0 => ".notdef", 269 | 143 => 0 => ".notdef", 270 | 144 => 0 => ".notdef", 271 | 145 => 0 => ".notdef", 272 | 146 => 0 => ".notdef", 273 | 147 => 0 => ".notdef", 274 | 148 => 0 => ".notdef", 275 | 149 => 0 => ".notdef", 276 | 150 => 0 => ".notdef", 277 | 151 => 0 => ".notdef", 278 | 152 => 0 => ".notdef", 279 | 153 => 0 => ".notdef", 280 | 154 => 0 => ".notdef", 281 | 155 => 0 => ".notdef", 282 | 156 => 0 => ".notdef", 283 | 157 => 0 => ".notdef", 284 | 158 => 0 => ".notdef", 285 | 159 => 0 => ".notdef", 286 | 160 => 0 => ".notdef", 287 | 161 => 96 => "exclamdown", 288 | 162 => 97 => "cent", 289 | 163 => 98 => "sterling", 290 | 164 => 99 => "fraction", 291 | 165 => 100 => "yen", 292 | 166 => 101 => "florin", 293 | 167 => 102 => "section", 294 | 168 => 103 => "currency", 295 | 169 => 104 => "quotesingle", 296 | 170 => 105 => "quotedblleft", 297 | 171 => 106 => "guillemotleft", 298 | 172 => 107 => "guilsinglleft", 299 | 173 => 108 => "guilsinglright", 300 | 174 => 109 => "fi", 301 | 175 => 110 => "fl", 302 | 176 => 0 => ".notdef", 303 | 177 => 111 => "endash", 304 | 178 => 112 => "dagger", 305 | 179 => 113 => "daggerdbl", 306 | 180 => 114 => "periodcentered", 307 | 181 => 0 => ".notdef", 308 | 182 => 115 => "paragraph", 309 | 183 => 116 => "bullet", 310 | 184 => 117 => "quotesinglbase", 311 | 185 => 118 => "quotedblbase", 312 | 186 => 119 => "quotedblright", 313 | 187 => 120 => "guillemotright", 314 | 188 => 121 => "ellipsis", 315 | 189 => 122 => "perthousand", 316 | 190 => 0 => ".notdef", 317 | 191 => 123 => "questiondown", 318 | 192 => 0 => ".notdef", 319 | 193 => 124 => "grave", 320 | 194 => 125 => "acute", 321 | 195 => 126 => "circumflex", 322 | 196 => 127 => "tilde", 323 | 197 => 128 => "macron", 324 | 198 => 129 => "breve", 325 | 199 => 130 => "dotaccent", 326 | 200 => 131 => "dieresis", 327 | 201 => 0 => ".notdef", 328 | 202 => 132 => "ring", 329 | 203 => 133 => "cedilla", 330 | 204 => 0 => ".notdef", 331 | 205 => 134 => "hungarumlaut", 332 | 206 => 135 => "ogonek", 333 | 207 => 136 => "caron", 334 | 208 => 137 => "emdash", 335 | 209 => 0 => ".notdef", 336 | 210 => 0 => ".notdef", 337 | 211 => 0 => ".notdef", 338 | 212 => 0 => ".notdef", 339 | 213 => 0 => ".notdef", 340 | 214 => 0 => ".notdef", 341 | 215 => 0 => ".notdef", 342 | 216 => 0 => ".notdef", 343 | 217 => 0 => ".notdef", 344 | 218 => 0 => ".notdef", 345 | 219 => 0 => ".notdef", 346 | 220 => 0 => ".notdef", 347 | 221 => 0 => ".notdef", 348 | 222 => 0 => ".notdef", 349 | 223 => 0 => ".notdef", 350 | 224 => 0 => ".notdef", 351 | 225 => 138 => "AE", 352 | 226 => 0 => ".notdef", 353 | 227 => 139 => "ordfeminine", 354 | 228 => 0 => ".notdef", 355 | 229 => 0 => ".notdef", 356 | 230 => 0 => ".notdef", 357 | 231 => 0 => ".notdef", 358 | 232 => 140 => "Lslash", 359 | 233 => 141 => "Oslash", 360 | 234 => 142 => "OE", 361 | 235 => 143 => "ordmasculine", 362 | 236 => 0 => ".notdef", 363 | 237 => 0 => ".notdef", 364 | 238 => 0 => ".notdef", 365 | 239 => 0 => ".notdef", 366 | 240 => 0 => ".notdef", 367 | 241 => 144 => "ae", 368 | 242 => 0 => ".notdef", 369 | 243 => 0 => ".notdef", 370 | 244 => 0 => ".notdef", 371 | 245 => 145 => "dotlessi", 372 | 246 => 0 => ".notdef", 373 | 247 => 0 => ".notdef", 374 | 248 => 146 => "lslash", 375 | 249 => 147 => "oslash", 376 | 250 => 148 => "oe", 377 | 251 => 149 => "germandbls", 378 | 252 => 0 => ".notdef", 379 | 253 => 0 => ".notdef", 380 | 254 => 0 => ".notdef", 381 | 255 => 0 => ".notdef", 382 | }) 383 | } 384 | 385 | fn get_expert(glyph_id: GlyphID) -> Option { 386 | get!(glyph_id { 387 | 0 => 0 => ".notdef", 388 | 1 => 0 => ".notdef", 389 | 2 => 0 => ".notdef", 390 | 3 => 0 => ".notdef", 391 | 4 => 0 => ".notdef", 392 | 5 => 0 => ".notdef", 393 | 6 => 0 => ".notdef", 394 | 7 => 0 => ".notdef", 395 | 8 => 0 => ".notdef", 396 | 9 => 0 => ".notdef", 397 | 10 => 0 => ".notdef", 398 | 11 => 0 => ".notdef", 399 | 12 => 0 => ".notdef", 400 | 13 => 0 => ".notdef", 401 | 14 => 0 => ".notdef", 402 | 15 => 0 => ".notdef", 403 | 16 => 0 => ".notdef", 404 | 17 => 0 => ".notdef", 405 | 18 => 0 => ".notdef", 406 | 19 => 0 => ".notdef", 407 | 20 => 0 => ".notdef", 408 | 21 => 0 => ".notdef", 409 | 22 => 0 => ".notdef", 410 | 23 => 0 => ".notdef", 411 | 24 => 0 => ".notdef", 412 | 25 => 0 => ".notdef", 413 | 26 => 0 => ".notdef", 414 | 27 => 0 => ".notdef", 415 | 28 => 0 => ".notdef", 416 | 29 => 0 => ".notdef", 417 | 30 => 0 => ".notdef", 418 | 31 => 0 => ".notdef", 419 | 32 => 1 => "space", 420 | 33 => 229 => "exclamsmall", 421 | 34 => 230 => "Hungarumlautsmall", 422 | 35 => 0 => ".notdef", 423 | 36 => 231 => "dollaroldstyle", 424 | 37 => 232 => "dollarsuperior", 425 | 38 => 233 => "ampersandsmall", 426 | 39 => 234 => "Acutesmall", 427 | 40 => 235 => "parenleftsuperior", 428 | 41 => 236 => "parenrightsuperior", 429 | 42 => 237 => "twodotenleader", 430 | 43 => 238 => "onedotenleader", 431 | 44 => 13 => "comma", 432 | 45 => 14 => "hyphen", 433 | 46 => 15 => "period", 434 | 47 => 99 => "fraction", 435 | 48 => 239 => "zerooldstyle", 436 | 49 => 240 => "oneoldstyle", 437 | 50 => 241 => "twooldstyle", 438 | 51 => 242 => "threeoldstyle", 439 | 52 => 243 => "fouroldstyle", 440 | 53 => 244 => "fiveoldstyle", 441 | 54 => 245 => "sixoldstyle", 442 | 55 => 246 => "sevenoldstyle", 443 | 56 => 247 => "eightoldstyle", 444 | 57 => 248 => "nineoldstyle", 445 | 58 => 27 => "colon", 446 | 59 => 28 => "semicolon", 447 | 60 => 249 => "commasuperior", 448 | 61 => 250 => "threequartersemdash", 449 | 62 => 251 => "periodsuperior", 450 | 63 => 252 => "questionsmall", 451 | 64 => 0 => ".notdef", 452 | 65 => 253 => "asuperior", 453 | 66 => 254 => "bsuperior", 454 | 67 => 255 => "centsuperior", 455 | 68 => 256 => "dsuperior", 456 | 69 => 257 => "esuperior", 457 | 70 => 0 => ".notdef", 458 | 71 => 0 => ".notdef", 459 | 72 => 0 => ".notdef", 460 | 73 => 258 => "isuperior", 461 | 74 => 0 => ".notdef", 462 | 75 => 0 => ".notdef", 463 | 76 => 259 => "lsuperior", 464 | 77 => 260 => "msuperior", 465 | 78 => 261 => "nsuperior", 466 | 79 => 262 => "osuperior", 467 | 80 => 0 => ".notdef", 468 | 81 => 0 => ".notdef", 469 | 82 => 263 => "rsuperior", 470 | 83 => 264 => "ssuperior", 471 | 84 => 265 => "tsuperior", 472 | 85 => 0 => ".notdef", 473 | 86 => 266 => "ff", 474 | 87 => 109 => "fi", 475 | 88 => 110 => "fl", 476 | 89 => 267 => "ffi", 477 | 90 => 268 => "ffl", 478 | 91 => 269 => "parenleftinferior", 479 | 92 => 0 => ".notdef", 480 | 93 => 270 => "parenrightinferior", 481 | 94 => 271 => "Circumflexsmall", 482 | 95 => 272 => "hyphensuperior", 483 | 96 => 273 => "Gravesmall", 484 | 97 => 274 => "Asmall", 485 | 98 => 275 => "Bsmall", 486 | 99 => 276 => "Csmall", 487 | 100 => 277 => "Dsmall", 488 | 101 => 278 => "Esmall", 489 | 102 => 279 => "Fsmall", 490 | 103 => 280 => "Gsmall", 491 | 104 => 281 => "Hsmall", 492 | 105 => 282 => "Ismall", 493 | 106 => 283 => "Jsmall", 494 | 107 => 284 => "Ksmall", 495 | 108 => 285 => "Lsmall", 496 | 109 => 286 => "Msmall", 497 | 110 => 287 => "Nsmall", 498 | 111 => 288 => "Osmall", 499 | 112 => 289 => "Psmall", 500 | 113 => 290 => "Qsmall", 501 | 114 => 291 => "Rsmall", 502 | 115 => 292 => "Ssmall", 503 | 116 => 293 => "Tsmall", 504 | 117 => 294 => "Usmall", 505 | 118 => 295 => "Vsmall", 506 | 119 => 296 => "Wsmall", 507 | 120 => 297 => "Xsmall", 508 | 121 => 298 => "Ysmall", 509 | 122 => 299 => "Zsmall", 510 | 123 => 300 => "colonmonetary", 511 | 124 => 301 => "onefitted", 512 | 125 => 302 => "rupiah", 513 | 126 => 303 => "Tildesmall", 514 | 127 => 0 => ".notdef", 515 | 128 => 0 => ".notdef", 516 | 129 => 0 => ".notdef", 517 | 130 => 0 => ".notdef", 518 | 131 => 0 => ".notdef", 519 | 132 => 0 => ".notdef", 520 | 133 => 0 => ".notdef", 521 | 134 => 0 => ".notdef", 522 | 135 => 0 => ".notdef", 523 | 136 => 0 => ".notdef", 524 | 137 => 0 => ".notdef", 525 | 138 => 0 => ".notdef", 526 | 139 => 0 => ".notdef", 527 | 140 => 0 => ".notdef", 528 | 141 => 0 => ".notdef", 529 | 142 => 0 => ".notdef", 530 | 143 => 0 => ".notdef", 531 | 144 => 0 => ".notdef", 532 | 145 => 0 => ".notdef", 533 | 146 => 0 => ".notdef", 534 | 147 => 0 => ".notdef", 535 | 148 => 0 => ".notdef", 536 | 149 => 0 => ".notdef", 537 | 150 => 0 => ".notdef", 538 | 151 => 0 => ".notdef", 539 | 152 => 0 => ".notdef", 540 | 153 => 0 => ".notdef", 541 | 154 => 0 => ".notdef", 542 | 155 => 0 => ".notdef", 543 | 156 => 0 => ".notdef", 544 | 157 => 0 => ".notdef", 545 | 158 => 0 => ".notdef", 546 | 159 => 0 => ".notdef", 547 | 160 => 0 => ".notdef", 548 | 161 => 304 => "exclamdownsmall", 549 | 162 => 305 => "centoldstyle", 550 | 163 => 306 => "Lslashsmall", 551 | 164 => 0 => ".notdef", 552 | 165 => 0 => ".notdef", 553 | 166 => 307 => "Scaronsmall", 554 | 167 => 308 => "Zcaronsmall", 555 | 168 => 309 => "Dieresissmall", 556 | 169 => 310 => "Brevesmall", 557 | 170 => 311 => "Caronsmall", 558 | 171 => 0 => ".notdef", 559 | 172 => 312 => "Dotaccentsmall", 560 | 173 => 0 => ".notdef", 561 | 174 => 0 => ".notdef", 562 | 175 => 313 => "Macronsmall", 563 | 176 => 0 => ".notdef", 564 | 177 => 0 => ".notdef", 565 | 178 => 314 => "figuredash", 566 | 179 => 315 => "hypheninferior", 567 | 180 => 0 => ".notdef", 568 | 181 => 0 => ".notdef", 569 | 182 => 316 => "Ogoneksmall", 570 | 183 => 317 => "Ringsmall", 571 | 184 => 318 => "Cedillasmall", 572 | 185 => 0 => ".notdef", 573 | 186 => 0 => ".notdef", 574 | 187 => 0 => ".notdef", 575 | 188 => 158 => "onequarter", 576 | 189 => 155 => "onehalf", 577 | 190 => 163 => "threequarters", 578 | 191 => 319 => "questiondownsmall", 579 | 192 => 320 => "oneeighth", 580 | 193 => 321 => "threeeighths", 581 | 194 => 322 => "fiveeighths", 582 | 195 => 323 => "seveneighths", 583 | 196 => 324 => "onethird", 584 | 197 => 325 => "twothirds", 585 | 198 => 0 => ".notdef", 586 | 199 => 0 => ".notdef", 587 | 200 => 326 => "zerosuperior", 588 | 201 => 150 => "onesuperior", 589 | 202 => 164 => "twosuperior", 590 | 203 => 169 => "threesuperior", 591 | 204 => 327 => "foursuperior", 592 | 205 => 328 => "fivesuperior", 593 | 206 => 329 => "sixsuperior", 594 | 207 => 330 => "sevensuperior", 595 | 208 => 331 => "eightsuperior", 596 | 209 => 332 => "ninesuperior", 597 | 210 => 333 => "zeroinferior", 598 | 211 => 334 => "oneinferior", 599 | 212 => 335 => "twoinferior", 600 | 213 => 336 => "threeinferior", 601 | 214 => 337 => "fourinferior", 602 | 215 => 338 => "fiveinferior", 603 | 216 => 339 => "sixinferior", 604 | 217 => 340 => "seveninferior", 605 | 218 => 341 => "eightinferior", 606 | 219 => 342 => "nineinferior", 607 | 220 => 343 => "centinferior", 608 | 221 => 344 => "dollarinferior", 609 | 222 => 345 => "periodinferior", 610 | 223 => 346 => "commainferior", 611 | 224 => 347 => "Agravesmall", 612 | 225 => 348 => "Aacutesmall", 613 | 226 => 349 => "Acircumflexsmall", 614 | 227 => 350 => "Atildesmall", 615 | 228 => 351 => "Adieresissmall", 616 | 229 => 352 => "Aringsmall", 617 | 230 => 353 => "AEsmall", 618 | 231 => 354 => "Ccedillasmall", 619 | 232 => 355 => "Egravesmall", 620 | 233 => 356 => "Eacutesmall", 621 | 234 => 357 => "Ecircumflexsmall", 622 | 235 => 358 => "Edieresissmall", 623 | 236 => 359 => "Igravesmall", 624 | 237 => 360 => "Iacutesmall", 625 | 238 => 361 => "Icircumflexsmall", 626 | 239 => 362 => "Idieresissmall", 627 | 240 => 363 => "Ethsmall", 628 | 241 => 364 => "Ntildesmall", 629 | 242 => 365 => "Ogravesmall", 630 | 243 => 366 => "Oacutesmall", 631 | 244 => 367 => "Ocircumflexsmall", 632 | 245 => 368 => "Otildesmall", 633 | 246 => 369 => "Odieresissmall", 634 | 247 => 370 => "OEsmall", 635 | 248 => 371 => "Oslashsmall", 636 | 249 => 372 => "Ugravesmall", 637 | 250 => 373 => "Uacutesmall", 638 | 251 => 374 => "Ucircumflexsmall", 639 | 252 => 375 => "Udieresissmall", 640 | 253 => 376 => "Yacutesmall", 641 | 254 => 377 => "Thornsmall", 642 | 255 => 378 => "Ydieresissmall", 643 | }) 644 | } 645 | -------------------------------------------------------------------------------- /src/compact1/font_set/character_id_keyed.rs: -------------------------------------------------------------------------------- 1 | //! The character-ID-keyed fonts. 2 | 3 | use std::io::Cursor; 4 | 5 | use crate::compact1::index::{CharacterStrings, Dictionaries, Subroutines}; 6 | use crate::compact1::{GlyphID, Number, Operations, Operator, StringID}; 7 | use crate::Result; 8 | 9 | /// A character-ID-keyed record in a font set. 10 | #[derive(Clone, Debug)] 11 | pub struct Record { 12 | pub registry: StringID, 13 | pub ordering: StringID, 14 | pub supplement: Number, 15 | pub encoding: Encoding, 16 | pub operations: Vec, 17 | pub records: Vec, 18 | } 19 | 20 | /// A record in a character-ID-keyed record in a font set. 21 | #[derive(Clone, Debug)] 22 | pub struct RecordInner { 23 | pub operations: Operations, 24 | pub subroutines: Subroutines, 25 | } 26 | 27 | /// An encoding of a glyph-to-dictionary mapping. 28 | #[derive(Clone, Debug)] 29 | pub enum Encoding { 30 | /// Format 0. 31 | Format0(Encoding0), 32 | /// Format 3. 33 | Format3(Encoding3), 34 | } 35 | 36 | /// A glyph-to-dictionary encoding in format 0. 37 | #[derive(Clone, Debug)] 38 | pub struct Encoding0 { 39 | pub format: u8, // format 40 | pub dictionary_ids: Vec, // fds 41 | } 42 | 43 | table! { 44 | /// A glyph-to-dictionary encoding in format 3. 45 | pub Encoding3 { 46 | format (u8 ) = { 3 }, // format 47 | range_count (u16), // nRanges 48 | 49 | ranges (Vec) |this, tape| { // Range3 50 | tape.take_given(this.range_count as usize) 51 | }, 52 | 53 | glyph_count (u16), // sentinel 54 | } 55 | } 56 | 57 | table! { 58 | /// A range of a glyph-to-dictionary encoding in format 3. 59 | #[derive(Copy)] 60 | pub Range3 { 61 | first_glyph_id (GlyphID), // first 62 | dictionary_id (u8 ), // fd 63 | } 64 | } 65 | 66 | impl<'l> crate::walue::Read<'l> for Record { 67 | type Parameter = (u64, &'l Operations, &'l CharacterStrings); 68 | 69 | fn read( 70 | tape: &mut T, 71 | (position, top_operations, character_strings): Self::Parameter, 72 | ) -> Result { 73 | let operands = match top_operations.get(Operator::ROS) { 74 | Some(operands) if operands.len() == 3 => operands, 75 | _ => raise!("found a malformed character-ID-keyed record"), 76 | }; 77 | let offset = get!(@single top_operations, FDSelect); 78 | let encoding = jump_take_given!(@unwrap tape, position, offset, character_strings); 79 | let offset = get!(@single top_operations, FDArray); 80 | let operations: Dictionaries = jump_take!(@unwrap tape, position, offset); 81 | let operations: Vec<_> = (&operations).try_into()?; 82 | let mut records = vec![]; 83 | for top_operations in operations.iter() { 84 | records.push(tape.take_given((position, top_operations))?); 85 | } 86 | Ok(Self { 87 | registry: operands[0].try_into()?, 88 | ordering: operands[1].try_into()?, 89 | supplement: operands[2], 90 | encoding, 91 | operations, 92 | records, 93 | }) 94 | } 95 | } 96 | 97 | impl<'l> crate::walue::Read<'l> for RecordInner { 98 | type Parameter = (u64, &'l Operations); 99 | 100 | fn read( 101 | tape: &mut T, 102 | (position, top_operations): Self::Parameter, 103 | ) -> Result { 104 | use crate::tape::Read; 105 | 106 | let (size, offset) = get!(@double top_operations, Private); 107 | let chunk: Vec = jump_take_given!(@unwrap tape, position, offset, size as usize); 108 | let operations = Cursor::new(chunk).take::()?; 109 | let subroutines = match get!(@try @single operations, Subrs) { 110 | Some(another_offset) => jump_take!(@unwrap tape, position, offset + another_offset), 111 | _ => Default::default(), 112 | }; 113 | Ok(Self { 114 | operations, 115 | subroutines, 116 | }) 117 | } 118 | } 119 | 120 | impl<'l> crate::walue::Read<'l> for Encoding { 121 | type Parameter = &'l CharacterStrings; 122 | 123 | fn read( 124 | tape: &mut T, 125 | character_strings: Self::Parameter, 126 | ) -> Result { 127 | Ok(match tape.peek::()? { 128 | 0 => Encoding::Format0(tape.take_given(character_strings)?), 129 | 3 => Encoding::Format3(tape.take()?), 130 | format => { 131 | raise!("found an unknown format of the glyph-to-dictionary encoding ({format})") 132 | } 133 | }) 134 | } 135 | } 136 | 137 | impl<'l> crate::walue::Read<'l> for Encoding0 { 138 | type Parameter = &'l CharacterStrings; 139 | 140 | fn read( 141 | tape: &mut T, 142 | character_strings: Self::Parameter, 143 | ) -> Result { 144 | let format = tape.take()?; 145 | debug_assert_eq!(format, 0); 146 | Ok(Self { 147 | format, 148 | dictionary_ids: tape.take_given(character_strings.count as usize)?, 149 | }) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/compact1/font_set/character_name_keyed.rs: -------------------------------------------------------------------------------- 1 | //! The character-name-keyed fonts. 2 | 3 | use std::io::Cursor; 4 | 5 | use crate::compact1::index::Subroutines; 6 | use crate::compact1::Operations; 7 | use crate::Result; 8 | 9 | /// A character-name-keyed record in a font set. 10 | #[derive(Clone, Debug)] 11 | pub struct Record { 12 | pub operations: Operations, 13 | pub subroutines: Subroutines, 14 | } 15 | 16 | impl<'l> crate::walue::Read<'l> for Record { 17 | type Parameter = (u64, &'l Operations); 18 | 19 | fn read( 20 | tape: &mut T, 21 | (position, top_operations): Self::Parameter, 22 | ) -> Result { 23 | use crate::tape::Read; 24 | 25 | let (size, offset) = get!(@double top_operations, Private); 26 | let chunk: Vec = jump_take_given!(@unwrap tape, position, offset, size as usize); 27 | let operations = Cursor::new(chunk).take::()?; 28 | let subroutines = match get!(@try @single operations, Subrs) { 29 | Some(another_offset) => jump_take!(@unwrap tape, position, offset + another_offset), 30 | _ => Default::default(), 31 | }; 32 | Ok(Self { 33 | operations, 34 | subroutines, 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/compact1/font_set/mod.rs: -------------------------------------------------------------------------------- 1 | //! The font sets. 2 | 3 | macro_rules! get( 4 | (@single $operations:expr, $operator:ident) => ( 5 | match $operations.get_single(crate::compact1::Operator::$operator) { 6 | Some(crate::compact1::Number::Integer(value)) => value, 7 | Some(_) => raise!(concat!("found a malformed operation with operator ", stringify!($operator))), 8 | _ => raise!(concat!("found no operation with operator ", stringify!($operator))), 9 | } 10 | ); 11 | (@try @single $operations:expr, $operator:ident) => ( 12 | match $operations.get_single(crate::compact1::Operator::$operator) { 13 | Some(crate::compact1::Number::Integer(value)) => Some(value), 14 | Some(_) => raise!(concat!("found a malformed operation with operator ", stringify!($operator))), 15 | _ => None, 16 | } 17 | ); 18 | (@double $operations:expr, $operator:ident) => ( 19 | match $operations.get_double(crate::compact1::Operator::$operator) { 20 | Some((crate::compact1::Number::Integer(value0), crate::compact1::Number::Integer(value1))) => (value0, value1), 21 | Some(_) => raise!(concat!("found a malformed operation with operator ", stringify!($operator))), 22 | _ => raise!(concat!("found no operation with operator ", stringify!($operator))), 23 | } 24 | ); 25 | ); 26 | 27 | pub mod character_id_keyed; 28 | pub mod character_name_keyed; 29 | 30 | use crate::compact1::index::{CharacterStrings, Dictionaries, Names, Strings, Subroutines}; 31 | use crate::compact1::{CharacterSet, Encoding, Header, Operations, Operator}; 32 | use crate::Result; 33 | 34 | /// A font set. 35 | #[derive(Clone, Debug)] 36 | pub struct FontSet { 37 | pub header: Header, 38 | pub names: Names, 39 | pub operations: Vec, 40 | pub strings: Strings, 41 | pub subroutines: Subroutines, 42 | pub encodings: Vec, 43 | pub character_strings: Vec, 44 | pub character_sets: Vec, 45 | pub records: Vec, 46 | } 47 | 48 | /// A record in a font set. 49 | #[derive(Clone, Debug)] 50 | pub enum Record { 51 | CharacterIDKeyed(character_id_keyed::Record), 52 | CharacterNameKeyed(character_name_keyed::Record), 53 | } 54 | 55 | impl FontSet { 56 | /// Count the number of records. 57 | pub fn count(tape: &mut T) -> Result { 58 | let position = tape.position()?; 59 | let header = tape.take::
()?; 60 | let count: u16 = jump_take!(@unwrap tape, position, header.header_size); 61 | Ok(count as usize) 62 | } 63 | } 64 | 65 | impl crate::value::Read for FontSet { 66 | fn read(tape: &mut T) -> Result { 67 | let position = tape.position()?; 68 | let header = tape.take::
()?; 69 | let names: Names = jump_take!(@unwrap tape, position, header.header_size); 70 | let operations: Vec<_> = (&tape.take::()?).try_into()?; 71 | let strings = tape.take::()?; 72 | let subroutines = tape.take::()?; 73 | let mut encodings = vec![]; 74 | let mut character_sets = vec![]; 75 | let mut character_strings: Vec = vec![]; 76 | let mut records = vec![]; 77 | for (i, operations) in operations.iter().enumerate() { 78 | character_strings.push(jump_take_given!( 79 | @unwrap 80 | tape, 81 | position, 82 | get!(@single operations, CharStrings), 83 | get!(@single operations, CharStringType) 84 | )); 85 | character_sets.push(match get!(@single operations, CharSet) { 86 | 0 => CharacterSet::ISOAdobe, 87 | 1 => CharacterSet::Expert, 88 | 2 => CharacterSet::ExpertSubset, 89 | offset => jump_take_given!( 90 | @unwrap 91 | tape, 92 | position, 93 | offset, 94 | character_strings[i].count as usize 95 | ), 96 | }); 97 | encodings.push(match get!(@single operations, Encoding) { 98 | 0 => Encoding::Standard, 99 | 1 => Encoding::Expert, 100 | offset => jump_take!(@unwrap tape, position, offset), 101 | }); 102 | records.push(tape.take_given((position, operations, &character_strings[i]))?); 103 | } 104 | Ok(Self { 105 | header, 106 | names, 107 | operations, 108 | strings, 109 | subroutines, 110 | encodings, 111 | character_strings, 112 | character_sets, 113 | records, 114 | }) 115 | } 116 | } 117 | 118 | impl<'l> crate::walue::Read<'l> for Record { 119 | type Parameter = (u64, &'l Operations, &'l CharacterStrings); 120 | 121 | fn read( 122 | tape: &mut T, 123 | (position, operations, character_strings): Self::Parameter, 124 | ) -> Result { 125 | if operations.contains_key(&Operator::ROS) { 126 | Ok(Record::CharacterIDKeyed(tape.take_given(( 127 | position, 128 | operations, 129 | character_strings, 130 | ))?)) 131 | } else { 132 | Ok(Record::CharacterNameKeyed( 133 | tape.take_given((position, operations))?, 134 | )) 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/compact1/header.rs: -------------------------------------------------------------------------------- 1 | use crate::compact1::OffsetSize; 2 | 3 | table! { 4 | /// A header. 5 | #[derive(Copy)] 6 | pub Header { 7 | major (u8 ), // major 8 | minor (u8 ), // minor 9 | header_size (u8 ), // hdrSize 10 | offset_size (OffsetSize), // offSize 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/compact1/index/character_strings.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | 3 | index! { 4 | @define 5 | /// A character-string index. 6 | pub CharacterStrings 7 | } 8 | 9 | impl crate::walue::Read<'static> for CharacterStrings { 10 | type Parameter = i32; 11 | 12 | fn read(tape: &mut T, format: i32) -> Result { 13 | Ok(match format { 14 | 2 => CharacterStrings(tape.take()?), 15 | format => raise!("found an unknown format of character strings ({format})"), 16 | }) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/compact1/index/dictionaries.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use crate::compact1::Operations; 4 | use crate::tape::Read; 5 | use crate::{Error, Result}; 6 | 7 | index! { 8 | /// A dictionary index. 9 | pub Dictionaries 10 | } 11 | 12 | impl Dictionaries { 13 | /// Return the operations at a specific position. 14 | #[inline] 15 | pub fn get(&self, index: usize) -> Result { 16 | debug_assert!(index < self.len()); 17 | Cursor::new(&self[index]).take() 18 | } 19 | } 20 | 21 | impl TryFrom<&Dictionaries> for Vec { 22 | type Error = Error; 23 | 24 | fn try_from(dictionatires: &Dictionaries) -> Result { 25 | let mut values = Vec::with_capacity(dictionatires.len()); 26 | for index in 0..dictionatires.len() { 27 | values.push(Dictionaries::get(dictionatires, index)?); 28 | } 29 | Ok(values) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/compact1/index/mod.rs: -------------------------------------------------------------------------------- 1 | //! The indices. 2 | 3 | use crate::compact1::{Offset, OffsetSize}; 4 | use crate::Result; 5 | 6 | table! { 7 | @define 8 | /// An index. 9 | pub Index { 10 | count (u16 ), // count 11 | offset_size (OffsetSize ), // offSize 12 | offsets (Vec ), // offset 13 | data (Vec>), // data 14 | } 15 | } 16 | 17 | dereference! { Index::data => [Vec] } 18 | 19 | impl crate::value::Read for Index { 20 | fn read(tape: &mut T) -> Result { 21 | let count = tape.take::()?; 22 | if count == 0 { 23 | return Ok(Index { 24 | count: 0, 25 | offset_size: 0, 26 | offsets: vec![], 27 | data: vec![], 28 | }); 29 | } 30 | let offset_size = tape.take::()?; 31 | let mut offsets = Vec::with_capacity(count as usize + 1); 32 | for _ in 0..(count as usize + 1) { 33 | let offset = tape.take_given::(offset_size)?; 34 | offsets.push(offset); 35 | } 36 | if offsets[0] != Offset(1) { 37 | raise!("found a malformed index"); 38 | } 39 | let mut data = Vec::with_capacity(count as usize); 40 | for i in 0..(count as usize) { 41 | if offsets[i] > offsets[i + 1] { 42 | raise!("found a malformed index"); 43 | } 44 | let size = (offsets[i + 1].0 - offsets[i].0) as usize; 45 | data.push(tape.take_given(size)?); 46 | } 47 | Ok(Index { 48 | count, 49 | offset_size, 50 | offsets, 51 | data, 52 | }) 53 | } 54 | } 55 | 56 | macro_rules! index { 57 | ($(#[$attribute:meta])* pub $structure:ident) => ( 58 | index! { @define $(#[$attribute])* pub $structure } 59 | index! { @implement $structure } 60 | ); 61 | (@define $(#[$attribute:meta])* pub $structure:ident) => ( 62 | $(#[$attribute])* 63 | #[derive(Clone, Debug)] 64 | pub struct $structure(pub $crate::compact1::index::Index); 65 | dereference! { $structure::0 => $crate::compact1::index::Index } 66 | ); 67 | (@implement $structure:ident) => ( 68 | impl $crate::value::Read for $structure { 69 | #[inline] 70 | fn read(tape: &mut T) -> $crate::Result { 71 | Ok($structure(tape.take()?)) 72 | } 73 | } 74 | ); 75 | } 76 | 77 | mod character_strings; 78 | mod dictionaries; 79 | mod names; 80 | mod strings; 81 | mod subroutines; 82 | 83 | pub use character_strings::CharacterStrings; 84 | pub use dictionaries::Dictionaries; 85 | pub use names::Names; 86 | pub use strings::Strings; 87 | pub use subroutines::Subroutines; 88 | -------------------------------------------------------------------------------- /src/compact1/index/names.rs: -------------------------------------------------------------------------------- 1 | use crate::compact1::index::Index; 2 | use crate::{Error, Result}; 3 | 4 | index! { 5 | /// A name index. 6 | pub Names 7 | } 8 | 9 | impl TryFrom for Vec { 10 | type Error = Error; 11 | 12 | fn try_from(names: Names) -> Result { 13 | let Names(Index { data, .. }) = names; 14 | let mut vector = Vec::with_capacity(data.len()); 15 | for chunk in data { 16 | vector.push(match String::from_utf8(chunk) { 17 | Ok(string) => string, 18 | Err(chunk) => String::from_utf8_lossy(&chunk.into_bytes()).into_owned(), 19 | }); 20 | } 21 | Ok(vector) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/compact1/index/strings.rs: -------------------------------------------------------------------------------- 1 | use crate::compact1::StringID; 2 | 3 | const NUMBER_OF_STANDARD_STRINGS: usize = 391; 4 | 5 | index! { 6 | /// A string index. 7 | pub Strings 8 | } 9 | 10 | impl Strings { 11 | /// Return the string corresponding to a string identifier. 12 | pub fn get(&self, string_id: StringID) -> Option { 13 | match string_id as usize { 14 | i if i < NUMBER_OF_STANDARD_STRINGS => { 15 | get_standard_string(string_id).map(|string| string.to_string()) 16 | } 17 | i => self 18 | .0 19 | .get(i - NUMBER_OF_STANDARD_STRINGS) 20 | .map(|chunk| String::from_utf8_lossy(chunk).into_owned()), 21 | } 22 | } 23 | } 24 | 25 | fn get_standard_string(string_id: StringID) -> Option<&'static str> { 26 | Some(match string_id { 27 | 0 => ".notdef", 28 | 1 => "space", 29 | 2 => "exclam", 30 | 3 => "quotedbl", 31 | 4 => "numbersign", 32 | 5 => "dollar", 33 | 6 => "percent", 34 | 7 => "ampersand", 35 | 8 => "quoteright", 36 | 9 => "parenleft", 37 | 10 => "parenright", 38 | 11 => "asterisk", 39 | 12 => "plus", 40 | 13 => "comma", 41 | 14 => "hyphen", 42 | 15 => "period", 43 | 16 => "slash", 44 | 17 => "zero", 45 | 18 => "one", 46 | 19 => "two", 47 | 20 => "three", 48 | 21 => "four", 49 | 22 => "five", 50 | 23 => "six", 51 | 24 => "seven", 52 | 25 => "eight", 53 | 26 => "nine", 54 | 27 => "colon", 55 | 28 => "semicolon", 56 | 29 => "less", 57 | 30 => "equal", 58 | 31 => "greater", 59 | 32 => "question", 60 | 33 => "at", 61 | 34 => "A", 62 | 35 => "B", 63 | 36 => "C", 64 | 37 => "D", 65 | 38 => "E", 66 | 39 => "F", 67 | 40 => "G", 68 | 41 => "H", 69 | 42 => "I", 70 | 43 => "J", 71 | 44 => "K", 72 | 45 => "L", 73 | 46 => "M", 74 | 47 => "N", 75 | 48 => "O", 76 | 49 => "P", 77 | 50 => "Q", 78 | 51 => "R", 79 | 52 => "S", 80 | 53 => "T", 81 | 54 => "U", 82 | 55 => "V", 83 | 56 => "W", 84 | 57 => "X", 85 | 58 => "Y", 86 | 59 => "Z", 87 | 60 => "bracketleft", 88 | 61 => "backslash", 89 | 62 => "bracketright", 90 | 63 => "asciicircum", 91 | 64 => "underscore", 92 | 65 => "quoteleft", 93 | 66 => "a", 94 | 67 => "b", 95 | 68 => "c", 96 | 69 => "d", 97 | 70 => "e", 98 | 71 => "f", 99 | 72 => "g", 100 | 73 => "h", 101 | 74 => "i", 102 | 75 => "j", 103 | 76 => "k", 104 | 77 => "l", 105 | 78 => "m", 106 | 79 => "n", 107 | 80 => "o", 108 | 81 => "p", 109 | 82 => "q", 110 | 83 => "r", 111 | 84 => "s", 112 | 85 => "t", 113 | 86 => "u", 114 | 87 => "v", 115 | 88 => "w", 116 | 89 => "x", 117 | 90 => "y", 118 | 91 => "z", 119 | 92 => "braceleft", 120 | 93 => "bar", 121 | 94 => "braceright", 122 | 95 => "asciitilde", 123 | 96 => "exclamdown", 124 | 97 => "cent", 125 | 98 => "sterling", 126 | 99 => "fraction", 127 | 100 => "yen", 128 | 101 => "florin", 129 | 102 => "section", 130 | 103 => "currency", 131 | 104 => "quotesingle", 132 | 105 => "quotedblleft", 133 | 106 => "guillemotleft", 134 | 107 => "guilsinglleft", 135 | 108 => "guilsinglright", 136 | 109 => "fi", 137 | 110 => "fl", 138 | 111 => "endash", 139 | 112 => "dagger", 140 | 113 => "daggerdbl", 141 | 114 => "periodcentered", 142 | 115 => "paragraph", 143 | 116 => "bullet", 144 | 117 => "quotesinglbase", 145 | 118 => "quotedblbase", 146 | 119 => "quotedblright", 147 | 120 => "guillemotright", 148 | 121 => "ellipsis", 149 | 122 => "perthousand", 150 | 123 => "questiondown", 151 | 124 => "grave", 152 | 125 => "acute", 153 | 126 => "circumflex", 154 | 127 => "tilde", 155 | 128 => "macron", 156 | 129 => "breve", 157 | 130 => "dotaccent", 158 | 131 => "dieresis", 159 | 132 => "ring", 160 | 133 => "cedilla", 161 | 134 => "hungarumlaut", 162 | 135 => "ogonek", 163 | 136 => "caron", 164 | 137 => "emdash", 165 | 138 => "AE", 166 | 139 => "ordfeminine", 167 | 140 => "Lslash", 168 | 141 => "Oslash", 169 | 142 => "OE", 170 | 143 => "ordmasculine", 171 | 144 => "ae", 172 | 145 => "dotlessi", 173 | 146 => "lslash", 174 | 147 => "oslash", 175 | 148 => "oe", 176 | 149 => "germandbls", 177 | 150 => "onesuperior", 178 | 151 => "logicalnot", 179 | 152 => "mu", 180 | 153 => "trademark", 181 | 154 => "Eth", 182 | 155 => "onehalf", 183 | 156 => "plusminus", 184 | 157 => "Thorn", 185 | 158 => "onequarter", 186 | 159 => "divide", 187 | 160 => "brokenbar", 188 | 161 => "degree", 189 | 162 => "thorn", 190 | 163 => "threequarters", 191 | 164 => "twosuperior", 192 | 165 => "registered", 193 | 166 => "minus", 194 | 167 => "eth", 195 | 168 => "multiply", 196 | 169 => "threesuperior", 197 | 170 => "copyright", 198 | 171 => "Aacute", 199 | 172 => "Acircumflex", 200 | 173 => "Adieresis", 201 | 174 => "Agrave", 202 | 175 => "Aring", 203 | 176 => "Atilde", 204 | 177 => "Ccedilla", 205 | 178 => "Eacute", 206 | 179 => "Ecircumflex", 207 | 180 => "Edieresis", 208 | 181 => "Egrave", 209 | 182 => "Iacute", 210 | 183 => "Icircumflex", 211 | 184 => "Idieresis", 212 | 185 => "Igrave", 213 | 186 => "Ntilde", 214 | 187 => "Oacute", 215 | 188 => "Ocircumflex", 216 | 189 => "Odieresis", 217 | 190 => "Ograve", 218 | 191 => "Otilde", 219 | 192 => "Scaron", 220 | 193 => "Uacute", 221 | 194 => "Ucircumflex", 222 | 195 => "Udieresis", 223 | 196 => "Ugrave", 224 | 197 => "Yacute", 225 | 198 => "Ydieresis", 226 | 199 => "Zcaron", 227 | 200 => "aacute", 228 | 201 => "acircumflex", 229 | 202 => "adieresis", 230 | 203 => "agrave", 231 | 204 => "aring", 232 | 205 => "atilde", 233 | 206 => "ccedilla", 234 | 207 => "eacute", 235 | 208 => "ecircumflex", 236 | 209 => "edieresis", 237 | 210 => "egrave", 238 | 211 => "iacute", 239 | 212 => "icircumflex", 240 | 213 => "idieresis", 241 | 214 => "igrave", 242 | 215 => "ntilde", 243 | 216 => "oacute", 244 | 217 => "ocircumflex", 245 | 218 => "odieresis", 246 | 219 => "ograve", 247 | 220 => "otilde", 248 | 221 => "scaron", 249 | 222 => "uacute", 250 | 223 => "ucircumflex", 251 | 224 => "udieresis", 252 | 225 => "ugrave", 253 | 226 => "yacute", 254 | 227 => "ydieresis", 255 | 228 => "zcaron", 256 | 229 => "exclamsmall", 257 | 230 => "Hungarumlautsmall", 258 | 231 => "dollaroldstyle", 259 | 232 => "dollarsuperior", 260 | 233 => "ampersandsmall", 261 | 234 => "Acutesmall", 262 | 235 => "parenleftsuperior", 263 | 236 => "parenrightsuperior", 264 | 237 => "twodotenleader", 265 | 238 => "onedotenleader", 266 | 239 => "zerooldstyle", 267 | 240 => "oneoldstyle", 268 | 241 => "twooldstyle", 269 | 242 => "threeoldstyle", 270 | 243 => "fouroldstyle", 271 | 244 => "fiveoldstyle", 272 | 245 => "sixoldstyle", 273 | 246 => "sevenoldstyle", 274 | 247 => "eightoldstyle", 275 | 248 => "nineoldstyle", 276 | 249 => "commasuperior", 277 | 250 => "threequartersemdash", 278 | 251 => "periodsuperior", 279 | 252 => "questionsmall", 280 | 253 => "asuperior", 281 | 254 => "bsuperior", 282 | 255 => "centsuperior", 283 | 256 => "dsuperior", 284 | 257 => "esuperior", 285 | 258 => "isuperior", 286 | 259 => "lsuperior", 287 | 260 => "msuperior", 288 | 261 => "nsuperior", 289 | 262 => "osuperior", 290 | 263 => "rsuperior", 291 | 264 => "ssuperior", 292 | 265 => "tsuperior", 293 | 266 => "ff", 294 | 267 => "ffi", 295 | 268 => "ffl", 296 | 269 => "parenleftinferior", 297 | 270 => "parenrightinferior", 298 | 271 => "Circumflexsmall", 299 | 272 => "hyphensuperior", 300 | 273 => "Gravesmall", 301 | 274 => "Asmall", 302 | 275 => "Bsmall", 303 | 276 => "Csmall", 304 | 277 => "Dsmall", 305 | 278 => "Esmall", 306 | 279 => "Fsmall", 307 | 280 => "Gsmall", 308 | 281 => "Hsmall", 309 | 282 => "Ismall", 310 | 283 => "Jsmall", 311 | 284 => "Ksmall", 312 | 285 => "Lsmall", 313 | 286 => "Msmall", 314 | 287 => "Nsmall", 315 | 288 => "Osmall", 316 | 289 => "Psmall", 317 | 290 => "Qsmall", 318 | 291 => "Rsmall", 319 | 292 => "Ssmall", 320 | 293 => "Tsmall", 321 | 294 => "Usmall", 322 | 295 => "Vsmall", 323 | 296 => "Wsmall", 324 | 297 => "Xsmall", 325 | 298 => "Ysmall", 326 | 299 => "Zsmall", 327 | 300 => "colonmonetary", 328 | 301 => "onefitted", 329 | 302 => "rupiah", 330 | 303 => "Tildesmall", 331 | 304 => "exclamdownsmall", 332 | 305 => "centoldstyle", 333 | 306 => "Lslashsmall", 334 | 307 => "Scaronsmall", 335 | 308 => "Zcaronsmall", 336 | 309 => "Dieresissmall", 337 | 310 => "Brevesmall", 338 | 311 => "Caronsmall", 339 | 312 => "Dotaccentsmall", 340 | 313 => "Macronsmall", 341 | 314 => "figuredash", 342 | 315 => "hypheninferior", 343 | 316 => "Ogoneksmall", 344 | 317 => "Ringsmall", 345 | 318 => "Cedillasmall", 346 | 319 => "questiondownsmall", 347 | 320 => "oneeighth", 348 | 321 => "threeeighths", 349 | 322 => "fiveeighths", 350 | 323 => "seveneighths", 351 | 324 => "onethird", 352 | 325 => "twothirds", 353 | 326 => "zerosuperior", 354 | 327 => "foursuperior", 355 | 328 => "fivesuperior", 356 | 329 => "sixsuperior", 357 | 330 => "sevensuperior", 358 | 331 => "eightsuperior", 359 | 332 => "ninesuperior", 360 | 333 => "zeroinferior", 361 | 334 => "oneinferior", 362 | 335 => "twoinferior", 363 | 336 => "threeinferior", 364 | 337 => "fourinferior", 365 | 338 => "fiveinferior", 366 | 339 => "sixinferior", 367 | 340 => "seveninferior", 368 | 341 => "eightinferior", 369 | 342 => "nineinferior", 370 | 343 => "centinferior", 371 | 344 => "dollarinferior", 372 | 345 => "periodinferior", 373 | 346 => "commainferior", 374 | 347 => "Agravesmall", 375 | 348 => "Aacutesmall", 376 | 349 => "Acircumflexsmall", 377 | 350 => "Atildesmall", 378 | 351 => "Adieresissmall", 379 | 352 => "Aringsmall", 380 | 353 => "AEsmall", 381 | 354 => "Ccedillasmall", 382 | 355 => "Egravesmall", 383 | 356 => "Eacutesmall", 384 | 357 => "Ecircumflexsmall", 385 | 358 => "Edieresissmall", 386 | 359 => "Igravesmall", 387 | 360 => "Iacutesmall", 388 | 361 => "Icircumflexsmall", 389 | 362 => "Idieresissmall", 390 | 363 => "Ethsmall", 391 | 364 => "Ntildesmall", 392 | 365 => "Ogravesmall", 393 | 366 => "Oacutesmall", 394 | 367 => "Ocircumflexsmall", 395 | 368 => "Otildesmall", 396 | 369 => "Odieresissmall", 397 | 370 => "OEsmall", 398 | 371 => "Oslashsmall", 399 | 372 => "Ugravesmall", 400 | 373 => "Uacutesmall", 401 | 374 => "Ucircumflexsmall", 402 | 375 => "Udieresissmall", 403 | 376 => "Yacutesmall", 404 | 377 => "Thornsmall", 405 | 378 => "Ydieresissmall", 406 | 379 => "001.000", 407 | 380 => "001.001", 408 | 381 => "001.002", 409 | 382 => "001.003", 410 | 383 => "Black", 411 | 384 => "Bold", 412 | 385 => "Book", 413 | 386 => "Light", 414 | 387 => "Medium", 415 | 388 => "Regular", 416 | 389 => "Roman", 417 | 390 => "Semibold", 418 | _ => return None, 419 | }) 420 | } 421 | 422 | #[cfg(test)] 423 | mod tests { 424 | use super::get_standard_string; 425 | use super::NUMBER_OF_STANDARD_STRINGS; 426 | use crate::compact1::StringID; 427 | 428 | #[test] 429 | fn number_of_standard_strings() { 430 | assert!(get_standard_string(NUMBER_OF_STANDARD_STRINGS as StringID - 1).is_some()); 431 | assert!(get_standard_string(NUMBER_OF_STANDARD_STRINGS as StringID).is_none()); 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /src/compact1/index/subroutines.rs: -------------------------------------------------------------------------------- 1 | index! { 2 | /// A subroutine index. 3 | #[derive(Default)] 4 | pub Subroutines 5 | } 6 | -------------------------------------------------------------------------------- /src/compact1/mod.rs: -------------------------------------------------------------------------------- 1 | //! The [Compact Font Format][1] of version 1.0. 2 | //! 3 | //! [1]: https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf 4 | 5 | pub mod character_set; 6 | pub mod encoding; 7 | pub mod font_set; 8 | pub mod index; 9 | 10 | mod header; 11 | mod number; 12 | mod offset; 13 | mod operation; 14 | 15 | pub use character_set::CharacterSet; 16 | pub use encoding::Encoding; 17 | pub use font_set::FontSet; 18 | pub use header::Header; 19 | pub use index::Index; 20 | pub use number::Number; 21 | pub use offset::{Offset, OffsetSize}; 22 | pub use operation::{Operand, Operations, Operator}; 23 | 24 | use crate::{Error, Result}; 25 | 26 | /// A glyph identifier. 27 | pub type GlyphID = u16; 28 | 29 | /// A string identifier. 30 | pub type StringID = u16; 31 | 32 | impl TryFrom for StringID { 33 | type Error = Error; 34 | 35 | #[inline] 36 | fn try_from(number: Number) -> Result { 37 | match number { 38 | Number::Integer(value) if value >= 0 => Ok(value as Self), 39 | _ => raise!("found a malformed string ID"), 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/compact1/number.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | 3 | macro_rules! reject(() => (raise!("found a malformed number"))); 4 | 5 | /// A number. 6 | #[derive(Clone, Copy, Debug, PartialEq)] 7 | pub enum Number { 8 | /// An integer number. 9 | Integer(i32), 10 | /// A real number. 11 | Real(f32), 12 | } 13 | 14 | impl From for Number { 15 | #[inline] 16 | fn from(value: f32) -> Self { 17 | Number::Real(value) 18 | } 19 | } 20 | 21 | impl From for Number { 22 | #[inline] 23 | fn from(value: i32) -> Self { 24 | Number::Integer(value) 25 | } 26 | } 27 | 28 | impl crate::value::Read for Number { 29 | fn read(tape: &mut T) -> Result { 30 | let first = tape.take::()?; 31 | Ok(match first { 32 | 0x20..=0xf6 => Number::Integer(first as i32 - 139), 33 | 0xf7..=0xfa => { 34 | Number::Integer((first as i32 - 247) * 256 + tape.take::()? as i32 + 108) 35 | } 36 | 0xfb..=0xfe => { 37 | Number::Integer(-(first as i32 - 251) * 256 - tape.take::()? as i32 - 108) 38 | } 39 | 0x1c => Number::Integer(tape.take::()? as i16 as i32), 40 | 0x1d => Number::Integer(tape.take::()? as i32), 41 | 0x1e => Number::Real(parse(tape)?), 42 | _ => reject!(), 43 | }) 44 | } 45 | } 46 | 47 | fn parse(tape: &mut T) -> Result { 48 | let mut buffer = String::new(); 49 | let mut byte = 0; 50 | let mut high = true; 51 | loop { 52 | let nibble = match high { 53 | true => { 54 | byte = tape.take::()?; 55 | byte >> 4 56 | } 57 | false => byte & 0x0f, 58 | }; 59 | high = !high; 60 | match nibble { 61 | 0..=9 => buffer.push((b'0' + nibble) as char), 62 | 0x0a => buffer.push('.'), 63 | 0x0b => buffer.push('e'), 64 | 0x0c => buffer.push_str("e-"), 65 | 0x0e => buffer.push('-'), 66 | 0x0f => break, 67 | _ => reject!(), 68 | } 69 | } 70 | match buffer.parse() { 71 | Ok(value) => Ok(value), 72 | _ => reject!(), 73 | } 74 | } 75 | 76 | #[cfg(test)] 77 | mod tests { 78 | use std::io::Cursor; 79 | 80 | use super::Number; 81 | use crate::tape::Read; 82 | 83 | #[test] 84 | fn integer() { 85 | macro_rules! read( 86 | ($tape:expr) => ( 87 | match $tape.take().unwrap() { 88 | Number::Integer(value) => value, 89 | _ => unreachable!(), 90 | } 91 | ) 92 | ); 93 | 94 | let mut tape = Cursor::new(vec![0x8b]); 95 | assert_eq!(read!(tape), 0); 96 | 97 | let mut tape = Cursor::new(vec![0xef]); 98 | assert_eq!(read!(tape), 100); 99 | 100 | let mut tape = Cursor::new(vec![0x27]); 101 | assert_eq!(read!(tape), -100); 102 | 103 | let mut tape = Cursor::new(vec![0xfa, 0x7c]); 104 | assert_eq!(read!(tape), 1000); 105 | 106 | let mut tape = Cursor::new(vec![0xfe, 0x7c]); 107 | assert_eq!(read!(tape), -1000); 108 | 109 | let mut tape = Cursor::new(vec![0x1c, 0x27, 0x10]); 110 | assert_eq!(read!(tape), 10000); 111 | 112 | let mut tape = Cursor::new(vec![0x1c, 0xd8, 0xf0]); 113 | assert_eq!(read!(tape), -10000); 114 | 115 | let mut tape = Cursor::new(vec![0x1d, 0x00, 0x01, 0x86, 0xa0]); 116 | assert_eq!(read!(tape), 100000); 117 | 118 | let mut tape = Cursor::new(vec![0x1d, 0xff, 0xfe, 0x79, 0x60]); 119 | assert_eq!(read!(tape), -100000); 120 | } 121 | 122 | #[test] 123 | fn real() { 124 | macro_rules! read( 125 | ($tape:expr) => ( 126 | match $tape.take().unwrap() { 127 | Number::Real(value) => value, 128 | _ => unreachable!(), 129 | } 130 | ) 131 | ); 132 | 133 | let mut tape = Cursor::new(vec![0x1e, 0xe2, 0xa2, 0x5f, 0x0f]); 134 | assert!((read!(tape) + 2.25).abs() < 1e-14); 135 | 136 | let mut tape = Cursor::new(vec![0x1e, 0x0a, 0x14, 0x05, 0x41, 0xc3, 0xff, 0x0f]); 137 | assert!((read!(tape) - 0.140541e-3).abs() < 1e-14); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/compact1/offset.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | 3 | /// An offset. 4 | #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] 5 | pub struct Offset(pub u32); 6 | 7 | /// An offset size. 8 | pub type OffsetSize = u8; 9 | 10 | impl crate::walue::Read<'static> for Offset { 11 | type Parameter = OffsetSize; 12 | 13 | fn read(tape: &mut T, size: OffsetSize) -> Result { 14 | Ok(Offset(match size { 15 | 1 => tape.take::()? as u32, 16 | 2 => tape.take::()? as u32, 17 | 3 => { 18 | let value: [u8; 3] = tape.take()?; 19 | u32::from_be_bytes([0, value[0], value[1], value[2]]) 20 | } 21 | 4 => tape.take::()?, 22 | _ => raise!("found a malformed offset"), 23 | })) 24 | } 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use std::io::Cursor; 30 | 31 | use crate::compact1::Offset; 32 | use crate::walue::Read; 33 | 34 | #[test] 35 | fn read() { 36 | let mut tape = Cursor::new(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 37 | 38 | assert_eq!(Offset::read(&mut tape, 1).unwrap().0, 0x01); 39 | assert_eq!(Offset::read(&mut tape, 2).unwrap().0, 0x0203); 40 | assert_eq!(Offset::read(&mut tape, 3).unwrap().0, 0x040506); 41 | assert_eq!(Offset::read(&mut tape, 4).unwrap().0, 0x0708090a); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/compact1/operation.rs: -------------------------------------------------------------------------------- 1 | //! The operations. 2 | 3 | use std::collections::HashMap; 4 | 5 | use crate::compact1::number::Number; 6 | use crate::Result; 7 | 8 | /// An operand. 9 | pub type Operand = Number; 10 | 11 | /// A collection of operations. 12 | #[derive(Clone, Debug, Default)] 13 | pub struct Operations(pub HashMap>); 14 | 15 | struct Operation(Operator, Vec); 16 | 17 | impl Operations { 18 | /// Return the operands of an operation. 19 | #[inline] 20 | pub fn get(&self, operator: Operator) -> Option<&[Operand]> { 21 | match self.0.get(&operator) { 22 | Some(operands) => Some(operands), 23 | _ => operator.default(), 24 | } 25 | } 26 | 27 | #[doc(hidden)] 28 | #[inline] 29 | pub fn get_single(&self, operator: Operator) -> Option { 30 | self.get(operator).and_then(|operands| { 31 | if !operands.is_empty() { 32 | Some(operands[0]) 33 | } else { 34 | None 35 | } 36 | }) 37 | } 38 | 39 | #[doc(hidden)] 40 | #[inline] 41 | pub fn get_double(&self, operator: Operator) -> Option<(Operand, Operand)> { 42 | self.get(operator).and_then(|operands| { 43 | if operands.len() > 1 { 44 | Some((operands[0], operands[1])) 45 | } else { 46 | None 47 | } 48 | }) 49 | } 50 | } 51 | 52 | impl crate::value::Read for Operations { 53 | fn read(tape: &mut T) -> Result { 54 | use std::io::ErrorKind; 55 | 56 | let mut operations = HashMap::new(); 57 | loop { 58 | match tape.take() { 59 | Ok(Operation(operator, operands)) => { 60 | operations.insert(operator, operands); 61 | } 62 | Err(error) => { 63 | if error.kind() == ErrorKind::UnexpectedEof { 64 | return Ok(Operations(operations)); 65 | } else { 66 | return Err(error); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | dereference! { Operations::0 => HashMap> } 75 | 76 | impl crate::value::Read for Operation { 77 | fn read(tape: &mut T) -> Result { 78 | let mut operands = vec![]; 79 | loop { 80 | match tape.peek::()? { 81 | 0x1c | 0x1d | 0x1e | 0x20..=0xfe => operands.push(tape.take()?), 82 | code => { 83 | let code = if code == 0x0c { 84 | tape.take::()? 85 | } else { 86 | tape.take::()? as u16 87 | }; 88 | return Ok(Self(Operator::from(code)?, operands)); 89 | } 90 | } 91 | } 92 | } 93 | } 94 | 95 | macro_rules! default( 96 | ([$($operand:expr),+ $(,)?]) => ({ 97 | const OPERANDS: &'static [Operand] = &[$($operand),+]; 98 | Some(OPERANDS) 99 | }); 100 | ([]) => (None); 101 | ); 102 | 103 | macro_rules! operator { 104 | (pub $name:ident { $($code:pat => $variant:ident $default:tt,)+ }) => ( 105 | operator! { @define pub $name { $($variant,)+ } } 106 | operator! { @implement pub $name { $($code => $variant $default,)+ } } 107 | ); 108 | (@define pub $name:ident { $($variant:ident,)* }) => ( 109 | /// An operator. 110 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 111 | pub enum $name { $($variant,)* } 112 | ); 113 | (@implement pub $name:ident { $($code:pat => $variant:ident $default:tt,)* }) => (impl $name { 114 | #[doc(hidden)] 115 | pub fn from(code: u16) -> Result { 116 | use self::$name::*; 117 | 118 | Ok(match code { 119 | $($code => $variant,)+ 120 | code => raise!("found an unknown operator ({code})"), 121 | }) 122 | } 123 | 124 | /// Return the default operands. 125 | pub fn default(&self) -> Option<&'static [Operand]> { 126 | use self::$name::*; 127 | 128 | match *self { 129 | $($variant => default!($default),)+ 130 | } 131 | } 132 | }); 133 | } 134 | 135 | operator! { 136 | pub Operator { 137 | 0x00 => Version [], 138 | 0x01 => Notice [], 139 | 0x02 => FullName [], 140 | 0x03 => FamilyName [], 141 | 0x04 => Weight [], 142 | 0x05 => FontBBox [ 143 | Number::Integer(0), 144 | Number::Integer(0), 145 | Number::Integer(0), 146 | Number::Integer(0), 147 | ], 148 | 0x06 => BlueValues [], 149 | 0x07 => OtherBlues [], 150 | 0x08 => FamilyBlues [], 151 | 0x09 => FamilyOtherBlues [], 152 | 0x0a => StdHW [], 153 | 0x0b => StdVW [], 154 | // 0x0c => Escape, 155 | 0x0d => UniqueID [], 156 | 0x0e => XUID [], 157 | 0x0f => CharSet [Number::Integer(0)], 158 | 0x10 => Encoding [Number::Integer(0)], 159 | 0x11 => CharStrings [], 160 | 0x12 => Private [], 161 | 0x13 => Subrs [], 162 | 0x14 => DefaultWidthX [Number::Integer(0)], 163 | 0x15 => NominalWidthX [Number::Integer(0)], 164 | // 0x16..=0x1b => Reserved, 165 | // 0x1c => ShortInt, 166 | // 0x1d => LongInt, 167 | // 0x1e => BCD, 168 | // 0x1f => Reserved, 169 | // 0x20..=0xf6 => , 170 | // 0xf7..=0xfe => , 171 | // 0xff => Reserved, 172 | 0x0c00 => Copyright [], 173 | 0x0c01 => IsFixedPitch [Number::Integer(false as i32)], 174 | 0x0c02 => ItalicAngle [Number::Integer(0)], 175 | 0x0c03 => UnderlinePosition [Number::Integer(-100)], 176 | 0x0c04 => UnderlineThickness [Number::Integer(50)], 177 | 0x0c05 => PaintType [Number::Integer(0)], 178 | 0x0c06 => CharStringType [Number::Integer(2)], 179 | 0x0c07 => FontMatrix [ 180 | Number::Real(0.001), 181 | Number::Real(0.0), 182 | Number::Real(0.0), 183 | Number::Real(0.001), 184 | Number::Real(0.0), 185 | Number::Real(0.0), 186 | ], 187 | 0x0c08 => StrokeWidth [Number::Integer(0)], 188 | 0x0c09 => BlueScale [Number::Real(0.039625)], 189 | 0x0c0a => BlueShift [Number::Integer(7)], 190 | 0x0c0b => BlueFuzz [Number::Integer(1)], 191 | 0x0c0c => StemSnapH [], 192 | 0x0c0d => StemSnapV [], 193 | 0x0c0e => ForceBold [Number::Integer(false as i32)], 194 | // 0x0c0f..=0x0c10 => Reserved, 195 | 0x0c11 => LanguageGroup [Number::Integer(0)], 196 | 0x0c12 => ExpansionFactor [Number::Real(0.06)], 197 | 0x0c13 => InitialRandomSeed [Number::Integer(0)], 198 | 0x0c14 => SyntheticBase [], 199 | 0x0c15 => PostScript [], 200 | 0x0c16 => BaseFontName [], 201 | 0x0c17 => BaseFontBlend [], 202 | // 0x0c18..=0x0c1d => Reserved, 203 | 0x0c1e => ROS [], 204 | 0x0c1f => CIDFontVersion [Number::Integer(0)], 205 | 0x0c20 => CIDFontRevision [Number::Integer(0)], 206 | 0x0c21 => CIDFontType [Number::Integer(0)], 207 | 0x0c22 => CIDCount [Number::Integer(8720)], 208 | 0x0c23 => UIDBase [], 209 | 0x0c24 => FDArray [], 210 | 0x0c25 => FDSelect [], 211 | 0x0c26 => FontName [], 212 | // 0x0c27..=0x0cff => Reserved, 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Parser of PostScript fonts. 2 | 3 | #[macro_use(dereference, jump_take, jump_take_given, raise, table)] 4 | extern crate typeface; 5 | 6 | pub mod compact1; 7 | pub mod type1; 8 | pub mod type2; 9 | 10 | pub use typeface::{tape, value, walue, Error, Result}; 11 | -------------------------------------------------------------------------------- /src/type1/mod.rs: -------------------------------------------------------------------------------- 1 | //! The [Type 1 Font Format][1]. 2 | //! 3 | //! [1]: https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf 4 | -------------------------------------------------------------------------------- /src/type2/mod.rs: -------------------------------------------------------------------------------- 1 | //! The [Type 2 Charstring Format][1]. 2 | //! 3 | //! [1]: https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf 4 | 5 | mod number; 6 | mod operation; 7 | mod program; 8 | 9 | pub use operation::{Operand, Operation, Operations, Operator}; 10 | pub use program::Program; 11 | -------------------------------------------------------------------------------- /src/type2/number.rs: -------------------------------------------------------------------------------- 1 | use typeface::q32; 2 | 3 | use crate::Result; 4 | 5 | pub fn read(tape: &mut T) -> Result { 6 | let first = tape.take::()?; 7 | Ok(match first { 8 | 0x20..=0xf6 => (first as i32 - 139) as f32, 9 | 0xf7..=0xfa => ((first as i32 - 247) * 256 + tape.take::()? as i32 + 108) as f32, 10 | 0xfb..=0xfe => (-(first as i32 - 251) * 256 - tape.take::()? as i32 - 108) as f32, 11 | 0x1c => tape.take::()? as i16 as i32 as f32, 12 | 0xff => tape.take::()?.into(), 13 | _ => raise!("found a malformed number"), 14 | }) 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use std::io::Cursor; 20 | 21 | #[test] 22 | fn real() { 23 | let mut tape = Cursor::new(vec![0xff, 0x00, 0x01, 0x04, 0x5a]); 24 | assert!(format!("{:.3}", super::read(&mut tape).unwrap()) == "1.017"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/type2/operation.rs: -------------------------------------------------------------------------------- 1 | //! The operations. 2 | 3 | use crate::Result; 4 | 5 | /// An operand. 6 | pub type Operand = f32; 7 | 8 | /// An operation. 9 | pub type Operation = (Operator, Vec); 10 | 11 | /// A collection of operations. 12 | pub type Operations = Vec; 13 | 14 | macro_rules! operator { 15 | (pub $name:ident { $($code:pat => $variant:ident,)+ }) => ( 16 | operator! { @define pub $name { $($variant,)+ } } 17 | operator! { @implement pub $name { $($code => $variant,)+ } } 18 | ); 19 | (@define pub $name:ident { $($variant:ident,)* }) => ( 20 | /// An operator. 21 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 22 | pub enum $name { $($variant,)* } 23 | ); 24 | (@implement pub $name:ident { $($code:pat => $variant:ident,)* }) => (impl $name { 25 | #[doc(hidden)] 26 | pub fn from(code: u16) -> Result { 27 | use self::$name::*; 28 | Ok(match code { 29 | $($code => $variant,)+ 30 | code => raise!("found an unknown operator ({code})"), 31 | }) 32 | } 33 | }); 34 | } 35 | 36 | operator! { 37 | pub Operator { 38 | // 0x00 => Reserved, 39 | 0x01 => HStem, 40 | // 0x02 => Reserved, 41 | 0x03 => VStem, 42 | 0x04 => VMoveTo, 43 | 0x05 => RLineTo, 44 | 0x06 => HLineTo, 45 | 0x07 => VLineTo, 46 | 0x08 => RRCurveTo, 47 | // 0x09 => Reserved, 48 | 0x0a => CallSubr, 49 | 0x0b => Return, 50 | // 0x0c => Escape, 51 | // 0x0d => Reserved, 52 | 0x0e => EndChar, 53 | // 0x0f => Reserved, 54 | // 0x10 => Reserved, 55 | // 0x11 => Reserved, 56 | 0x12 => HStemHM, 57 | 0x13 => HintMask, 58 | 0x14 => CntrMask, 59 | 0x15 => RMoveTo, 60 | 0x16 => HMoveTo, 61 | 0x17 => VStemHM, 62 | 0x18 => RCurveLine, 63 | 0x19 => RLineCurve, 64 | 0x1a => VVCurveTo, 65 | 0x1b => HHCurveTo, 66 | // 0x1c => ShortInt, 67 | 0x1d => CallGSubr, 68 | 0x1e => VHCurveTo, 69 | 0x1f => HVCurveTo, 70 | // 0x20..=0xf6 => , 71 | // 0xf7..=0xfe => , 72 | // 0xff => , 73 | // 0x0c00 => Reserved, 74 | // 0x0c01 => Reserved, 75 | // 0x0c02 => Reserved, 76 | 0x0c03 => And, 77 | 0x0c04 => Or, 78 | 0x0c05 => Not, 79 | // 0x0c06 => Reserved, 80 | // 0x0c07 => Reserved, 81 | // 0x0c08 => Reserved, 82 | 0x0c09 => Abs, 83 | 0x0c0a => Add, 84 | 0x0c0b => Sub, 85 | 0x0c0c => Div, 86 | // 0x0c0d => Reserved, 87 | 0x0c0e => Neg, 88 | 0x0c0f => Eq, 89 | // 0x0c10 => Reserved, 90 | // 0x0c11 => Reserved, 91 | 0x0c12 => Drop, 92 | // 0x0c13 => Reserved, 93 | 0x0c14 => Put, 94 | 0x0c15 => Get, 95 | 0x0c16 => IfElse, 96 | 0x0c17 => Random, 97 | 0x0c18 => Mul, 98 | // 0x0c19 => Reserved, 99 | 0x0c1a => Sqrt, 100 | 0x0c1b => Dup, 101 | 0x0c1c => Exch, 102 | 0x0c1d => Index, 103 | 0x0c1e => Roll, 104 | // 0x0c1f => Reserved, 105 | // 0x0c20 => Reserved, 106 | // 0x0c21 => Reserved, 107 | 0x0c22 => HFlex, 108 | 0x0c23 => Flex, 109 | 0x0c24 => HFlex1, 110 | 0x0c25 => Flex1, 111 | // 0x0c26..=0x0cff => Reserved, 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/type2/program.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use crate::tape::Read; 4 | use crate::type2::{number, Operand, Operation, Operator}; 5 | use crate::Result; 6 | 7 | /// A program. 8 | pub struct Program<'l> { 9 | routine: Routine<'l>, 10 | global: &'l [Vec], 11 | local: &'l [Vec], 12 | stack: Vec, 13 | stems: usize, 14 | width: Option, 15 | } 16 | 17 | struct Routine<'l> { 18 | tape: Cursor<&'l [u8]>, 19 | size: usize, 20 | caller: Option>>, 21 | } 22 | 23 | impl<'l> Program<'l> { 24 | /// Create a program. 25 | #[inline] 26 | pub fn new(code: &'l [u8], global: &'l [Vec], local: &'l [Vec]) -> Self { 27 | Program { 28 | routine: Routine::new(code), 29 | global, 30 | local, 31 | stack: vec![], 32 | stems: 0, 33 | width: None, 34 | } 35 | } 36 | 37 | /// Return the next operation. 38 | #[allow(clippy::should_implement_trait)] 39 | #[allow(unused_comparisons, unused_must_use)] 40 | pub fn next(&mut self) -> Result> { 41 | use crate::type2::Operator::*; 42 | 43 | if self.routine.done()? { 44 | return Ok(None); 45 | } 46 | 47 | macro_rules! pop( 48 | () => (match self.stack.pop() { 49 | Some(value) => value, 50 | _ => raise!("expected an operand"), 51 | }); 52 | (bool) => (match self.stack.pop() { 53 | Some(value) => value != 0.0, 54 | _ => raise!("expected an operand"), 55 | }); 56 | (i32) => (match self.stack.pop() { 57 | Some(value) if value as i32 as Operand == value => value as i32, 58 | _ => raise!("expected an operand of a different type"), 59 | }); 60 | ); 61 | macro_rules! push( 62 | ($operand:expr, bool) => ({ 63 | let operand = $operand; 64 | self.stack.push(if operand { 1.0 } else { 0.0 }); 65 | }); 66 | ($operand:expr) => ({ 67 | let operand = $operand; 68 | self.stack.push(operand); 69 | }); 70 | ); 71 | macro_rules! read(($index:expr) => ({ 72 | let length = self.stack.len(); 73 | if $index >= length { 74 | raise!("expected more operands"); 75 | } 76 | self.stack[length - 1 - $index] 77 | })); 78 | 79 | let mut code; 80 | loop { 81 | code = self.routine.peek::()?; 82 | match code { 83 | 0x1c | 0x20..=0xff => push!(self.routine.take_operand()?), 84 | _ => break, 85 | } 86 | } 87 | let operator = if code == 0x0c { 88 | Operator::from(self.routine.take::()?)? 89 | } else { 90 | Operator::from(self.routine.take::()? as u16)? 91 | }; 92 | 93 | macro_rules! clear( 94 | (@reduce [$min:expr, $left:expr] []) => ({ 95 | if $min > $left { 96 | $min = $left; 97 | } 98 | }); 99 | (@reduce [$min:expr, $left:expr] [equal($count:expr), $($tail:tt)*]) => ({ 100 | if $left >= $count { 101 | let left = $left - $count; 102 | clear!(@reduce [$min, left] [$($tail)*]); 103 | } 104 | }); 105 | (@reduce [$min:expr, $left:expr] [maybe_equal($count:expr), $($tail:tt)*]) => ({ 106 | clear!(@reduce [$min, $left] [$($tail)*]); 107 | if $left >= $count { 108 | clear!(@reduce [$min, $left - $count] [$($tail)*]); 109 | } 110 | }); 111 | (@reduce [$min:expr, $left:expr] [modulo($count:expr), $($tail:tt)*]) => ({ 112 | for i in 1..($left / $count + 1) { 113 | let left = $left - i * $count; 114 | clear!(@reduce [$min, left] [$($tail)*]); 115 | } 116 | }); 117 | (@reduce [$min:expr, $left:expr] [maybe_modulo($count:expr), $($tail:tt)*]) => ({ 118 | for i in 0..($left / $count + 1) { 119 | let left = $left - i * $count; 120 | clear!(@reduce [$min, left] [$($tail)*]); 121 | } 122 | }); 123 | ($([$($predicate:ident($count:expr)),*]),+) => ({ 124 | let length = self.stack.len(); 125 | let mut min = !0; 126 | $(clear!(@reduce [min, length] [$($predicate($count),)*]);)+ 127 | if min == !0 { 128 | raise!("found malformed operands"); 129 | } 130 | let mut stack = std::mem::take(&mut self.stack); 131 | let operands = stack.drain(min..).collect(); 132 | if min > 0 && self.width.is_none() { 133 | self.width = Some(stack[min - 1]); 134 | } 135 | return Ok(Some((operator, operands))); 136 | }); 137 | ); 138 | 139 | match operator { 140 | // Path-construction operators 141 | RMoveTo => clear!([equal(2)]), 142 | HMoveTo | VMoveTo => clear!([equal(1)]), 143 | RLineTo => clear!([modulo(2)]), 144 | HLineTo | VLineTo => clear!([equal(1), maybe_modulo(2)], [modulo(2)]), 145 | RRCurveTo => clear!([modulo(6)]), 146 | HHCurveTo | VVCurveTo => clear!([maybe_equal(1), modulo(4)]), 147 | HVCurveTo | VHCurveTo => clear!( 148 | [equal(4), maybe_modulo(8), maybe_equal(1)], 149 | [modulo(8), maybe_equal(1)] 150 | ), 151 | RCurveLine => clear!([modulo(6), equal(2)]), 152 | RLineCurve => clear!([modulo(2), equal(6)]), 153 | Flex => clear!([equal(13)]), 154 | Flex1 => clear!([equal(11)]), 155 | HFlex => clear!([equal(7)]), 156 | HFlex1 => clear!([equal(9)]), 157 | 158 | // Terminal operator 159 | EndChar => { 160 | while let Some(caller) = self.routine.caller.take() { 161 | if !self.routine.done()? { 162 | raise!("found trailing data after the end operator"); 163 | } 164 | std::mem::replace(&mut self.routine, *caller); 165 | } 166 | let length = self.stack.len(); 167 | if length > 0 && self.width.is_none() { 168 | self.width = Some(self.stack[length - 1]); 169 | } 170 | return Ok(None); 171 | } 172 | 173 | // Hint operators 174 | HStem | VStem | HStemHM | VStemHM => { 175 | self.stems += self.stack.len() >> 1; 176 | clear!([equal(2), maybe_modulo(2)]); 177 | } 178 | HintMask | CntrMask => { 179 | self.stems += self.stack.len() >> 1; 180 | let _ = self.routine.take_given::>((self.stems + 7) >> 3)?; 181 | clear!([equal(0)]); 182 | } 183 | 184 | // Arithmetic operators 185 | Abs => push!(pop!().abs()), 186 | Add => push!(pop!() + pop!()), 187 | Sub => { 188 | let (right, left) = (pop!(), pop!()); 189 | push!(left - right); 190 | } 191 | Div => { 192 | let (right, left) = (pop!(), pop!()); 193 | push!(left / right); 194 | } 195 | Neg => push!(-pop!()), 196 | // Random => 197 | Mul => push!(pop!() * pop!()), 198 | Sqrt => push!(pop!().sqrt()), 199 | #[allow(dropping_copy_types)] 200 | Drop => std::mem::drop(pop!()), 201 | Exch => { 202 | let (right, left) = (pop!(), pop!()); 203 | push!(right); 204 | push!(left); 205 | } 206 | Index => { 207 | let i = pop!(i32); 208 | push!(read!(if i >= 0 { i as usize } else { 0 })); 209 | } 210 | Roll => { 211 | let (shift, span) = (pop!(i32), pop!(i32)); 212 | let length = self.stack.len(); 213 | if span < 0 { 214 | raise!("found an invalid operand"); 215 | } else if span as usize > length { 216 | raise!("expected more operands"); 217 | } else if span > 0 { 218 | let position = length - span as usize; 219 | match shift.cmp(&0) { 220 | std::cmp::Ordering::Greater => { 221 | for _ in 0..shift { 222 | let operand = pop!(); 223 | self.stack.insert(position, operand); 224 | } 225 | } 226 | std::cmp::Ordering::Less => { 227 | for _ in 0..(-shift) { 228 | push!(self.stack.remove(position)); 229 | } 230 | } 231 | _ => {} 232 | } 233 | } 234 | } 235 | Dup => push!(read!(0)), 236 | 237 | // Storage operators 238 | // Put => 239 | // Get => 240 | 241 | // Conditional operators 242 | And => { 243 | let (right, left) = (pop!(bool), pop!(bool)); 244 | push!(left && right, bool); 245 | } 246 | Or => { 247 | let (right, left) = (pop!(bool), pop!(bool)); 248 | push!(left || right, bool); 249 | } 250 | Not => push!(!pop!(bool), bool), 251 | Eq => { 252 | let (right, left) = (pop!(), pop!()); 253 | push!(left == right, bool); 254 | } 255 | IfElse => { 256 | let (right, left, no, yes) = (pop!(), pop!(), pop!(), pop!()); 257 | push!(if left <= right { yes } else { no }); 258 | } 259 | 260 | // Subroutine operators 261 | CallSubr | CallGSubr => { 262 | let address = pop!(i32); 263 | let mut routine = { 264 | let subroutines = if operator == CallSubr { 265 | &self.local 266 | } else { 267 | &self.global 268 | }; 269 | let count = subroutines.len(); 270 | let i = address + bias(count); 271 | if i < 0 || i as usize >= count { 272 | raise!("found no subroutine"); 273 | } 274 | Routine::new(&subroutines[i as usize]) 275 | }; 276 | std::mem::swap(&mut self.routine, &mut routine); 277 | self.routine.caller = Some(Box::new(routine)); 278 | } 279 | Return => { 280 | let caller = match self.routine.caller.take() { 281 | Some(caller) => caller, 282 | _ => raise!("found a return operator without a caller"), 283 | }; 284 | std::mem::replace(&mut self.routine, *caller); 285 | } 286 | 287 | operator => raise!("found an unknown operator ({operator:?})"), 288 | }; 289 | self.next() 290 | } 291 | 292 | /// Return the width difference with respect to the nominal width. 293 | #[inline] 294 | pub fn width(&self) -> Option { 295 | self.width 296 | } 297 | } 298 | 299 | impl<'l> Routine<'l> { 300 | #[inline] 301 | fn new(code: &'l [u8]) -> Routine<'l> { 302 | Routine { 303 | tape: Cursor::new(code), 304 | size: code.len(), 305 | caller: None, 306 | } 307 | } 308 | 309 | #[inline] 310 | fn done(&mut self) -> Result { 311 | Ok(Read::position(&mut self.tape)? == self.size as u64) 312 | } 313 | 314 | #[inline] 315 | fn take_operand(&mut self) -> Result { 316 | number::read(&mut self.tape) 317 | } 318 | } 319 | 320 | dereference! { Routine<'l>::tape => Cursor<&'l [u8]> } 321 | 322 | #[inline] 323 | fn bias(count: usize) -> i32 { 324 | if count < 1240 { 325 | 107 326 | } else if count < 33900 { 327 | 1131 328 | } else { 329 | 32768 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /tests/compact1.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod support; 3 | 4 | macro_rules! operations( 5 | ($($operator:ident: [$($operand:expr),*],)*) => ({ 6 | use postscript::compact1::{Operations, Operator}; 7 | Operations(vec![$((Operator::$operator, vec![$($operand.into()),*]),)*].into_iter().collect()) 8 | }); 9 | ); 10 | 11 | mod noto_sans_direct { 12 | use postscript::tape::Read; 13 | 14 | use crate::support::{setup, Fixture}; 15 | 16 | #[test] 17 | fn header() { 18 | use postscript::compact1::Header; 19 | 20 | let mut tape = setup(Fixture::NotoSansJP); 21 | let table = ok!(tape.take::
()); 22 | assert_eq!(table.major, 1); 23 | assert_eq!(table.minor, 0); 24 | assert_eq!(table.header_size, 4); 25 | assert_eq!(table.offset_size, 4); 26 | } 27 | 28 | #[test] 29 | fn names() { 30 | use postscript::compact1::index::Names; 31 | use postscript::compact1::Header; 32 | 33 | let mut tape = setup(Fixture::NotoSansJP); 34 | let position = ok!(tape.position()); 35 | let table = ok!(tape.take::
()); 36 | ok!(tape.jump(position + table.header_size as u64)); 37 | let table: Vec<_> = ok!(ok!(tape.take::()).try_into()); 38 | assert_eq!(table.len(), 1); 39 | assert_eq!(&table[0], "NotoSansJP-Regular"); 40 | } 41 | 42 | #[test] 43 | fn operations() { 44 | use postscript::compact1::index::{Dictionaries, Names}; 45 | use postscript::compact1::Header; 46 | 47 | let mut tape = setup(Fixture::NotoSansJP); 48 | let position = ok!(tape.position()); 49 | let table = ok!(tape.take::
()); 50 | ok!(tape.jump(position + table.header_size as u64)); 51 | let _ = ok!(tape.take::()); 52 | let table: Vec<_> = ok!((&ok!(tape.take::())).try_into()); 53 | assert_eq!(table.len(), 1); 54 | let operations = operations!( 55 | ROS: [394, 395, 0], 56 | Notice: [391], 57 | FullName: [392], 58 | FamilyName: [393], 59 | Weight: [388], 60 | UnderlinePosition: [-150], 61 | FontBBox: [-1002, -1048, 2928, 1808], 62 | CIDFontVersion: [2.002], 63 | CIDCount: [65529], 64 | CharSet: [7068], 65 | CharStrings: [43287], 66 | FDSelect: [42687], 67 | FDArray: [43013], 68 | ); 69 | assert_eq!(table[0].0, operations.0); 70 | } 71 | } 72 | 73 | mod noto_sans_indirect { 74 | use crate::support::{setup_font_set, Fixture}; 75 | 76 | #[test] 77 | fn character_strings() { 78 | let set = setup_font_set(Fixture::NotoSansJP); 79 | let tables = &set.character_strings; 80 | assert_eq!(tables.len(), 1); 81 | assert_eq!(tables[0].len(), 17810); 82 | } 83 | 84 | #[test] 85 | fn records() { 86 | use postscript::compact1::font_set::character_id_keyed::Encoding; 87 | use postscript::compact1::font_set::Record; 88 | use postscript::compact1::{Number, Operator}; 89 | 90 | let set = setup_font_set(Fixture::NotoSansJP); 91 | let records = &set.records; 92 | let strings = &set.strings; 93 | assert_eq!(records.len(), 1); 94 | let record = match &records[0] { 95 | Record::CharacterIDKeyed(ref record) => record, 96 | _ => unreachable!(), 97 | }; 98 | assert_eq!(ok!(strings.get(record.registry)), "Adobe"); 99 | assert_eq!(ok!(strings.get(record.ordering)), "Identity"); 100 | assert_eq!(record.supplement, Number::Integer(0)); 101 | match record.encoding { 102 | Encoding::Format3(ref encoding) => { 103 | assert_eq!(encoding.range_count, 107); 104 | assert_eq!(encoding.glyph_count, 17810); 105 | } 106 | _ => unreachable!(), 107 | } 108 | assert_eq!(record.operations.len(), 18); 109 | let operations = operations!( 110 | FontName: [396], 111 | Private: [32, 3978751], 112 | ); 113 | assert_eq!(record.operations[0].0, operations.0); 114 | assert_eq!( 115 | ok!(strings.get(ok!( 116 | ok!(record.operations[0].get(Operator::FontName))[0].try_into() 117 | ))), 118 | "NotoSansJP-Regular-Alphabetic", 119 | ); 120 | assert_eq!(record.records.len(), 18); 121 | assert_eq!( 122 | record 123 | .records 124 | .iter() 125 | .filter(|record| record.operations.get(Operator::Subrs).is_some()) 126 | .count(), 127 | 14, 128 | ); 129 | assert_eq!( 130 | record 131 | .records 132 | .iter() 133 | .filter(|record| record.subroutines.len() > 0) 134 | .count(), 135 | 14, 136 | ); 137 | let operations = operations!( 138 | BlueValues: [-13, 13, 544, 13, 178, 12], 139 | OtherBlues: [-250, 21], 140 | StdHW: [78], 141 | StdVW: [85], 142 | StemSnapH: [78, 33], 143 | StemSnapV: [85, 10], 144 | DefaultWidthX: [1000], 145 | Subrs: [32], 146 | ); 147 | assert_eq!(record.records[0].operations.0, operations.0); 148 | let operations = operations!( 149 | BlueValues: [-250, 0, 1350, 0], 150 | StdHW: [78], 151 | StdVW: [61], 152 | LanguageGroup: [1], 153 | DefaultWidthX: [500], 154 | ); 155 | assert_eq!(record.records[7].operations.0, operations.0); 156 | } 157 | } 158 | 159 | mod source_serif { 160 | use crate::support::{setup_font_set, Fixture}; 161 | 162 | #[test] 163 | fn header() { 164 | let set = setup_font_set(Fixture::SourceSerifPro); 165 | let table = &set.header; 166 | assert_eq!(table.major, 1); 167 | assert_eq!(table.minor, 0); 168 | assert_eq!(table.header_size, 4); 169 | assert_eq!(table.offset_size, 2); 170 | } 171 | 172 | #[test] 173 | fn names() { 174 | let set = setup_font_set(Fixture::SourceSerifPro); 175 | let table: Vec<_> = ok!(set.names.try_into()); 176 | assert_eq!(table.len(), 1); 177 | assert_eq!(&table[0], "SourceSerifPro-Regular"); 178 | } 179 | 180 | #[test] 181 | fn operations() { 182 | let set = setup_font_set(Fixture::SourceSerifPro); 183 | let table = &set.operations; 184 | assert_eq!(table.len(), 1); 185 | let operations = operations!( 186 | Version: [709], 187 | Notice: [710], 188 | Copyright: [711], 189 | FullName: [712], 190 | FamilyName: [712], 191 | Weight: [388], 192 | FontBBox: [-178, -335, 1138, 918], 193 | CharSet: [8340], 194 | CharStrings: [8917], 195 | Private: [65, 33671], 196 | ); 197 | assert_eq!(table[0].0, operations.0); 198 | } 199 | 200 | #[test] 201 | fn strings() { 202 | let set = setup_font_set(Fixture::SourceSerifPro); 203 | let table = &set.strings; 204 | assert_eq!(table.len(), 322); 205 | assert_eq!(ok!(table.get(0)), ".notdef"); 206 | assert_eq!(ok!(table.get(175)), "Aring"); 207 | assert_eq!(ok!(table.get(500)), "nine.tosf"); 208 | } 209 | 210 | #[test] 211 | fn subroutines() { 212 | let set = setup_font_set(Fixture::SourceSerifPro); 213 | let table = &set.subroutines; 214 | assert_eq!(table.len(), 181); 215 | } 216 | 217 | #[test] 218 | fn character_strings() { 219 | let set = setup_font_set(Fixture::SourceSerifPro); 220 | let tables = &set.character_strings; 221 | assert_eq!(tables.len(), 1); 222 | assert_eq!(tables[0].len(), 547); 223 | } 224 | 225 | #[test] 226 | fn character_sets() { 227 | use postscript::compact1::CharacterSet; 228 | 229 | let set = setup_font_set(Fixture::SourceSerifPro); 230 | let tables = &set.character_sets; 231 | assert_eq!(tables.len(), 1); 232 | match &tables[0] { 233 | &CharacterSet::Format1(..) => {} 234 | _ => unreachable!(), 235 | } 236 | } 237 | 238 | #[test] 239 | fn encodings() { 240 | use postscript::compact1::Encoding; 241 | 242 | let set = setup_font_set(Fixture::SourceSerifPro); 243 | let encodings = &set.encodings; 244 | let strings = &set.strings; 245 | assert_eq!(encodings.len(), 1); 246 | match &encodings[0] { 247 | encoding @ &Encoding::Standard => { 248 | assert_eq!(ok!(strings.get(ok!(encoding.get(0)))), ".notdef"); 249 | assert_eq!(ok!(strings.get(ok!(encoding.get(42)))), "asterisk"); 250 | } 251 | _ => unreachable!(), 252 | } 253 | } 254 | 255 | #[test] 256 | fn records() { 257 | use postscript::compact1::font_set::Record; 258 | 259 | let set = setup_font_set(Fixture::SourceSerifPro); 260 | let tables = &set.records; 261 | assert_eq!(tables.len(), 1); 262 | let operations = operations!( 263 | BlueValues: [-20, 20, 473, 18, 34, 15, 104, 15, 10, 20, 40, 20], 264 | OtherBlues: [-249, 10], 265 | FamilyBlues: [-20, 20, 473, 18, 34, 15, 104, 15, 10, 20, 40, 20], 266 | FamilyOtherBlues: [-249, 10], 267 | BlueScale: [0.0375], 268 | BlueFuzz: [0], 269 | StdHW: [41], 270 | StdVW: [85], 271 | StemSnapH: [41, 15], 272 | StemSnapV: [85, 10], 273 | DefaultWidthX: [370], 274 | NominalWidthX: [604], 275 | Subrs: [65], 276 | ); 277 | match &tables[0] { 278 | Record::CharacterNameKeyed(ref record) => { 279 | assert_eq!(record.operations.0, operations.0); 280 | assert_eq!(record.subroutines.len(), 180); 281 | } 282 | _ => unreachable!(), 283 | } 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /tests/fixtures/Hirakatana-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bodoni/postscript/6cbda8d7d70649c18b8267da4ab553b1a665b6e6/tests/fixtures/Hirakatana-Regular.otf -------------------------------------------------------------------------------- /tests/fixtures/NotoSansJP-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bodoni/postscript/6cbda8d7d70649c18b8267da4ab553b1a665b6e6/tests/fixtures/NotoSansJP-Regular.otf -------------------------------------------------------------------------------- /tests/fixtures/SourceSerifPro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bodoni/postscript/6cbda8d7d70649c18b8267da4ab553b1a665b6e6/tests/fixtures/SourceSerifPro-Regular.otf -------------------------------------------------------------------------------- /tests/support/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::fs::File; 4 | use std::io::{Seek, SeekFrom}; 5 | use std::path::PathBuf; 6 | 7 | use postscript::compact1::FontSet; 8 | use postscript::value::Read; 9 | 10 | macro_rules! ok(($result:expr) => ($result.unwrap())); 11 | 12 | pub enum Fixture { 13 | Hirakatana, 14 | NotoSansJP, 15 | SourceSerifPro, 16 | } 17 | 18 | impl Fixture { 19 | pub fn path(&self) -> PathBuf { 20 | let file_name = match *self { 21 | Fixture::Hirakatana => "Hirakatana-Regular.otf", 22 | Fixture::NotoSansJP => "NotoSansJP-Regular.otf", 23 | Fixture::SourceSerifPro => "SourceSerifPro-Regular.otf", 24 | }; 25 | PathBuf::from("tests").join("fixtures").join(file_name) 26 | } 27 | 28 | pub fn offset(&self) -> u64 { 29 | match *self { 30 | Fixture::Hirakatana => 1524, 31 | Fixture::NotoSansJP => 337316, 32 | Fixture::SourceSerifPro => 17732, 33 | } 34 | } 35 | } 36 | 37 | pub fn setup(fixture: Fixture) -> File { 38 | let mut file = ok!(File::open(fixture.path())); 39 | ok!(file.seek(SeekFrom::Start(fixture.offset()))); 40 | file 41 | } 42 | 43 | pub fn setup_font_set(fixture: Fixture) -> FontSet { 44 | let mut file = ok!(File::open(fixture.path())); 45 | ok!(file.seek(SeekFrom::Start(fixture.offset()))); 46 | let count = ok!(FontSet::count(&mut file)); 47 | ok!(file.seek(SeekFrom::Start(fixture.offset()))); 48 | let table = ok!(FontSet::read(&mut file)); 49 | assert_eq!(table.operations.len(), count); 50 | table 51 | } 52 | -------------------------------------------------------------------------------- /tests/type2.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod support; 3 | 4 | macro_rules! operations( 5 | ($($operator:ident: [$($operand:expr),*],)*) => ({ 6 | use postscript::type2::{Operand, Operator}; 7 | let mut operations = vec![]; 8 | $(operations.push((Operator::$operator, vec![$($operand as Operand),*]));)* 9 | operations 10 | }); 11 | ); 12 | 13 | mod hirakatana { 14 | use postscript::compact1::font_set::Record; 15 | use postscript::type2::Program; 16 | 17 | use crate::support::{setup_font_set, Fixture}; 18 | 19 | #[test] 20 | fn one() { 21 | let set = setup_font_set(Fixture::Hirakatana); 22 | let code = &set.character_strings[0][10]; 23 | let global = &set.subroutines; 24 | let local = match &set.records[0] { 25 | Record::CharacterNameKeyed(ref record) => &*record.subroutines, 26 | _ => unreachable!(), 27 | }; 28 | let mut program = Program::new(code, global, local); 29 | let mut operations = vec![]; 30 | while let Some(operation) = ok!(program.next()) { 31 | operations.push(operation); 32 | } 33 | assert_eq!( 34 | operations, 35 | operations!( 36 | HStem: [372.0, 57.933594], 37 | RMoveTo: [42.0, 401.05176], 38 | VLineTo: [-29.051758, 186.59766], 39 | RLineTo: [186.59668, 0.0, -0.18261719, 28.88086, -0.18261719, 28.88086, -186.41504, 0.171875, -186.41406, 0.17089844], 40 | ) 41 | ); 42 | } 43 | } 44 | 45 | mod source_serif { 46 | use postscript::compact1::font_set::Record; 47 | use postscript::type2::Program; 48 | 49 | use crate::support::{setup_font_set, Fixture}; 50 | 51 | #[test] 52 | fn all() { 53 | let set = setup_font_set(Fixture::SourceSerifPro); 54 | let global = &set.subroutines; 55 | let local = match &set.records[0] { 56 | Record::CharacterNameKeyed(ref record) => &*record.subroutines, 57 | _ => unreachable!(), 58 | }; 59 | for code in set.character_strings[0].iter() { 60 | let mut program = Program::new(code, global, local); 61 | while let Some(..) = ok!(program.next()) {} 62 | } 63 | } 64 | 65 | #[test] 66 | fn one() { 67 | let set = setup_font_set(Fixture::SourceSerifPro); 68 | let code = &set.character_strings[0][134]; 69 | let global = &set.subroutines; 70 | let local = match &set.records[0] { 71 | Record::CharacterNameKeyed(ref record) => &*record.subroutines, 72 | _ => unreachable!(), 73 | }; 74 | let mut program = Program::new(code, global, local); 75 | let mut operations = vec![]; 76 | while let Some(operation) = ok!(program.next()) { 77 | operations.push(operation); 78 | } 79 | assert_eq!(program.width(), Some(-95.0)); 80 | assert_eq!( 81 | operations, 82 | operations!( 83 | HStemHM: [-15, 66, -61, 52, 403, 46, 82, 63, 20, 62], 84 | HintMask: [], 85 | RMoveTo: [112, 585], 86 | VHCurveTo: [50, 20, 21, 28, 21, 16, -13, -26, 27], 87 | HintMask: [], 88 | HHCurveTo: [-29, 29, 26, -15, 31], 89 | HVCurveTo: [53, 43, 42, 68, 10, -1, 7, -1, 7], 90 | HLineTo: [-34], 91 | VHCurveTo: [-51, -21, -20, -26, -21, -18, 13, 26, -26], 92 | HintMask: [], 93 | HHCurveTo: [28, -29, -26, 15, -31], 94 | HVCurveTo: [-53, -43, -42, -68, -7, 1, -10, 1, -6], 95 | RMoveTo: [246, -479], 96 | HintMask: [], 97 | HHCurveTo: [-41, -58, -19, -14, -33], 98 | HVCurveTo: [-24, -21, 7, 16, -15], 99 | VVCurveTo: [-12, 12, -8, 15, 26], 100 | VHCurveTo: [30, 11, 39, 87, 34], 101 | RRCurveTo: [21, 8, 36, 12, 35, 10], 102 | HintMask: [], 103 | RMoveTo: [159, -196], 104 | RLineTo: [-5, -5], 105 | HHCurveTo: [-8, -8, -13, -9, -16], 106 | HVCurveTo: [-22, -11, 18, 41], 107 | VLineTo: [216], 108 | VHCurveTo: [126, -50, 48, -105, -102, -74, -50, -76, -19], 109 | HHCurveTo: [-26, 3, 16, -17, 28], 110 | HVCurveTo: [27, 18, 17, 31, 9], 111 | RLineTo: [20, 69], 112 | HHCurveTo: [5, 20, 17, 1, 14], 113 | HVCurveTo: [66, 28, -24, -97], 114 | VLineTo: [-27], 115 | RRCurveTo: [-40, -9, -42, -13, -31, -11], 116 | VVCurveTo: [-135, -49, -31, -46, -57], 117 | HintMask: [], 118 | VHCurveTo: [-83, 61, -44, 73, 59, 33, 27, 55, 55], 119 | HintMask: [], 120 | HHCurveTo: [-47, 8, 31, -30, 57], 121 | HVCurveTo: [32, 26, 10, 42, 23], 122 | ) 123 | ); 124 | } 125 | } 126 | --------------------------------------------------------------------------------