diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ad7fa86..b9a0dc32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,10 +2,31 @@ name: Continuous integration on: [push, pull_request] jobs: - ci: + tests: + name: Tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install fonttools + run: pip install fonttools==4.50 - uses: dtolnay/rust-toolchain@stable - run: cargo build --release + name: Build - run: cargo test --release + name: Run tests + + checks: + name: Check clippy, formatting, and documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@cargo-hack + - run: cargo clippy + - run: cargo fmt --check --all + - run: cargo doc --workspace --no-deps \ No newline at end of file diff --git a/.gitignore b/.gitignore index 46b4767a..dfae216e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,10 @@ desktop.ini # Rust /target bench/target -Cargo.lock +debug +.idea + +# Tests +tests/subsets +tests/ttx/*.otf +tests/ttx/*_ref.ttx diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..11c9fb5c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,410 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bytemuck" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cc" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cli" +version = "0.11.0" +dependencies = [ + "subsetter", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "font-types" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf6aa1de86490d8e39e04589bd04eb5953cc2a5ef0c25e389e807f44fd24e41" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "freetype-rs" +version = "0.36.0" +source = "git+https://github.com/LaurenzV/freetype-rs?rev=d27ea29#d27ea29fc1aa0bd73b2df120e83948036723170a" +dependencies = [ + "bitflags", + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "fuzz" +version = "0.11.0" +dependencies = [ + "freetype-rs", + "rand", + "rand_distr", + "rayon", + "skrifa", + "subsetter", + "ttf-parser", + "walkdir", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4749db2bd1c853db31a7ae5ee2fc6c30bbddce353ea8fedf673fed187c68c7" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "skrifa" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dbed6a0c9addb84c2c8f66f35f504bb875b901e2a022419173e6ee2adfd0fb4" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "subsetter" +version = "0.11.0" +dependencies = [ + "freetype-rs", + "skrifa", + "ttf-parser", +] + +[[package]] +name = "syn" +version = "2.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "git+https://github.com/RazrFalcon/ttf-parser?rev=9bbd432#9bbd432c2a41599dfbe2b572e72119b155083438" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml index 91f14fee..50b417ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,29 @@ -[package] -name = "subsetter" -version = "0.1.1" -authors = ["Laurenz "] +[workspace] +members = ["tests/cli", "tests/fuzz"] +resolver = "2" + +[workspace.package] +version = "0.11.0" +authors = ["Laurenz , Laurenz Stampfl "] edition = "2021" -description = "Reduces the size and coverage of OpenType fonts." repository = "https://github.com/typst/subsetter" readme = "README.md" license = "MIT OR Apache-2.0" + +[package] +name = "subsetter" +description = "Reduces the size and coverage of OpenType fonts." categories = ["compression", "encoding"] keywords = ["subsetting", "OpenType", "PDF"] exclude = ["fonts/*"] +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +readme = { workspace = true } +license = { workspace = true } [dev-dependencies] -ttf-parser = "0.15" +freetype-rs = { git = "https://github.com/LaurenzV/freetype-rs", rev = "d27ea29" } +skrifa = "0.19.1" +ttf-parser = { git = "https://github.com/RazrFalcon/ttf-parser", rev = "9bbd432" } diff --git a/NOTICE b/NOTICE index cedf390f..92813ce8 100644 --- a/NOTICE +++ b/NOTICE @@ -1,15 +1,75 @@ -Licenses for third party components used by this project can be found below. +# Code -================================================================================ -The SIL Open Font License Version 1.1 applies to: +ttf-parser +Code from ttf-parser was used/adapted for the following parts: +- Reader, Writer, LazyArray16 and LazyArray16Iter +- `name` table parsing. +- `hmtx` table parsing. +- Most of the `cff` parsing logic as well auxiliary structs and functions +such as `calc_subroutine_bias`, `SIDMetadata`, `CIDMetadata` `RealNumber`, +`Index`, `IndexSize`, `OffsetSize`, `FDSelect`, `ArgumentStack`, `Top Dict` and `FONT Dict`, +`DictionaryParser`. -* Noto fonts in fonts/Noto*.ttf - Copyright 2018 The Noto Project Authors - (github.com/googlei18n/noto-fonts) +The MIT license applies: -* Clicker Script fonts in fonts/ClickerScript*.ttf - Copyright (c) 2012 by Brian J. Bonislawsky and Jim Lyles DBA Astigmatic - (AOETI) (astigma@astigmatic.com), with Reserved Font Name "Mouse Memoirs" +Copyright (c) 2018 Yevhenii Reizner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +============================================================================== + +fonttools +Code from fonttools was used/adapted for the following parts: +- The `Decompiler` for charstrings. + +The MIT license applies: +MIT License + +Copyright (c) 2017 Just van Rossum + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +# Fonts + +The SIL OPEN FONT LICENSE Version 1.1 applies to the following fonts: +- fonts/ClickerScript-Regular.ttf +- fonts/MPLUS1p-Regular.ttf +- fonts/NotoSans-Regular.ttf +- fonts/NotoSansCJKsc-Bold-subset1.otf +- fonts/NotoSansCJKsc-Regular.otf +- fonts/Syne-Regular_subset.oft (Copyright 2017 The Syne Project Authors (https://gitlab.com/bonjour-monde/fonderie/syne-typeface)) ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 @@ -97,15 +157,112 @@ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. -================================================================================ -================================================================================ -The GUST Font License Version 1.0 applies to: +============================================================================== + +The Bitstream Vera Fonts license applies to the following fonts: +- fonts/DejaVuSansMono.ttf + +Bitstream Vera Fonts Copyright +——————————————— + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is +a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the fonts accompanying this license (“Fonts”) and associated +documentation files (the “Font Software”), to reproduce and distribute the +Font Software, including without limitation the rights to use, copy, merge, +publish, distribute, and/or sell copies of the Font Software, and to permit +persons to whom the Font Software is furnished to do so, subject to the +following conditions: + +The above copyright and trademark notices and this permission notice shall +be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular +the designs of glyphs or characters in the Fonts may be modified and +additional glyphs or characters may be added to the Fonts, only if the fonts +are renamed to names not containing either the words “Bitstream” or the word +“Vera”. + +This License becomes null and void to the extent applicable to Fonts or Font +Software that has been modified and is distributed under the “Bitstream +Vera” names. + +The Font Software may be sold as part of a larger software package but no +copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME +FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING +ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE +FONT SOFTWARE. -* Latin Modern fonts in fonts/LatinModern*.otf - (http://www.gust.org.pl/projects/e-foundry/lm-math) +Except as contained in this notice, the names of Gnome, the Gnome +Foundation, and Bitstream Inc., shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this Font Software +without prior written authorization from the Gnome Foundation or Bitstream +Inc., respectively. For further information, contact: fonts at gnome dot +org. -* NewComputerModern fonts in fonts/NewCM*.otf +Arev Fonts Copyright +——————————————— + +Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the fonts accompanying this license (“Fonts”) and +associated documentation files (the “Font Software”), to reproduce +and distribute the modifications to the Bitstream Vera Font Software, +including without limitation the rights to use, copy, merge, publish, +distribute, and/or sell copies of the Font Software, and to permit +persons to whom the Font Software is furnished to do so, subject to +the following conditions: + +The above copyright and trademark notices and this permission notice +shall be included in all copies of one or more of the Font Software +typefaces. + +The Font Software may be modified, altered, or added to, and in +particular the designs of glyphs or characters in the Fonts may be +modified and additional glyphs or characters may be added to the +Fonts, only if the fonts are renamed to names not containing either +the words “Tavmjong Bah” or the word “Arev”. + +This License becomes null and void to the extent applicable to Fonts +or Font Software that has been modified and is distributed under the +“Tavmjong Bah Arev” names. + +The Font Software may be sold as part of a larger software package but +no copy of one or more of the Font Software typefaces may be sold by +itself. + +THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL +TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the name of Tavmjong Bah shall not +be used in advertising or otherwise to promote the sale, use or other +dealings in this Font Software without prior written authorization +from Tavmjong Bah. For further information, contact: tavmjong @ free +. fr. + +============================================================================== + +The GUST font license applies to the following fonts: +- fonts/LatinModernRoman-Regular.otf +- fonts/NewCMMath-Regular.otf % This is version 1.0, dated 22 June 2009, of the GUST Font License. % (GUST is the Polish TeX Users Group, http://www.gust.org.pl) @@ -135,4 +292,238 @@ The GUST Font License Version 1.0 applies to: % The latest version of the LaTeX Project Public License is in % http://www.latex-project.org/lppl.txt and version 1.3c or later % is part of all distributions of LaTeX version 2006/05/20 or later. -================================================================================ + +============================================================================== + +The Apache 2.0 license applies to the following fonts: +- fonts/Roboto-Regular.ttf + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +============================================================================== + +The MIT license applies to the following fonts: +- fonts/TestTTC.ttc + +MIT License + +Copyright (c) 2017 Just van Rossum + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index a7870c3b..5472c0f0 100644 --- a/README.md +++ b/README.md @@ -2,53 +2,30 @@ [![Crates.io](https://img.shields.io/crates/v/subsetter.svg)](https://crates.io/crates/subsetter) [![Documentation](https://docs.rs/subsetter/badge.svg)](https://docs.rs/subsetter) -Reduces the size and coverage of OpenType fonts with TrueType or CFF outlines. +Reduces the size and coverage of OpenType fonts with TrueType or CFF outlines for embedding +in PDFs. You can in general expect very good results in terms of font size, as most of the things +that can be subsetted are also subsetted. -```toml -[dependencies] -subsetter = "0.1" -``` +# Scope +**Note that the resulting font subsets will most likely be unusable in any other contexts than PDF writing, +since a lot of information will be removed from the font which is not necessary in PDFs, but is +necessary in other contexts.** This is on purpose, and for now, there are no plans to expand the +scope of this crate to become a general purpose subsetter, as this is a massive undertaking and +will make the already complex codebase even more complex. -## Example -In the example below, we remove all glyphs except the ones with IDs 68, 69, 70. -Those correspond to the letters 'a', 'b' and 'c'. +In the future, +[klippa](https://github.com/googlefonts/fontations/tree/main/klippa) will hopefully fill this gap. -```rust -use subsetter::{subset, Profile}; - -// Read the raw font data. -let data = std::fs::read("fonts/NotoSans-Regular.ttf")?; - -// Keep only three glyphs and the OpenType tables -// required for embedding the font in a PDF file. -let glyphs = &[68, 69, 70]; -let profile = Profile::pdf(glyphs); -let sub = subset(&data, 0, profile)?; - -// Write the resulting file. -std::fs::write("target/Noto-Small.ttf", sub)?; -``` - -Notably, this subsetter does not really remove glyphs, just their outlines. This -means that you don't have to worry about changed glyphs IDs. However, it also -means that the resulting font won't always be as small as possible. To somewhat -remedy this, this crate sometimes at least zeroes out unused data that it cannot -fully remove. This helps if the font gets compressed, for example when embedding -it in a PDF file. - -In the above example, the original font was 375 KB (188 KB zipped) while the -resulting font is 36 KB (5 KB zipped). +For an example on how to use this crate, have a look at the +[documentation](https://docs.rs/subsetter/latest/subsetter/). ## Limitations -Currently, the library only subsets static outline fonts. Furthermore, it is -designed for use cases where text was already mapped to glyphs. Possible future -work includes: - -- The option to pass variation coordinates which would make the subsetter create - a static instance of a variable font. -- Subsetting of bitmap, color and SVG tables. -- A profile which takes a char set instead of a glyph set and subsets the - layout tables. +As mentioned above, this crate is specifically aimed at subsetting a font with the purpose of +including it in a PDF file. For any other purposes, this crate will most likely not be very useful. + +Potential future work could include allowing to define variation coordinates for which to generate +the subset for. However, apart from that there are no plans to increase the scope of this crate, apart from +fixing bugs and adding new APIs to the existing interface. ## Safety and Dependencies This crate forbids unsafe code and has zero dependencies. diff --git a/fonts/DejaVuSansMono.ttf b/fonts/DejaVuSansMono.ttf new file mode 100644 index 00000000..f5786022 Binary files /dev/null and b/fonts/DejaVuSansMono.ttf differ diff --git a/fonts/MPLUS1p-Regular.ttf b/fonts/MPLUS1p-Regular.ttf new file mode 100644 index 00000000..85827c56 Binary files /dev/null and b/fonts/MPLUS1p-Regular.ttf differ diff --git a/fonts/NewCMMath-Regular.otf b/fonts/NewCMMath-Regular.otf index 82221fb8..9da2a191 100644 Binary files a/fonts/NewCMMath-Regular.otf and b/fonts/NewCMMath-Regular.otf differ diff --git a/fonts/NotoSans-Regular.ttf b/fonts/NotoSans-Regular.ttf index 59cc882f..7552fbe8 100644 Binary files a/fonts/NotoSans-Regular.ttf and b/fonts/NotoSans-Regular.ttf differ diff --git a/fonts/NotoSansCJKsc-Bold-subset1.otf b/fonts/NotoSansCJKsc-Bold-subset1.otf new file mode 100644 index 00000000..f6f4eac5 Binary files /dev/null and b/fonts/NotoSansCJKsc-Bold-subset1.otf differ diff --git a/fonts/Roboto-Regular.ttf b/fonts/Roboto-Regular.ttf new file mode 100644 index 00000000..ddf4bfac Binary files /dev/null and b/fonts/Roboto-Regular.ttf differ diff --git a/fonts/Syne-Regular_subset.otf b/fonts/Syne-Regular_subset.otf new file mode 100644 index 00000000..ee443cc0 Binary files /dev/null and b/fonts/Syne-Regular_subset.otf differ diff --git a/fonts/TestTTC.ttc b/fonts/TestTTC.ttc new file mode 100644 index 00000000..a21fe89d Binary files /dev/null and b/fonts/TestTTC.ttc differ diff --git a/src/cff/argstack.rs b/src/cff/argstack.rs new file mode 100644 index 00000000..3d3ee1de --- /dev/null +++ b/src/cff/argstack.rs @@ -0,0 +1,44 @@ +use crate::cff::number::Number; +use crate::Error::CFFError; +use crate::Result; + +const MAX_OPERANDS_LEN: usize = 48; + +// Taken from ttf-parser. +/// TODO: Use array instead? +pub struct ArgumentsStack { + pub data: Vec, +} + +impl ArgumentsStack { + pub fn new() -> Self { + Self { data: vec![] } + } + + #[inline] + pub fn len(&self) -> usize { + self.data.len() + } + + #[inline] + pub fn push(&mut self, n: Number) -> Result<()> { + if self.len() == MAX_OPERANDS_LEN { + Err(CFFError) + } else { + self.data.push(n); + Ok(()) + } + } + + #[inline] + pub fn pop(&mut self) -> Option { + self.data.pop() + } + + #[inline] + pub fn pop_all(&mut self) -> Vec { + let mut ret_vec = vec![]; + std::mem::swap(&mut self.data, &mut ret_vec); + ret_vec + } +} diff --git a/src/cff/charset.rs b/src/cff/charset.rs new file mode 100644 index 00000000..21abccb2 --- /dev/null +++ b/src/cff/charset.rs @@ -0,0 +1,23 @@ +use crate::write::Writer; +use crate::GlyphRemapper; +use crate::Result; + +/// Rewrite the charset of the font. We do not perserve the CID's from the original font. Instead, +/// we assign each glyph it's original glyph as the CID. This makes it easier to reference them +/// from the PDF, since we know the CID a glyph will have before it's subsetted. +pub fn rewrite_charset(gid_mapper: &GlyphRemapper, w: &mut Writer) -> Result<()> { + if gid_mapper.num_gids() == 1 { + // We only have .notdef, so use format 0. + w.write::(0); + } else { + // Use format 2. + w.write::(2); + + w.write::(1); + // -2 because -1 for not including .notdef and -1 since the first glyph + // in the range is not counted. + w.write::(gid_mapper.num_gids() - 2); + } + + Ok(()) +} diff --git a/src/cff/charstring.rs b/src/cff/charstring.rs new file mode 100644 index 00000000..a6f6efa5 --- /dev/null +++ b/src/cff/charstring.rs @@ -0,0 +1,267 @@ +use crate::cff::argstack::ArgumentsStack; +use crate::cff::number::Number; +use crate::cff::operator::Operator; +use crate::cff::subroutines::SubroutineHandler; +use crate::read::Reader; +use crate::write::Writer; +use crate::Error::{CFFError, Unimplemented}; +use crate::{Error, Result}; +use operators::*; +use std::fmt::{Debug, Formatter}; + +pub type CharString<'a> = &'a [u8]; + +// Adapted from fonttools. +/// A charstring decompiler. +pub struct Decompiler<'a> { + gsubr_handler: SubroutineHandler<'a>, + lsubr_handler: SubroutineHandler<'a>, + stack: ArgumentsStack, + hint_count: u16, + hint_mask_bytes: u16, +} + +impl<'a> Decompiler<'a> { + /// Create a new charstring decompiler. + pub fn new( + gsubr_handler: SubroutineHandler<'a>, + lsubr_handler: SubroutineHandler<'a>, + ) -> Self { + Self { + gsubr_handler, + lsubr_handler, + stack: ArgumentsStack::new(), + hint_count: 0, + hint_mask_bytes: 0, + } + } + + /// Decompile a charstring with the given decompiler. + pub fn decompile(mut self, charstring: CharString<'a>) -> Result> { + let mut program = Program::default(); + self.decompile_inner(charstring, &mut program, 1)?; + Ok(program) + } + + fn decompile_inner( + &mut self, + charstring: CharString<'a>, + program: &mut Program<'a>, + depth: u8, + ) -> Result<()> { + if depth > 64 { + return Err(CFFError); + } + + let mut r = Reader::new(charstring); + + while !r.at_end() { + // We need to peak instead of read because parsing a number requires + // access to the whole buffer. + let op = r.peak::().ok_or(Error::CFFError)?; + + // Numbers + if matches!(op, 28 | 32..=255) { + let number = Number::parse_char_string_number(&mut r).ok_or(CFFError)?; + self.stack.push(number)?; + program.push(Instruction::Operand(number)); + continue; + } + + // No numbers can appear now, so now we can actually read it. + let op = r.read::().ok_or(CFFError)?; + let operator = if op == 12 { + Operator::from_two_byte(r.read::().ok_or(CFFError)?) + } else { + Operator::from_one_byte(op) + }; + + match operator { + HFLEX | FLEX | HFLEX1 | FLEX1 => { + self.stack.pop_all(); + program.push(Instruction::Operator(operator)); + } + HORIZONTAL_STEM + | VERTICAL_STEM + | HORIZONTAL_STEM_HINT_MASK + | VERTICAL_STEM_HINT_MASK => { + self.count_hints(); + program.push(Instruction::Operator(operator)); + } + VERTICAL_MOVE_TO | HORIZONTAL_MOVE_TO | LINE_TO | VERTICAL_LINE_TO + | HORIZONTAL_LINE_TO | MOVE_TO | CURVE_LINE | LINE_CURVE + | VV_CURVE_TO | VH_CURVE_TO | HH_CURVE_TO | HV_CURVE_TO | CURVE_TO => { + self.stack.pop_all(); + program.push(Instruction::Operator(operator)); + } + RETURN => { + // Don't do anything for return, since we desubroutinize. + } + CALL_GLOBAL_SUBROUTINE => { + // Pop the subroutine index from the program. + program.0.pop(); + + let biased_index = + self.stack.pop().and_then(|n| n.as_i32()).ok_or(CFFError)?; + let gsubr = self + .gsubr_handler + .get_with_biased(biased_index) + .ok_or(CFFError)?; + self.decompile_inner(gsubr, program, depth + 1)?; + } + CALL_LOCAL_SUBROUTINE => { + // Pop the subroutine index from the program. + program.0.pop(); + + let biased_index = + self.stack.pop().and_then(|n| n.as_i32()).ok_or(CFFError)?; + let lsubr = self + .lsubr_handler + .get_with_biased(biased_index) + .ok_or(CFFError)?; + self.decompile_inner(lsubr, program, depth + 1)?; + } + HINT_MASK | COUNTER_MASK => { + program.push(Instruction::Operator(operator)); + if self.hint_mask_bytes == 0 { + // Hintmask can contain implicit stems. + self.count_hints(); + self.hint_mask_bytes = (self.hint_count + 7) / 8; + } + + let hint_bytes = + r.read_bytes(self.hint_mask_bytes as usize).ok_or(CFFError)?; + program.push(Instruction::HintMask(hint_bytes)); + } + ENDCHAR => { + // We don't support seac for now. It's a deprecated feature and Typst for some + // reason does not support it anyway. + if self.stack.len() >= 4 { + return Err(Unimplemented); + } + + program.push(Instruction::Operator(operator)); + } + _ => { + return Err(CFFError); + } + } + } + + Ok(()) + } + + fn count_hints(&mut self) { + let elements = self.stack.pop_all(); + self.hint_count += elements.len() as u16 / 2; + } +} + +/// A type of instruction in a charstring program. +#[derive(Debug)] +pub enum Instruction<'a> { + Operand(Number), + Operator(Operator), + HintMask(&'a [u8]), +} + +/// A charstring program, decompiled into its constituent instructions. +#[derive(Default)] +pub struct Program<'a>(Vec>); + +impl Debug for Program<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut formatted_strings = vec![]; + let mut str_buffer = vec![]; + + for instr in &self.0 { + match instr { + Instruction::Operand(op) => str_buffer.push(format!("{}", op.as_f64())), + Instruction::Operator(op) => { + str_buffer.push(format!("op({})", op)); + + if *op != HINT_MASK && *op != COUNTER_MASK { + formatted_strings.push(str_buffer.join(" ")); + str_buffer.clear(); + } + } + Instruction::HintMask(bytes) => { + let mut byte_string = String::new(); + + for byte in *bytes { + byte_string.push_str(&format!("{:08b}", *byte)); + } + + str_buffer.push(byte_string); + formatted_strings.push(str_buffer.join(" ")); + str_buffer.clear(); + } + } + } + + write!(f, "{}", formatted_strings.join("\n")) + } +} + +impl<'a> Program<'a> { + /// Push a new instruction to the program. + pub fn push(&mut self, instruction: Instruction<'a>) { + self.0.push(instruction); + } + + /// Compile the program. + pub fn compile(&self) -> Vec { + let mut w = Writer::new(); + + for instr in &self.0 { + match instr { + Instruction::Operand(num) => { + w.write(num); + } + Instruction::Operator(op) => { + w.write(op); + } + Instruction::HintMask(hm) => { + w.write(hm); + } + } + } + + w.finish() + } +} + +#[allow(dead_code)] +pub mod operators { + use crate::cff::operator::Operator; + + pub const HORIZONTAL_STEM: Operator = Operator::from_one_byte(1); + pub const VERTICAL_STEM: Operator = Operator::from_one_byte(3); + pub const VERTICAL_MOVE_TO: Operator = Operator::from_one_byte(4); + pub const LINE_TO: Operator = Operator::from_one_byte(5); + pub const HORIZONTAL_LINE_TO: Operator = Operator::from_one_byte(6); + pub const VERTICAL_LINE_TO: Operator = Operator::from_one_byte(7); + pub const CURVE_TO: Operator = Operator::from_one_byte(8); + pub const CALL_LOCAL_SUBROUTINE: Operator = Operator::from_one_byte(10); + pub const RETURN: Operator = Operator::from_one_byte(11); + pub const ENDCHAR: Operator = Operator::from_one_byte(14); + pub const HORIZONTAL_STEM_HINT_MASK: Operator = Operator::from_one_byte(18); + pub const HINT_MASK: Operator = Operator::from_one_byte(19); + pub const COUNTER_MASK: Operator = Operator::from_one_byte(20); + pub const MOVE_TO: Operator = Operator::from_one_byte(21); + pub const HORIZONTAL_MOVE_TO: Operator = Operator::from_one_byte(22); + pub const VERTICAL_STEM_HINT_MASK: Operator = Operator::from_one_byte(23); + pub const CURVE_LINE: Operator = Operator::from_one_byte(24); + pub const LINE_CURVE: Operator = Operator::from_one_byte(25); + pub const VV_CURVE_TO: Operator = Operator::from_one_byte(26); + pub const HH_CURVE_TO: Operator = Operator::from_one_byte(27); + pub const SHORT_INT: Operator = Operator::from_one_byte(28); + pub const CALL_GLOBAL_SUBROUTINE: Operator = Operator::from_one_byte(29); + pub const VH_CURVE_TO: Operator = Operator::from_one_byte(30); + pub const HV_CURVE_TO: Operator = Operator::from_one_byte(31); + pub const HFLEX: Operator = Operator::from_two_byte(34); + pub const FLEX: Operator = Operator::from_two_byte(35); + pub const HFLEX1: Operator = Operator::from_two_byte(36); + pub const FLEX1: Operator = Operator::from_two_byte(37); + pub const FIXED_16_16: Operator = Operator::from_one_byte(255); +} diff --git a/src/cff/cid_font.rs b/src/cff/cid_font.rs new file mode 100644 index 00000000..a4f9775e --- /dev/null +++ b/src/cff/cid_font.rs @@ -0,0 +1,129 @@ +use crate::cff::dict::font_dict; +use crate::cff::dict::font_dict::FontDict; +use crate::cff::dict::top_dict::TopDictData; +use crate::cff::index::{parse_index, Index}; +use crate::cff::remapper::FontDictRemapper; +use crate::read::{LazyArray16, Reader}; +use crate::write::Writer; +use crate::Error::{MalformedFont, SubsetError}; +use crate::GlyphRemapper; +use crate::Result; + +// The parsing logic was taken from ttf-parser. + +/// Parse CID metadata from a font. +pub fn parse_cid_metadata<'a>( + data: &'a [u8], + top_dict: &TopDictData, + number_of_glyphs: u16, +) -> Option> { + let (fd_array_offset, fd_select_offset) = + match (top_dict.fd_array, top_dict.fd_select) { + (Some(a), Some(b)) => (a, b), + _ => return None, + }; + + let mut metadata = CIDMetadata { + fd_array: { + let mut r = Reader::new_at(data, fd_array_offset); + parse_index::(&mut r)? + }, + fd_select: { + let mut s = Reader::new_at(data, fd_select_offset); + parse_fd_select(number_of_glyphs, &mut s)? + }, + ..CIDMetadata::default() + }; + + for font_dict_data in metadata.fd_array { + metadata + .font_dicts + .push(font_dict::parse_font_dict(data, font_dict_data)?); + } + + Some(metadata) +} + +fn parse_fd_select<'a>( + number_of_glyphs: u16, + r: &mut Reader<'a>, +) -> Option> { + let format = r.read::()?; + match format { + 0 => Some(FDSelect::Format0(r.read_array16::(number_of_glyphs)?)), + 3 => Some(FDSelect::Format3(r.tail()?)), + _ => None, + } +} + +/// Metadata necessary for processing CID-keyed fonts. +#[derive(Clone, Default, Debug)] +pub struct CIDMetadata<'a> { + pub font_dicts: Vec>, + pub fd_array: Index<'a>, + pub fd_select: FDSelect<'a>, +} + +#[derive(Clone, Copy, Debug)] +pub enum FDSelect<'a> { + Format0(LazyArray16<'a, u8>), + Format3(&'a [u8]), +} + +impl Default for FDSelect<'_> { + fn default() -> Self { + FDSelect::Format0(LazyArray16::default()) + } +} + +impl FDSelect<'_> { + /// Get the font dict index for a glyph. + pub fn font_dict_index(&self, glyph_id: u16) -> Option { + match self { + FDSelect::Format0(ref array) => array.get(glyph_id), + FDSelect::Format3(data) => { + let mut r = Reader::new(data); + let number_of_ranges = r.read::()?; + if number_of_ranges == 0 { + return None; + } + + let number_of_ranges = number_of_ranges.checked_add(1)?; + + let mut prev_first_glyph = r.read::()?; + let mut prev_index = r.read::()?; + for _ in 1..number_of_ranges { + let curr_first_glyph = r.read::()?; + if (prev_first_glyph..curr_first_glyph).contains(&glyph_id) { + return Some(prev_index); + } else { + prev_index = r.read::()?; + } + + prev_first_glyph = curr_first_glyph; + } + + None + } + } + } +} + +/// Rewrite the FD INDEX for CID-keyed font. +pub fn rewrite_fd_index( + gid_remapper: &GlyphRemapper, + fd_select: FDSelect, + fd_remapper: &FontDictRemapper, + w: &mut Writer, +) -> Result<()> { + // We always use format 0, since it's the simplest. + w.write::(0); + + for gid in gid_remapper.remapped_gids() { + let old_fd = fd_select.font_dict_index(gid).ok_or(MalformedFont)?; + let new_fd = fd_remapper.get(old_fd).ok_or(SubsetError)?; + w.write(new_fd); + } + + Ok(()) +} diff --git a/src/cff/dict.rs b/src/cff/dict.rs deleted file mode 100644 index be613655..00000000 --- a/src/cff/dict.rs +++ /dev/null @@ -1,297 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::ops::Range; - -use crate::{Error, Reader, Result, Structure, Writer}; - -/// A DICT data structure. -#[derive(Clone)] -pub struct Dict<'a>(Vec>); - -impl<'a> Dict<'a> { - pub fn get(&self, op: Op) -> Option<&[Operand<'a>]> { - self.0 - .iter() - .find(|pair| pair.op == op) - .map(|pair| pair.operands.as_slice()) - } - - pub fn get_offset(&self, op: Op) -> Option { - match self.get(op)? { - &[Operand::Int(offset)] if offset > 0 => Some(offset as usize), - _ => None, - } - } - - pub fn get_range(&self, op: Op) -> Option> { - match self.get(op)? { - &[Operand::Int(len), Operand::Int(offset)] if offset > 0 => { - let offset = usize::try_from(offset).ok()?; - let len = usize::try_from(len).ok()?; - Some(offset..offset + len) - } - _ => None, - } - } - - pub fn retain(&mut self, ops: &[Op]) { - self.0.retain(|pair| ops.contains(&pair.op)); - } - - pub fn set(&mut self, op: Op, operands: Vec>) { - if let Some(pair) = self.0.iter_mut().find(|pair| pair.op == op) { - pair.operands = operands; - } else { - self.0.push(Pair { operands, op }); - } - } - - pub fn set_offset(&mut self, op: Op, offset: usize) { - self.set(op, vec![Operand::Offset(offset)]); - } - - pub fn set_range(&mut self, op: Op, range: &Range) { - self.set( - op, - vec![Operand::Offset(range.end - range.start), Operand::Offset(range.start)], - ); - } -} - -impl<'a> Structure<'a> for Dict<'a> { - fn read(r: &mut Reader<'a>) -> Result { - let mut pairs = vec![]; - while !r.eof() { - pairs.push(r.read::()?); - } - Ok(Self(pairs)) - } - - fn write(&self, w: &mut Writer) { - for pair in &self.0 { - w.write_ref::(pair); - } - } -} - -impl Debug for Dict<'_> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_list().entries(self.0.iter()).finish() - } -} - -/// An operand-operator pair in a DICT. -#[derive(Clone)] -struct Pair<'a> { - operands: Vec>, - op: Op, -} - -impl<'a> Structure<'a> for Pair<'a> { - fn read(r: &mut Reader<'a>) -> Result { - let mut operands = vec![]; - loop { - match r.data().first().ok_or(Error::MissingData)? { - 0..=21 => break, - 28..=30 | 32..=254 => operands.push(r.read::()?), - _ => r.skip(1)?, - } - } - Ok(Self { operands, op: r.read::()? }) - } - - fn write(&self, w: &mut Writer) { - for operand in &self.operands { - w.write_ref::(operand); - } - w.write::(self.op); - } -} - -impl Debug for Pair<'_> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{:?}: {:?}", self.op, self.operands) - } -} - -/// An operator in a DICT. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct Op(u8, u8); - -impl Structure<'_> for Op { - fn read(r: &mut Reader) -> Result { - let b0 = r.read::()?; - match b0 { - 12 => Ok(Self(b0, r.read::()?)), - 0..=21 => Ok(Self(b0, 0)), - _ => panic!("cannot read operator here"), - } - } - - fn write(&self, w: &mut Writer) { - w.write::(self.0); - if self.0 == 12 { - w.write::(self.1); - } - } -} - -/// An operand in a DICT. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum Operand<'a> { - Int(i32), - Offset(usize), - Real(&'a [u8]), -} - -impl<'a> Structure<'a> for Operand<'a> { - fn read(r: &mut Reader<'a>) -> Result { - let b0 = i32::from(r.read::()?); - Ok(match b0 { - 28 => Self::Int(i32::from(r.read::()?)), - 29 => Self::Int(r.read::()?), - 30 => { - let mut len = 0; - for &byte in r.data() { - len += 1; - if byte & 0x0f == 0x0f { - break; - } - } - Self::Real(r.take(len)?) - } - 32..=246 => Self::Int(b0 - 139), - 247..=250 => { - let b1 = i32::from(r.read::()?); - Self::Int((b0 - 247) * 256 + b1 + 108) - } - 251..=254 => { - let b1 = i32::from(r.read::()?); - Self::Int(-(b0 - 251) * 256 - b1 - 108) - } - _ => panic!("cannot read operand here"), - }) - } - - fn write(&self, w: &mut Writer) { - match self { - Self::Int(int) => { - // TODO: Select most compact encoding. - w.write::(29); - w.write::(*int); - } - Self::Offset(offset) => { - w.write::(29); - w.write::(*offset as i32); - } - Self::Real(real) => { - w.write::(30); - w.give(real); - } - } - } -} - -/// Top DICT operators. -pub mod top { - use super::Op; - - pub const KEEP: &[Op] = &[ - ROS, - CID_FONT_VERSION, - CID_FONT_REVISION, - CID_FONT_TYPE, - CID_COUNT, - FONT_NAME, - VERSION, - NOTICE, - COPYRIGHT, - FULL_NAME, - FAMILY_NAME, - WEIGHT, - IS_FIXED_PITCH, - ITALIC_ANGLE, - UNDERLINE_POSITION, - UNDERLINE_THICKNESS, - PAINT_TYPE, - CHARSTRING_TYPE, - FONT_MATRIX, - FONT_BBOX, - STROKE_WIDTH, - POST_SCRIPT, - ]; - - pub const VERSION: Op = Op(0, 0); - pub const NOTICE: Op = Op(1, 0); - pub const COPYRIGHT: Op = Op(12, 0); - pub const FULL_NAME: Op = Op(2, 0); - pub const FAMILY_NAME: Op = Op(3, 0); - pub const WEIGHT: Op = Op(4, 0); - pub const IS_FIXED_PITCH: Op = Op(12, 1); - pub const ITALIC_ANGLE: Op = Op(12, 2); - pub const UNDERLINE_POSITION: Op = Op(12, 3); - pub const UNDERLINE_THICKNESS: Op = Op(12, 4); - pub const PAINT_TYPE: Op = Op(12, 5); - pub const CHARSTRING_TYPE: Op = Op(12, 6); - pub const FONT_MATRIX: Op = Op(12, 7); - pub const FONT_BBOX: Op = Op(5, 0); - pub const STROKE_WIDTH: Op = Op(12, 8); - pub const CHARSET: Op = Op(15, 0); - pub const ENCODING: Op = Op(16, 0); - pub const CHAR_STRINGS: Op = Op(17, 0); - pub const PRIVATE: Op = Op(18, 0); - pub const POST_SCRIPT: Op = Op(12, 21); - - // CID-keyed fonts. - pub const ROS: Op = Op(12, 30); - pub const CID_FONT_VERSION: Op = Op(12, 31); - pub const CID_FONT_REVISION: Op = Op(12, 32); - pub const CID_FONT_TYPE: Op = Op(12, 33); - pub const CID_COUNT: Op = Op(12, 34); - pub const FD_ARRAY: Op = Op(12, 36); - pub const FD_SELECT: Op = Op(12, 37); - pub const FONT_NAME: Op = Op(12, 38); -} - -/// Private DICT operators. -pub mod private { - use super::Op; - - pub const KEEP: &[Op] = &[ - BLUE_VALUES, - OTHER_BLUES, - FAMILY_BLUES, - FAMILY_OTHER_BLUES, - BLUE_SCALE, - BLUE_SHIFT, - BLUE_FUZZ, - STD_HW, - STD_VW, - STEM_SNAP_H, - STEM_SNAP_V, - FORCE_BOLD, - LANGUAGE_GROUP, - EXPANSION_FACTOR, - INITIAL_RANDOM_SEED, - DEFAULT_WIDTH_X, - NOMINAL_WIDTH_X, - ]; - - pub const BLUE_VALUES: Op = Op(6, 0); - pub const OTHER_BLUES: Op = Op(7, 0); - pub const FAMILY_BLUES: Op = Op(8, 0); - pub const FAMILY_OTHER_BLUES: Op = Op(9, 0); - pub const BLUE_SCALE: Op = Op(12, 9); - pub const BLUE_SHIFT: Op = Op(12, 10); - pub const BLUE_FUZZ: Op = Op(12, 11); - pub const STD_HW: Op = Op(10, 0); - pub const STD_VW: Op = Op(11, 0); - pub const STEM_SNAP_H: Op = Op(12, 12); - pub const STEM_SNAP_V: Op = Op(12, 13); - pub const FORCE_BOLD: Op = Op(12, 14); - pub const LANGUAGE_GROUP: Op = Op(12, 17); - pub const EXPANSION_FACTOR: Op = Op(12, 18); - pub const INITIAL_RANDOM_SEED: Op = Op(12, 19); - pub const SUBRS: Op = Op(19, 0); - pub const DEFAULT_WIDTH_X: Op = Op(20, 0); - pub const NOMINAL_WIDTH_X: Op = Op(21, 0); -} diff --git a/src/cff/dict/font_dict.rs b/src/cff/dict/font_dict.rs new file mode 100644 index 00000000..bd8a9d2f --- /dev/null +++ b/src/cff/dict/font_dict.rs @@ -0,0 +1,165 @@ +use crate::cff::cid_font::CIDMetadata; +use crate::cff::dict::operators::*; +use crate::cff::dict::private_dict::parse_subr_offset; +use crate::cff::dict::DictionaryParser; +use crate::cff::index::{create_index, parse_index, Index}; +use crate::cff::number::{Number, StringId}; +use crate::cff::remapper::{FontDictRemapper, SidRemapper}; +use crate::cff::Offsets; +use crate::read::Reader; +use crate::write::Writer; +use crate::Error::SubsetError; +use crate::Result; +use std::array; + +// The parsing logic was adapted from ttf-parser. + +/// A font DICT. +#[derive(Default, Clone, Debug)] +pub struct FontDict<'a> { + /// The local subroutines that are linked in the font DICT. + pub local_subrs: Index<'a>, + /// The underlying data of the private dict. + pub private_dict: &'a [u8], + /// The StringID of the font name in this font DICT. + pub font_name: Option, + /// The font matrix. + pub font_matrix: Option<[Number; 6]>, +} + +pub fn parse_font_dict<'a>( + font_data: &'a [u8], + font_dict_data: &[u8], +) -> Option> { + let mut font_dict = FontDict::default(); + + let mut operands_buffer: [Number; 48] = array::from_fn(|_| Number::zero()); + let mut dict_parser = DictionaryParser::new(font_dict_data, &mut operands_buffer); + while let Some(operator) = dict_parser.parse_next() { + match operator { + PRIVATE => { + let private_dict_range = dict_parser.parse_range()?; + let private_dict_data = font_data.get(private_dict_range.clone())?; + font_dict.private_dict = private_dict_data; + font_dict.local_subrs = { + if let Some(subrs_offset) = parse_subr_offset(private_dict_data) { + let start = private_dict_range.start.checked_add(subrs_offset)?; + let subrs_data = font_data.get(start..)?; + let mut r = Reader::new(subrs_data); + parse_index::(&mut r)? + } else { + Index::default() + } + }; + } + FONT_NAME => font_dict.font_name = Some(dict_parser.parse_sid()?), + FONT_MATRIX => font_dict.font_matrix = Some(dict_parser.parse_font_matrix()?), + _ => {} + } + } + + Some(font_dict) +} + +/// Rewrite the font DICT INDEX for CID-keyed fonts. +pub fn rewrite_font_dict_index( + fd_remapper: &FontDictRemapper, + sid_remapper: &SidRemapper, + offsets: &mut Offsets, + metadata: &CIDMetadata, + w: &mut Writer, +) -> Result<()> { + let mut dicts = vec![]; + + for (new_df, old_df) in fd_remapper.sorted_iter().enumerate() { + let new_df = new_df as u8; + + let dict = metadata.font_dicts.get(old_df as usize).ok_or(SubsetError)?; + let mut w = Writer::new(); + + // See comment in `generate_font_dict_index`. + w.write(dict.font_matrix.as_ref().unwrap_or(&[ + Number::one(), + Number::zero(), + Number::zero(), + Number::one(), + Number::zero(), + Number::zero(), + ])); + w.write(FONT_MATRIX); + + // Write the length and offset of the private dict. + // Private dicts have already been written, so the offsets are already correct. + // This means that these two offsets are a bit special compared to the others, since + // we never use the `location` field of the offset and we don't overwrite it like we do + // for the others. + offsets + .private_dicts_lens + .get(new_df as usize) + .ok_or(SubsetError)? + .value + .write_as_5_bytes(&mut w); + + offsets + .private_dicts_offsets + .get_mut(new_df as usize) + .ok_or(SubsetError)? + .value + .write_as_5_bytes(&mut w); + w.write(PRIVATE); + + if let Some(font_name) = dict.font_name.and_then(|s| sid_remapper.get_new_sid(s)) + { + w.write(Number::from_i32(font_name.0 as i32)); + w.write(FONT_NAME); + } + + dicts.push(w.finish()); + } + + w.write(create_index(dicts)?); + + Ok(()) +} + +/// Generate a new font DICT INDEX for SID-keyed fonts. +pub fn generate_font_dict_index(offsets: &mut Offsets, w: &mut Writer) -> Result<()> { + let mut sub_w = Writer::new(); + + // Similarly to ghostscript, write a default font matrix. Fixes issues for some printers + // https://leahneukirchen.org/blog/archive/2022/10/50-blank-pages-or-black-box-debugging-of-pdf-rendering-in-printers.html + sub_w.write([ + Number::one(), + Number::zero(), + Number::zero(), + Number::one(), + Number::zero(), + Number::zero(), + ]); + sub_w.write(FONT_MATRIX); + + // Write the length and offset of the private dict. + // Private dicts have already been written, so the offsets are already correct. + // This means that these two offsets are a bit special compared to the others, since + // we never use the `location` field of the offset and we don't overwrite it like we do + // for the others. + offsets + .private_dicts_lens + .first() + .ok_or(SubsetError)? + .value + .write_as_5_bytes(&mut sub_w); + + offsets + .private_dicts_offsets + .first_mut() + .ok_or(SubsetError)? + .value + .write_as_5_bytes(&mut sub_w); + + sub_w.write(PRIVATE); + w.write(create_index(vec![sub_w.finish()])?); + + // TODO: Maybe write a font name as well? But shouldn't matter. + Ok(()) +} diff --git a/src/cff/dict/mod.rs b/src/cff/dict/mod.rs new file mode 100644 index 00000000..465b0269 --- /dev/null +++ b/src/cff/dict/mod.rs @@ -0,0 +1,182 @@ +pub(crate) mod font_dict; +pub(crate) mod private_dict; +pub(crate) mod top_dict; + +// The `DictionaryParser` was taken from ttf-parser. + +use crate::cff::number::{Number, StringId}; +use crate::cff::operator::{Operator, TWO_BYTE_OPERATOR_MARK}; +use crate::read::Reader; +use std::ops::Range; + +pub struct DictionaryParser<'a> { + data: &'a [u8], + offset: usize, + operands_offset: usize, + operands: &'a mut [Number], + operands_len: u16, +} + +impl<'a> DictionaryParser<'a> { + pub fn new(data: &'a [u8], operands_buffer: &'a mut [Number]) -> Self { + DictionaryParser { + data, + offset: 0, + operands_offset: 0, + operands: operands_buffer, + operands_len: 0, + } + } + + pub fn parse_next(&mut self) -> Option { + let mut r = Reader::new_at(self.data, self.offset); + self.operands_offset = self.offset; + while !r.at_end() { + // 0..=21 bytes are operators. + if is_dict_one_byte_op(r.peak::()?) { + let b = r.read::()?; + let mut operator = Operator::from_one_byte(b); + + if b == TWO_BYTE_OPERATOR_MARK { + operator = Operator::from_two_byte(r.read::()?); + } + + self.offset = r.offset(); + return Some(operator); + } else { + let _ = Number::parse_cff_number(&mut r)?; + } + } + + None + } + + pub fn parse_operands(&mut self) -> Option<()> { + let mut r = Reader::new_at(self.data, self.operands_offset); + self.operands_len = 0; + while !r.at_end() { + let b = r.peak::()?; + // 0..=21 bytes are operators. + if is_dict_one_byte_op(b) { + r.read::()?; + break; + } else { + let op = Number::parse_cff_number(&mut r)?; + self.operands[usize::from(self.operands_len)] = op; + self.operands_len += 1; + + if usize::from(self.operands_len) >= self.operands.len() { + break; + } + } + } + + Some(()) + } + + pub fn operands(&self) -> &[Number] { + &self.operands[..usize::from(self.operands_len)] + } + + pub fn parse_sid(&mut self) -> Option { + self.parse_operands()?; + let operands = self.operands(); + if operands.len() == 1 { + Some(StringId(u16::try_from(operands[0].as_i32()?).ok()?)) + } else { + None + } + } + + pub fn parse_offset(&mut self) -> Option { + self.parse_operands()?; + let operands = self.operands(); + if operands.len() == 1 { + usize::try_from(operands[0].as_u32()?).ok() + } else { + None + } + } + + pub fn parse_font_bbox(&mut self) -> Option<[Number; 4]> { + self.parse_operands()?; + let operands = self.operands(); + if operands.len() == 4 { + Some([operands[0], operands[1], operands[2], operands[3]]) + } else { + None + } + } + + pub fn parse_font_matrix(&mut self) -> Option<[Number; 6]> { + self.parse_operands()?; + let operands = self.operands(); + if operands.len() == 6 { + Some([ + operands[0], + operands[1], + operands[2], + operands[3], + operands[4], + operands[5], + ]) + } else { + None + } + } + + pub fn parse_range(&mut self) -> Option> { + self.parse_operands()?; + let operands = self.operands(); + if operands.len() == 2 { + let len = usize::try_from(operands[0].as_u32()?).ok()?; + let start = usize::try_from(operands[1].as_u32()?).ok()?; + let end = start.checked_add(len)?; + Some(start..end) + } else { + None + } + } +} + +fn is_dict_one_byte_op(b: u8) -> bool { + match b { + 0..=27 => true, + 28..=30 => false, // numbers + 31 => true, // Reserved + 32..=254 => false, // numbers + 255 => true, // Reserved + } +} + +/// A subset of the operators for DICT's we care about. +pub(crate) mod operators { + use crate::cff::operator::{Operator, OperatorType, TWO_BYTE_OPERATOR_MARK}; + + // TOP DICT OPERATORS + pub const NOTICE: Operator = Operator(OperatorType::OneByteOperator([1])); + pub const FONT_BBOX: Operator = Operator(OperatorType::OneByteOperator([5])); + pub const COPYRIGHT: Operator = + Operator(OperatorType::TwoByteOperator([TWO_BYTE_OPERATOR_MARK, 0])); + pub const FONT_MATRIX: Operator = + Operator(OperatorType::TwoByteOperator([TWO_BYTE_OPERATOR_MARK, 7])); + pub const CHARSET: Operator = Operator(OperatorType::OneByteOperator([15])); + pub const ENCODING: Operator = Operator(OperatorType::OneByteOperator([16])); + pub const CHAR_STRINGS: Operator = Operator(OperatorType::OneByteOperator([17])); + pub const PRIVATE: Operator = Operator(OperatorType::OneByteOperator([18])); + + // TOP DICT OPERATORS (CID FONTS) + pub const ROS: Operator = + Operator(OperatorType::TwoByteOperator([TWO_BYTE_OPERATOR_MARK, 30])); + pub const CID_COUNT: Operator = + Operator(OperatorType::TwoByteOperator([TWO_BYTE_OPERATOR_MARK, 34])); + pub const FD_ARRAY: Operator = + Operator(OperatorType::TwoByteOperator([TWO_BYTE_OPERATOR_MARK, 36])); + pub const FD_SELECT: Operator = + Operator(OperatorType::TwoByteOperator([TWO_BYTE_OPERATOR_MARK, 37])); + pub const FONT_NAME: Operator = + Operator(OperatorType::TwoByteOperator([TWO_BYTE_OPERATOR_MARK, 38])); + + // PRIVATE DICT OPERATORS + pub const SUBRS: Operator = Operator(OperatorType::OneByteOperator([19])); +} diff --git a/src/cff/dict/private_dict.rs b/src/cff/dict/private_dict.rs new file mode 100644 index 00000000..4c478d6d --- /dev/null +++ b/src/cff/dict/private_dict.rs @@ -0,0 +1,95 @@ +use crate::cff::cid_font::CIDMetadata; +use crate::cff::dict::operators::*; +use crate::cff::dict::DictionaryParser; +use crate::cff::number::Number; +use crate::cff::remapper::FontDictRemapper; +use crate::cff::Offsets; +use crate::write::Writer; +use crate::Error::{MalformedFont, SubsetError}; +use crate::Result; +use std::array; + +// The parsing logic was adapted from ttf-parser. + +/// Parse the subroutine offset from a private dict. +pub fn parse_subr_offset(data: &[u8]) -> Option { + let mut operands_buffer: [Number; 48] = array::from_fn(|_| Number::zero()); + let mut dict_parser = DictionaryParser::new(data, &mut operands_buffer); + + while let Some(operator) = dict_parser.parse_next() { + if operator == SUBRS { + return dict_parser.parse_offset(); + } + } + + None +} + +/// Write the private dicts of a CID font for each font dict. +pub fn rewrite_cid_private_dicts( + fd_remapper: &FontDictRemapper, + offsets: &mut Offsets, + metadata: &CIDMetadata, + w: &mut Writer, +) -> Result<()> { + for (new_df, old_df) in fd_remapper.sorted_iter().enumerate() { + let font_dict = metadata.font_dicts.get(old_df as usize).ok_or(SubsetError)?; + rewrite_private_dict(offsets, font_dict.private_dict, w, new_df)?; + } + + Ok(()) +} + +pub(crate) fn rewrite_private_dict( + offsets: &mut Offsets, + private_dict_data: &[u8], + w: &mut Writer, + dict_index: usize, +) -> Result<()> { + let private_dict_offset = w.len(); + + let private_dict_data = { + let mut operands_buffer: [Number; 48] = array::from_fn(|_| Number::zero()); + let mut dict_parser = + DictionaryParser::new(private_dict_data, &mut operands_buffer); + + let mut sub_w = Writer::new(); + + // We just make sure that no subroutine offset gets written, all other operators stay the + // same. + while let Some(operator) = dict_parser.parse_next() { + match operator { + SUBRS => { + // We don't have any subroutines, so don't rewrite this DICT entry. + } + _ => { + dict_parser.parse_operands().ok_or(MalformedFont)?; + let operands = dict_parser.operands(); + + sub_w.write(operands); + sub_w.write(operator); + } + } + } + + sub_w.finish() + }; + + let private_dict_len = private_dict_data.len(); + + offsets + .private_dicts_lens + .get_mut(dict_index) + .ok_or(SubsetError)? + .update_value(private_dict_len)?; + + offsets + .private_dicts_offsets + .get_mut(dict_index) + .ok_or(SubsetError)? + .update_value(private_dict_offset)?; + + w.extend(&private_dict_data); + + Ok(()) +} diff --git a/src/cff/dict/top_dict.rs b/src/cff/dict/top_dict.rs new file mode 100644 index 00000000..5185febf --- /dev/null +++ b/src/cff/dict/top_dict.rs @@ -0,0 +1,174 @@ +use crate::cff::dict::DictionaryParser; +use crate::cff::index::{create_index, parse_index}; +use crate::cff::number::{Number, StringId}; +use crate::cff::remapper::SidRemapper; +use crate::cff::{Offsets, DUMMY_VALUE}; +use crate::read::Reader; +use crate::write::Writer; +use crate::Error::SubsetError; +use crate::Result; +use std::array; +use std::ops::Range; + +// The parsing logic was adapted from ttf-parser. + +#[derive(Default, Debug, Clone)] +pub struct TopDictData { + pub charset: Option, + pub char_strings: Option, + pub private: Option>, + pub fd_array: Option, + pub fd_select: Option, + pub notice: Option, + pub copyright: Option, + pub font_name: Option, + pub has_ros: bool, + pub font_matrix: Option<[Number; 6]>, + pub font_bbox: Option<[Number; 4]>, +} + +pub fn parse_top_dict_index(r: &mut Reader) -> Option { + use super::operators::*; + let mut top_dict = TopDictData::default(); + + let index = parse_index::(r)?; + + // The Top DICT INDEX should have only one dictionary in CFF fonts. + let data = index.get(0)?; + + let mut operands_buffer: [Number; 48] = array::from_fn(|_| Number::zero()); + let mut dict_parser = DictionaryParser::new(data, &mut operands_buffer); + + while let Some(operator) = dict_parser.parse_next() { + match operator { + // We only need to preserve the copyrights and font name. + NOTICE => top_dict.notice = Some(dict_parser.parse_sid()?), + COPYRIGHT => top_dict.copyright = Some(dict_parser.parse_sid()?), + FONT_NAME => top_dict.font_name = Some(dict_parser.parse_sid()?), + CHARSET => top_dict.charset = Some(dict_parser.parse_offset()?), + // We don't care about encoding since we convert to CID-keyed font anyway. + ENCODING => {} + CHAR_STRINGS => top_dict.char_strings = Some(dict_parser.parse_offset()?), + PRIVATE => top_dict.private = Some(dict_parser.parse_range()?), + // We will rewrite the ROS, so no need to grab it from here. But we need to + // register it, so we know we are dealing with a CID-keyed font. + ROS => top_dict.has_ros = true, + FD_ARRAY => top_dict.fd_array = Some(dict_parser.parse_offset()?), + FD_SELECT => top_dict.fd_select = Some(dict_parser.parse_offset()?), + FONT_MATRIX => top_dict.font_matrix = Some(dict_parser.parse_font_matrix()?), + FONT_BBOX => top_dict.font_bbox = Some(dict_parser.parse_font_bbox()?), + _ => {} + } + } + + Some(top_dict) +} + +/// Rewrite the top dict. Implementation is based on what ghostscript seems to keep when +/// rewriting a font subset. +pub fn rewrite_top_dict_index( + top_dict_data: &TopDictData, + offsets: &mut Offsets, + sid_remapper: &SidRemapper, + w: &mut Writer, +) -> Result<()> { + use super::operators::*; + + let mut sub_w = Writer::new(); + + // ROS. + sub_w + .write(Number::from_i32(sid_remapper.get(b"Adobe").ok_or(SubsetError)?.0 as i32)); + sub_w.write(Number::from_i32( + sid_remapper.get(b"Identity").ok_or(SubsetError)?.0 as i32, + )); + sub_w.write(Number::zero()); + sub_w.write(ROS); + + // Copyright notices. + if let Some(copyright) = + top_dict_data.copyright.and_then(|s| sid_remapper.get_new_sid(s)) + { + sub_w.write(Number::from_i32(copyright.0 as i32)); + sub_w.write(COPYRIGHT); + } + + if let Some(notice) = top_dict_data.notice.and_then(|s| sid_remapper.get_new_sid(s)) { + sub_w.write(Number::from_i32(notice.0 as i32)); + sub_w.write(NOTICE); + } + + // Font name. + if let Some(font_name) = + top_dict_data.font_name.and_then(|s| sid_remapper.get_new_sid(s)) + { + sub_w.write(Number::from_i32(font_name.0 as i32)); + sub_w.write(FONT_NAME); + } + + // Write a default font matrix, if it does not exist. + sub_w.write(top_dict_data.font_matrix.as_ref().unwrap_or(&[ + Number::from_f32(0.001), + Number::zero(), + Number::zero(), + Number::from_f32(0.001), + Number::zero(), + Number::zero(), + ])); + sub_w.write(FONT_MATRIX); + + // Write a default font bbox, if it does not exist. + sub_w.write(top_dict_data.font_bbox.as_ref().unwrap_or(&[ + Number::zero(), + Number::zero(), + Number::zero(), + Number::zero(), + ])); + sub_w.write(FONT_BBOX); + + // Note: When writing the offsets, we need to add the current length of w AND sub_w. + // Charset + offsets.charset_offset.update_location(sub_w.len() + w.len()); + DUMMY_VALUE.write_as_5_bytes(&mut sub_w); + sub_w.write(CHARSET); + + // Charstrings + offsets.char_strings_offset.update_location(sub_w.len() + w.len()); + DUMMY_VALUE.write_as_5_bytes(&mut sub_w); + sub_w.write(CHAR_STRINGS); + + sub_w.write(Number::from_i32(u16::MAX as i32)); + sub_w.write(CID_COUNT); + + // Note: Previously, we wrote those two entries directly after ROS. + // However, for some reason not known to me, Apple Preview does not like show the CFF font + // at all if that's the case. This is why we now write the offsets in the very end. + + // FD array. + offsets.fd_array_offset.update_location(sub_w.len() + w.len()); + DUMMY_VALUE.write_as_5_bytes(&mut sub_w); + sub_w.write(FD_ARRAY); + + // FD select. + offsets.fd_select_offset.update_location(sub_w.len() + w.len()); + DUMMY_VALUE.write_as_5_bytes(&mut sub_w); + sub_w.write(FD_SELECT); + + let finished = sub_w.finish(); + + // TOP DICT INDEX always has size 1 in CFF. + let index = create_index(vec![finished])?; + + // The offsets we calculated before were calculated under the assumption + // that the contents of sub_w will be appended directly to w. However, when we create an index, + // the INDEX header data will be appended in the beginning, meaning that we need to adjust the offsets + // to account for that. + offsets.charset_offset.adjust_location(index.header_size); + offsets.char_strings_offset.adjust_location(index.header_size); + offsets.fd_array_offset.adjust_location(index.header_size); + offsets.fd_select_offset.adjust_location(index.header_size); + + w.write(index); + + Ok(()) +} diff --git a/src/cff/index.rs b/src/cff/index.rs index 9db5eb9a..658729a9 100644 --- a/src/cff/index.rs +++ b/src/cff/index.rs @@ -1,143 +1,293 @@ -use std::fmt::{self, Debug, Formatter}; -use std::ops::{Deref, DerefMut}; +use crate::cff::number::U24; +use crate::read::{Readable, Reader}; +use crate::write::{Writeable, Writer}; +use crate::Error::OverflowError; +use crate::Result; -use crate::{Error, Reader, Result, Structure, Writer}; +// Taken from ttf-parser. -/// An INDEX data structure. -#[derive(Clone)] -pub struct Index(pub Vec); +pub trait IndexSize: for<'a> Readable<'a> { + fn to_u32(self) -> u32; +} -impl Index { - /// Create a new index with a single entry. - pub fn from_one(item: T) -> Self { - Self(vec![item]) +impl IndexSize for u16 { + fn to_u32(self) -> u32 { + u32::from(self) } +} - /// Extract the index's first entry. - pub fn into_one(self) -> Option { - self.0.into_iter().next() +impl IndexSize for u32 { + fn to_u32(self) -> u32 { + self } } -impl<'a, T> Structure<'a> for Index -where - T: Structure<'a>, -{ - fn read(r: &mut Reader<'a>) -> Result { - let data = r.data(); - let count = r.read::()? as usize; - if count == 0 { - return Ok(Self(vec![])); - } +pub fn parse_index<'a, T: IndexSize>(r: &mut Reader<'a>) -> Option> { + let count = r.read::()?; + parse_index_impl(count.to_u32(), r) +} - let offsize = r.read::()? as usize; - let base = 3 + offsize * (count + 1) - 1; - let mut read_offset = || { - let mut bytes: [u8; 4] = [0; 4]; - bytes[4 - offsize..4].copy_from_slice(r.take(offsize)?); - Ok(base + u32::from_be_bytes(bytes) as usize) - }; +fn parse_index_impl<'a>(count: u32, r: &mut Reader<'a>) -> Option> { + if count == 0 || count == core::u32::MAX { + return Some(Index::default()); + } - let mut objects = Vec::with_capacity(count); - let mut last = read_offset()?; - let mut skip = 0; - for _ in 0..count { - let offset = read_offset()?; - let slice = data.get(last..offset).ok_or(Error::InvalidOffset)?; - objects.push(T::read_at(slice, 0)?); - skip += slice.len(); - last = offset; + let offset_size = r.read::()?; + let offsets_len = (count + 1).checked_mul(offset_size.to_u32())?; + let offsets = VarOffsets { + data: r.read_bytes(offsets_len as usize)?, + offset_size, + }; + + match offsets.last() { + Some(last_offset) => { + let data = r.read_bytes(last_offset as usize)?; + Some(Index { data, offsets }) } + None => Some(Index::default()), + } +} + +pub fn skip_index(r: &mut Reader) -> Option<()> { + let count = r.read::()?; + skip_index_impl(count.to_u32(), r) +} - r.skip(skip)?; - Ok(Self(objects)) +fn skip_index_impl(count: u32, r: &mut Reader) -> Option<()> { + if count == 0 || count == core::u32::MAX { + return Some(()); } - fn write(&self, w: &mut Writer) { - w.write::(self.0.len() as u16); - if self.0.is_empty() { - return; - } + let offset_size = r.read::()?; + let offsets_len = (count + 1).checked_mul(offset_size.to_u32())?; + let offsets = VarOffsets { + data: r.read_bytes(offsets_len as usize)?, + offset_size, + }; + + if let Some(last_offset) = offsets.last() { + r.skip_bytes(last_offset as usize); + } + + Some(()) +} + +#[derive(Clone, Copy, Debug)] +pub struct VarOffsets<'a> { + pub data: &'a [u8], + pub offset_size: OffsetSize, +} - let mut buffer = Writer::new(); - let mut offsets = vec![]; - for object in &self.0 { - offsets.push(1 + buffer.len() as u32); - buffer.write_ref::(object); +impl<'a> VarOffsets<'a> { + pub fn get(&self, index: u32) -> Option { + if index >= self.len() { + return None; } - let end = 1 + buffer.len() as u32; - offsets.push(end); + let start = index as usize * self.offset_size.to_usize(); + let mut r = Reader::new_at(self.data, start); + let n: u32 = match self.offset_size { + OffsetSize::Size1 => u32::from(r.read::()?), + OffsetSize::Size2 => u32::from(r.read::()?), + OffsetSize::Size3 => r.read::()?.0, + OffsetSize::Size4 => r.read::()?, + }; - let offsize = Offsize::select(end); - w.write::(offsize); + // Offsets are offset by one byte in the font, + // so we have to shift them back. + n.checked_sub(1) + } - let offsize = offsize as usize; - for offset in offsets { - let bytes = u32::to_be_bytes(offset); - w.give(&bytes[4 - offsize..4]); + #[inline] + pub fn last(&self) -> Option { + if !self.is_empty() { + self.get(self.len() - 1) + } else { + None } + } + + #[inline] + pub fn len(&self) -> u32 { + self.data.len() as u32 / self.offset_size as u32 + } - w.give(&buffer.finish()); + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 } } -impl Debug for Index { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_list().entries(&self.0).finish() +#[derive(Clone, Copy, Debug)] +pub struct Index<'a> { + pub data: &'a [u8], + pub offsets: VarOffsets<'a>, +} + +impl<'a> Default for Index<'a> { + #[inline] + fn default() -> Self { + Index { + data: b"", + offsets: VarOffsets { data: b"", offset_size: OffsetSize::Size1 }, + } } } -impl Deref for Index { - type Target = [T]; +impl<'a> IntoIterator for Index<'a> { + type Item = &'a [u8]; + type IntoIter = IndexIter<'a>; - fn deref(&self) -> &Self::Target { - &self.0 + #[inline] + fn into_iter(self) -> Self::IntoIter { + IndexIter { data: self, offset_index: 0 } } } -impl DerefMut for Index { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 +impl<'a> Index<'a> { + #[inline] + pub fn len(&self) -> u32 { + self.offsets.len().saturating_sub(1) + } + + pub fn get(&self, index: u32) -> Option<&'a [u8]> { + let next_index = index.checked_add(1)?; + let start = self.offsets.get(index)? as usize; + let end = self.offsets.get(next_index)? as usize; + self.data.get(start..end) } } -/// The number of bytes an offset is encoded with. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[repr(u8)] -enum Offsize { - One = 1, - Two = 2, - Three = 3, - Four = 4, +pub struct IndexIter<'a> { + data: Index<'a>, + offset_index: u32, } -impl Offsize { - fn select(max: u32) -> Self { - if max < (1 << 8) { - Self::One - } else if max < (1 << 16) { - Self::Two - } else if max < (1 << 24) { - Self::Three - } else { - Self::Four +impl<'a> Iterator for IndexIter<'a> { + type Item = &'a [u8]; + + #[inline] + fn next(&mut self) -> Option { + if self.offset_index == self.data.len() { + return None; } + + let index = self.offset_index; + self.offset_index += 1; + self.data.get(index) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum OffsetSize { + Size1 = 1, + Size2 = 2, + Size3 = 3, + Size4 = 4, +} + +impl OffsetSize { + #[inline] + pub fn to_u32(self) -> u32 { + self as u32 + } + #[inline] + pub fn to_usize(self) -> usize { + self as usize } } -impl Structure<'_> for Offsize { - fn read(r: &mut Reader) -> Result { +impl Readable<'_> for OffsetSize { + const SIZE: usize = 1; + + fn read(r: &mut Reader<'_>) -> Option { match r.read::()? { - 1 => Ok(Self::One), - 2 => Ok(Self::Two), - 3 => Ok(Self::Three), - 4 => Ok(Self::Four), - _ => Err(Error::InvalidOffset), + 1 => Some(OffsetSize::Size1), + 2 => Some(OffsetSize::Size2), + 3 => Some(OffsetSize::Size3), + 4 => Some(OffsetSize::Size4), + _ => None, } } +} + +/// An index that owns its data. +pub struct OwnedIndex { + pub data: Vec, + pub header_size: usize, +} +impl Writeable for OwnedIndex { fn write(&self, w: &mut Writer) { - w.write::(*self as u8); + w.extend(&self.data); + } +} + +impl Default for OwnedIndex { + fn default() -> Self { + Self { data: vec![0, 0], header_size: 2 } } } + +/// Create an index from a vector of data. +pub fn create_index(data: Vec>) -> Result { + let count = u16::try_from(data.len()).map_err(|_| OverflowError)?; + // + 1 Since we start counting from the preceding byte. + let offsize = data.iter().map(|v| v.len() as u32).sum::() + 1; + + // Empty Index only contains the count field + if count == 0 { + return Ok(OwnedIndex::default()); + } + + let offset_size = if offsize <= u8::MAX as u32 { + OffsetSize::Size1 + } else if offsize <= u16::MAX as u32 { + OffsetSize::Size2 + } else if offsize <= U24::MAX { + OffsetSize::Size3 + } else { + OffsetSize::Size4 + }; + + let mut w = Writer::new(); + w.write(count); + w.write(offset_size as u8); + + let mut cur_offset: u32 = 0; + + let mut write_offset = |len| { + cur_offset += len; + + match offset_size { + OffsetSize::Size1 => { + let num = u8::try_from(cur_offset).map_err(|_| OverflowError)?; + w.write(num); + } + OffsetSize::Size2 => { + let num = u16::try_from(cur_offset).map_err(|_| OverflowError)?; + w.write(num); + } + OffsetSize::Size3 => { + let num = U24(cur_offset); + w.write(num); + } + OffsetSize::Size4 => w.write(cur_offset), + } + + Ok(()) + }; + + write_offset(1)?; + for el in &data { + write_offset(el.len() as u32)?; + } + + let header_size = w.len(); + + for el in &data { + w.extend(el); + } + + Ok(OwnedIndex { header_size, data: w.finish() }) +} diff --git a/src/cff/mod.rs b/src/cff/mod.rs index 6f365ca2..d4cf2ac0 100644 --- a/src/cff/mod.rs +++ b/src/cff/mod.rs @@ -1,531 +1,399 @@ +mod argstack; +mod charset; +mod charstring; +mod cid_font; mod dict; mod index; +mod number; +mod operator; +mod remapper; +mod sid_font; +mod subroutines; -use std::collections::HashSet; -use std::fmt::{self, Debug, Formatter}; -use std::ops::Range; - -use self::dict::*; -use self::index::*; use super::*; - -/// A CFF table. -struct Table<'a> { - name: Index>, - top: Dict<'a>, - strings: Index>, - global_subrs: Index>, - encoding: Option>, - charset: Option>, - char_strings: Index>, - private: Option>, - cid: Option>, +use crate::cff::charset::rewrite_charset; +use crate::cff::charstring::Decompiler; +use crate::cff::cid_font::{rewrite_fd_index, CIDMetadata}; +use crate::cff::dict::font_dict::{generate_font_dict_index, rewrite_font_dict_index}; +use crate::cff::dict::private_dict::{rewrite_cid_private_dicts, rewrite_private_dict}; +use crate::cff::dict::top_dict::{ + parse_top_dict_index, rewrite_top_dict_index, TopDictData, +}; +use crate::cff::index::{create_index, parse_index, skip_index, Index, OwnedIndex}; +use crate::cff::remapper::{FontDictRemapper, SidRemapper}; +use crate::cff::sid_font::SIDMetadata; +use crate::cff::subroutines::{SubroutineCollection, SubroutineContainer}; +use crate::Error::{OverflowError, SubsetError}; +use number::{IntegerNumber, StringId}; +use sid_font::generate_fd_index; +use std::cmp::PartialEq; +use std::collections::BTreeSet; + +#[derive(Clone, Debug)] +pub(crate) enum FontKind<'a> { + Sid(SIDMetadata<'a>), + Cid(CIDMetadata<'a>), } -/// Data specific to Private DICTs. -struct PrivateData<'a> { - dict: Dict<'a>, - subrs: Option>>, +#[derive(Clone)] +pub struct Table<'a> { + names: &'a [u8], + top_dict_data: TopDictData, + strings: Index<'a>, + global_subrs: Index<'a>, + char_strings: Index<'a>, + font_kind: FontKind<'a>, } -/// Data specific to CID-keyed fonts. -struct CidData<'a> { - array: Index>, - select: FdSelect<'a>, - private: Vec>, +/// An offset that needs to be written after the whole font +/// has been written. location indicates where in the buffer the offset needs to be written to +/// and value indicates the value of the offset. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +struct DeferredOffset { + location: usize, + value: IntegerNumber, } -/// An FD Select dat structure. -struct FdSelect<'a>(Cow<'a, [u8]>); - -/// Recorded offsets that will be written into DICTs. -struct Offsets { - char_strings: usize, - encoding: Option, - charset: Option, - private: Option, - cid: Option, -} - -/// Offsets specific to Private DICTs. -struct PrivateOffsets { - dict: Range, - subrs: Option, -} - -/// Offsets specific to CID-keyed fonts. -struct CidOffsets { - array: usize, - select: usize, - private: Vec, -} - -/// Find all glyphs referenced through components. -/// CFF doesn't used component glyphs, so it's just the profile's set. -/// -/// TODO: What about seac? -pub(crate) fn discover(ctx: &mut Context) { - ctx.subset = ctx.profile.glyphs.iter().copied().collect(); -} - -/// Subset the CFF table by removing glyph data for unused glyphs. -pub(crate) fn subset(ctx: &mut Context) -> Result<()> { - let cff = ctx.expect_table(Tag::CFF)?; - - // Check version. - let mut r = Reader::new(cff); - let major = r.read::()?; - if major != 1 { - ctx.push(Tag::CFF, cff); - return Ok(()); - } - - // Parse CFF table. - let mut table = read_cff_table(ctx, cff)?; +const DUMMY_VALUE: IntegerNumber = IntegerNumber(0); +/// This represents a not-existing offset. +const DUMMY_OFFSET: DeferredOffset = DeferredOffset { location: 0, value: DUMMY_VALUE }; - // Subset the char strings. - subset_char_strings(ctx, &mut table.char_strings)?; - - // Subset Top and Private DICT. - table.top.retain(top::KEEP); - if let Some(private) = &mut table.private { - private.dict.retain(private::KEEP); +impl DeferredOffset { + fn update_value(&mut self, value: usize) -> Result<()> { + self.value = IntegerNumber(i32::try_from(value).map_err(|_| OverflowError)?); + Ok(()) } - // Subset data specific to CID-keyed fonts. - if let Some(cid) = &mut table.cid { - subset_font_dicts(ctx, cid)?; - - for dict in cid.array.iter_mut() { - dict.retain(top::KEEP); - } - - for private in &mut cid.private { - private.dict.retain(private::KEEP); + /// Adjust the location of an offset, if it has been set (i.e. it's not a dummy offset). + fn adjust_location(&mut self, delta: usize) { + if *self != DUMMY_OFFSET { + self.location += delta; } } - // Construct a new CFF table. - let mut sub_cff = vec![]; - let mut offsets = create_offsets(&table); - - // Write twice because we first need to find out the offsets of various data - // structures. - for _ in 0..2 { - let mut w = Writer::new(); - insert_offsets(&mut table, &offsets); - write_cff_table(&mut w, &table, &mut offsets); - sub_cff = w.finish(); + fn update_location(&mut self, location: usize) { + self.location = location; } - ctx.push(Tag::CFF, sub_cff); + /// Write the deferred offset into a buffer. + fn write_into(&self, buffer: &mut [u8]) -> Result<()> { + let mut w = Writer::new(); + // Always write using 5 bytes, to prevent its size from changing. + self.value.write_as_5_bytes(&mut w); + let encoded = w.finish(); + let pos = buffer.get_mut(self.location..self.location + 5).ok_or(SubsetError)?; - Ok(()) -} + pos.copy_from_slice(&encoded); -/// Subset the glyph descriptions. -fn subset_char_strings(ctx: &Context, strings: &mut Index) -> Result<()> { - for glyph in 0..ctx.num_glyphs { - if !ctx.subset.contains(&glyph) { - // The byte sequence [14] is the minimal valid charstring consisting - // of just a single `endchar` operator. - *strings.get_mut(glyph as usize).ok_or(Error::InvalidOffset)? = Opaque(&[14]); - } + Ok(()) } - - Ok(()) } -/// Subset CID-related data. -fn subset_font_dicts(ctx: &Context, cid: &mut CidData) -> Result<()> { - // Determine which subroutine indices to keep. - let mut kept_subrs = HashSet::new(); - for &glyph in ctx.profile.glyphs { - kept_subrs - .insert(*cid.select.0.get(usize::from(glyph)).ok_or(Error::MissingData)?); - } - - // Remove subroutines for unused Private DICTs. - for (i, dict) in cid.private.iter_mut().enumerate() { - if !kept_subrs.contains(&(i as u8)) { - dict.subrs = None; - } - } - - Ok(()) +/// Keeps track of the offsets that need to be written in the font. +#[derive(Debug)] +struct Offsets { + /// Offset of the charset data. + charset_offset: DeferredOffset, + /// Offset of the charstrings data. + char_strings_offset: DeferredOffset, + /// Lengths of the private dicts (not strictly an offset, but we still use it for simplicity). + private_dicts_lens: Vec, + /// Offset of the private dicts. + private_dicts_offsets: Vec, + /// Offset of the fd array. + fd_array_offset: DeferredOffset, + /// Offset of the fd select. + fd_select_offset: DeferredOffset, } -/// Parse a CFF table. -fn read_cff_table<'a>(ctx: &Context, cff: &'a [u8]) -> Result> { - // Skip header. - let mut r = Reader::new(cff); - r.read::()?; - r.read::()?; - let header_size = r.read::()? as usize; - r = Reader::new(cff.get(header_size..).ok_or(Error::InvalidOffset)?); - - // Read four indices at fixed positions. - let name = r.read::>()?; - let tops = r.read::>()?; - let strings = r.read::>()?; - let global_subrs = r.read::>()?; - - // Extract only Top DICT. - let top = tops.into_one().ok_or(Error::MissingData)?; - - // Read encoding if it exists. - let mut encoding = None; - if let Some(offset) = top.get_offset(top::ENCODING) { - let data = cff.get(offset..).ok_or(Error::InvalidOffset)?; - encoding = Some(read_encoding(data)?); - } - - // Read the glyph descriptions. - let char_strings = { - let offset = top.get_offset(top::CHAR_STRINGS).ok_or(Error::MissingData)?; - Index::read_at(cff, offset)? - }; - - // Read the charset. - let mut charset = None; - if let Some(offset @ 1..) = top.get_offset(top::CHARSET) { - let sub = cff.get(offset..).ok_or(Error::InvalidOffset)?; - charset = Some(read_charset(sub, ctx.num_glyphs)?); - } - - // Read Private DICT with local subroutines. - let mut private = None; - if let Some(range) = top.get_range(top::PRIVATE) { - private = Some(read_private_dict(cff, range)?); +impl Offsets { + pub fn new_cid(num_font_dicts: u8) -> Self { + Self { + char_strings_offset: DUMMY_OFFSET, + charset_offset: DUMMY_OFFSET, + private_dicts_lens: vec![DUMMY_OFFSET; num_font_dicts as usize], + private_dicts_offsets: vec![DUMMY_OFFSET; num_font_dicts as usize], + fd_select_offset: DUMMY_OFFSET, + fd_array_offset: DUMMY_OFFSET, + } } - // Read data specific to CID-keyed fonts. - let mut cid = None; - if top.get(top::ROS).is_some() { - cid = Some(read_cid_data(ctx, cff, &top)?); + pub fn new_sid() -> Self { + Self::new_cid(1) } - - Ok(Table { - name, - top, - strings, - global_subrs, - encoding, - charset, - char_strings, - private, - cid, - }) } -/// Write the a new CFF table. -fn write_cff_table(w: &mut Writer, table: &Table, offsets: &mut Offsets) { - // Write header. - w.write::(1); - w.write::(0); - w.write::(4); - w.write::(4); - w.inspect("Header"); - - // Write the four fixed indices. - w.write_ref(&table.name); - w.inspect("Name INDEX"); - - w.write(Index::from_one(table.top.clone())); - w.inspect("Top DICT INDEX"); - - w.write_ref(&table.strings); - w.inspect("String INDEX"); - - w.write_ref(&table.global_subrs); - w.inspect("Global Subroutine INDEX"); - - // Write encoding. - if let Some(encoding) = &table.encoding { - offsets.encoding = Some(w.len()); - write_encoding(w, encoding); - w.inspect("Encoding"); - } - - // Write charset. - if let Some(charset) = &table.charset { - offsets.charset = Some(w.len()); - write_charset(w, charset); - w.inspect("Charset"); - } - - // Write char strings. - offsets.char_strings = w.len(); - w.write_ref(&table.char_strings); - w.inspect("Charstring INDEX"); - - // Write private dict. - if let (Some(private), Some(offsets)) = (&table.private, &mut offsets.private) { - write_private_data(w, private, offsets); - } +pub fn subset(ctx: &mut Context<'_>) -> Result<()> { + let table = Table::parse(ctx).unwrap(); - // Write data specific to CID-keyed fonts. - if let (Some(cid), Some(offsets)) = (&table.cid, &mut offsets.cid) { - write_cid_data(w, cid, offsets); - } -} + // Note: The charstrings are already in the new order that they need be written in. + let (char_strings, fd_remapper) = subset_charstrings(&table, &ctx.mapper)?; -/// Read data specific to CID-keyed fonts. -fn read_cid_data<'a>( - ctx: &Context, - cff: &'a [u8], - top: &Dict<'a>, -) -> Result> { - // Read FD Array. - let array = { - let offset = top.get_offset(top::FD_ARRAY).ok_or(Error::MissingData)?; - Index::>::read_at(cff, offset)? - }; + let sid_remapper = get_sid_remapper(&table, &fd_remapper).ok_or(SubsetError)?; - // Read FD Select data structure. - let select = { - let offset = top.get_offset(top::FD_SELECT).ok_or(Error::MissingData)?; - let sub = cff.get(offset..).ok_or(Error::InvalidOffset)?; - read_fd_select(sub, ctx.num_glyphs)? + let mut offsets = match &table.font_kind { + FontKind::Sid(_) => Offsets::new_sid(), + FontKind::Cid(_) => Offsets::new_cid(fd_remapper.len()), }; - // Read Private DICTs. - let mut private = vec![]; - for dict in array.iter() { - let range = dict.get_range(top::PRIVATE).ok_or(Error::MissingData)?; - private.push(read_private_dict(cff, range)?); - } + let mut subsetted_font = { + let mut w = Writer::new(); + // HEADER + // We always use OffSize 4 (but as far as I can tell this field is unused anyway). + w.write([1u8, 0, 4, 4]); + // Name INDEX + w.write(table.names); + // Top DICT INDEX + rewrite_top_dict_index( + &table.top_dict_data, + &mut offsets, + &sid_remapper, + &mut w, + )?; + // String INDEX + let index = create_index( + sid_remapper + .sorted_strings() + .map(|s| Vec::from(s.as_ref())) + .collect::>(), + )?; + w.extend(&index.data); + // Global Subr INDEX + // We desubroutinized, so no global subroutines and thus empty index. + w.write(&OwnedIndex::default()); + + // Charsets + offsets.charset_offset.update_value(w.len())?; + rewrite_charset(&ctx.mapper, &mut w)?; + + // Private dicts. + match &table.font_kind { + FontKind::Sid(sid) => { + // Since we convert SID-keyed to CID-keyed, we write one private dict with index 0. + rewrite_private_dict(&mut offsets, sid.private_dict_data, &mut w, 0)?; + } + FontKind::Cid(cid) => { + rewrite_cid_private_dicts(&fd_remapper, &mut offsets, cid, &mut w)?; + } + } - Ok(CidData { array, select, private }) -} + // FDSelect + offsets.fd_select_offset.update_value(w.len())?; + match &table.font_kind { + FontKind::Sid(_) => generate_fd_index(&ctx.mapper, &mut w)?, + FontKind::Cid(cid_metadata) => rewrite_fd_index( + &ctx.mapper, + cid_metadata.fd_select, + &fd_remapper, + &mut w, + )?, + }; + + // FDArray + offsets.fd_array_offset.update_value(w.len())?; + match &table.font_kind { + FontKind::Sid(_) => generate_font_dict_index(&mut offsets, &mut w)?, + FontKind::Cid(cid_metadata) => rewrite_font_dict_index( + &fd_remapper, + &sid_remapper, + &mut offsets, + cid_metadata, + &mut w, + )?, + } -/// Write data specific to CID-keyed fonts. -fn write_cid_data(w: &mut Writer, cid: &CidData, offsets: &mut CidOffsets) { - // Write FD Array. - offsets.array = w.len(); - w.write_ref(&cid.array); - w.inspect("FD Array"); - - // Write FD Select. - offsets.select = w.len(); - write_fd_select(w, &cid.select); - w.inspect("FD Select"); - - // Write Private DICTs. - for (private, offsets) in cid.private.iter().zip(&mut offsets.private) { - write_private_data(w, private, offsets); - } -} + // Charstrings INDEX + offsets.char_strings_offset.update_value(w.len())?; + w.extend(&create_index(char_strings)?.data); -/// Read a Private DICT and optionally local subroutines. -fn read_private_dict(cff: &[u8], range: Range) -> Result> { - let start = range.start; - let sub = cff.get(range).ok_or(Error::InvalidOffset)?; - let dict = Dict::read_at(sub, 0)?; + w.finish() + }; - let mut subrs = None; - if let Some(offset) = dict.get_offset(private::SUBRS) { - subrs = Some(Index::read_at(cff, start + offset)?); - } + // Rewrite the dummy offsets. + update_offsets(&offsets, subsetted_font.as_mut_slice())?; - Ok(PrivateData { dict, subrs }) -} + ctx.push(Tag::CFF, subsetted_font); -/// Write a Private DICT and optionally local subroutines. -fn write_private_data( - w: &mut Writer, - private: &PrivateData, - offsets: &mut PrivateOffsets, -) { - offsets.dict.start = w.len(); - w.write_ref(&private.dict); - offsets.dict.end = w.len(); - w.inspect("Private DICT"); - - // Write local subroutines. - if let Some(subrs) = &private.subrs { - offsets.subrs = Some(w.len() - offsets.dict.start); - w.write_ref(subrs); - w.inspect("Local Subroutine INDEX"); - } + Ok(()) } -/// Read an encoding. -fn read_encoding(data: &[u8]) -> Result> { - let mut r = Reader::new(data); - let mut len = 1; - - let format = r.read::()?; - match format { - 0 => { - let n_codes = r.read::()? as usize; - len += 1 + n_codes; - } - 1 => { - let n_ranges = r.read::()? as usize; - len += 1 + 2 * n_ranges; +fn update_offsets(offsets: &Offsets, buffer: &mut [u8]) -> Result<()> { + let mut write = |offset: DeferredOffset| { + if offset != DUMMY_OFFSET { + offset.write_into(buffer)?; } - _ => return Err(Error::InvalidData), - } + Ok(()) + }; - Ok(Opaque(data.get(..len).ok_or(Error::InvalidOffset)?)) -} + // Private dicts offset have already been written correctly, so no need to write them + // here. -/// Write an encoding. -fn write_encoding(w: &mut Writer, encoding: &Opaque<'_>) { - w.write_ref(encoding); + write(offsets.charset_offset)?; + write(offsets.char_strings_offset)?; + write(offsets.fd_select_offset)?; + write(offsets.fd_array_offset)?; + + Ok(()) } -/// Read a charset. -fn read_charset(data: &[u8], num_glyphs: u16) -> Result> { - let mut r = Reader::new(data); - let mut len = 1; +/// Create the list of bytes that constitute the programs of the charstrings, sorted in the new glyph order. +fn subset_charstrings( + table: &Table, + remapper: &GlyphRemapper, +) -> Result<(Vec>, FontDictRemapper)> { + let gsubrs = { + let subroutines = table.global_subrs.into_iter().collect::>(); + SubroutineContainer::new(subroutines) + }; - let format = r.read::()?; - match format { - 0 => { - len += 2 * num_glyphs.saturating_sub(1) as usize; - } - 1 => { - let mut seen = 1; - while seen < num_glyphs { - r.read::()?; - seen = seen.saturating_add(1); - seen = seen.saturating_add(r.read::()? as u16); - len += 3; + let lsubrs = { + match &table.font_kind { + FontKind::Cid(cid) => { + let subroutines = cid + .font_dicts + .iter() + .map(|font_dict| { + font_dict.local_subrs.into_iter().collect::>() + }) + .collect::>(); + SubroutineCollection::new(subroutines) } - } - 2 => { - let mut seen = 1; - while seen < num_glyphs { - r.read::()?; - seen = seen.saturating_add(1); - seen = seen.saturating_add(r.read::()?); - len += 4; + FontKind::Sid(sid) => { + let subroutines = sid.local_subrs.into_iter().collect::>(); + SubroutineCollection::new(vec![subroutines]) } } - _ => return Err(Error::InvalidData), - } - - Ok(Opaque(data.get(..len).ok_or(Error::InvalidOffset)?)) -} + }; -/// Write a charset. -fn write_charset(w: &mut Writer, charset: &Opaque<'_>) { - w.write_ref(charset); -} + let mut used_fds = BTreeSet::new(); + let mut char_strings = vec![]; -/// Read the FD Select data structure. -fn read_fd_select(data: &[u8], num_glyphs: u16) -> Result> { - let mut r = Reader::new(data); - let format = r.read::()?; - Ok(FdSelect(match format { - 0 => Cow::Borrowed(r.take(num_glyphs as usize)?), - 3 => { - let count = r.read::()?; - let mut fds = vec![]; - let mut first = r.read::()?; - for _ in 0..count { - let fd = r.read::()?; - let end = r.read::()?; - for _ in first..end { - fds.push(fd); - } - first = end; + for old_gid in remapper.remapped_gids() { + let fd_index = match &table.font_kind { + FontKind::Cid(ref cid) => { + let fd_index = + cid.fd_select.font_dict_index(old_gid).ok_or(MalformedFont)?; + used_fds.insert(fd_index); + fd_index } - Cow::Owned(fds) - } - _ => return Err(Error::InvalidData), - })) -} + FontKind::Sid(_) => 0, + }; + + let decompiler = Decompiler::new( + gsubrs.get_handler(), + lsubrs.get_handler(fd_index).ok_or(MalformedFont)?, + ); + let charstring = table.char_strings.get(old_gid as u32).ok_or(MalformedFont)?; + char_strings.push(decompiler.decompile(charstring)?); + } -/// Write an FD Select data structure. -fn write_fd_select(w: &mut Writer, select: &FdSelect) { - w.write::(0); - w.give(&select.0); -} + let mut fd_remapper = FontDictRemapper::new(); -/// Create initial zero offsets for all data structures. -fn create_offsets(table: &Table) -> Offsets { - Offsets { - char_strings: 0, - charset: table.charset.as_ref().map(|_| 0), - encoding: table.encoding.as_ref().map(|_| 0), - private: table.private.as_ref().map(create_private_offsets), - cid: table.cid.as_ref().map(create_cid_offsets), + for fd in used_fds { + fd_remapper.remap(fd); } -} -/// Create initial zero offsets for all CID-related data structures. -fn create_cid_offsets(cid: &CidData) -> CidOffsets { - CidOffsets { - array: 0, - select: 0, - private: cid.private.iter().map(create_private_offsets).collect(), - } + Ok((char_strings.iter().map(|p| p.compile()).collect(), fd_remapper)) } -/// Create initial zero offsets for a Private DICT. -fn create_private_offsets(private: &PrivateData) -> PrivateOffsets { - PrivateOffsets { - dict: 0..0, - subrs: private.subrs.as_ref().map(|_| 0), - } -} +fn get_sid_remapper<'a>( + table: &Table<'a>, + fd_remapper: &FontDictRemapper, +) -> Option> { + let mut sid_remapper = SidRemapper::new(); + sid_remapper.remap(&b"Adobe"[..]); + sid_remapper.remap(&b"Identity"[..]); + + let mut remap_sid = |sid: StringId| { + if sid.is_standard_string() { + Some(()) + } else { + let string = + table.strings.get((sid.0 - StringId::STANDARD_STRING_LEN) as u32)?; + sid_remapper.remap_with_old_sid(sid, Cow::Borrowed(string)); + + Some(()) + } + }; -/// Insert the offsets of various parts of the font into the relevant DICTs. -fn insert_offsets(table: &mut Table, offsets: &Offsets) { - if let Some(offset) = offsets.encoding { - table.top.set_offset(top::ENCODING, offset); + if let Some(copyright) = table.top_dict_data.copyright { + remap_sid(copyright)?; } - if let Some(offset) = offsets.charset { - table.top.set_offset(top::CHARSET, offset); + if let Some(font_name) = table.top_dict_data.font_name { + remap_sid(font_name)?; } - table.top.set_offset(top::CHAR_STRINGS, offsets.char_strings); - - if let (Some(private), Some(offsets)) = (&mut table.private, &offsets.private) { - table.top.set_range(top::PRIVATE, &offsets.dict); - - if let Some(offset) = offsets.subrs { - private.dict.set_offset(private::SUBRS, offset); - } + if let Some(notice) = table.top_dict_data.notice { + remap_sid(notice)?; } - if let (Some(cid), Some(offsets)) = (&mut table.cid, &offsets.cid) { - table.top.set_offset(top::FD_ARRAY, offsets.array); - table.top.set_offset(top::FD_SELECT, offsets.select); - - for (dict, offsets) in cid.array.iter_mut().zip(&offsets.private) { - dict.set_range(top::PRIVATE, &offsets.dict); - } + if let FontKind::Cid(ref cid) = table.font_kind { + for font_dict in fd_remapper.sorted_iter() { + let font_dict = cid.font_dicts.get(font_dict as usize)?; - for (private, offsets) in cid.private.iter_mut().zip(&offsets.private) { - if let Some(offset) = offsets.subrs { - private.dict.set_offset(private::SUBRS, offset); + if let Some(font_name) = font_dict.font_name { + remap_sid(font_name)?; } } } + + Some(sid_remapper) } -/// An opaque binary data structure. -struct Opaque<'a>(&'a [u8]); +// The parsing logic was taken from ttf-parser. +impl<'a> Table<'a> { + pub fn parse(ctx: &mut Context<'a>) -> Result { + let cff = ctx.expect_table(Tag::CFF).ok_or(MalformedFont)?; -impl<'a> Structure<'a> for Opaque<'a> { - fn read(r: &mut Reader<'a>) -> Result { - let data = r.data(); - r.skip(data.len())?; - Ok(Self(data)) - } + let mut r = Reader::new(cff); - fn write(&self, w: &mut Writer) { - w.give(self.0); - } -} + let major = r.read::().ok_or(MalformedFont)?; + + if major != 1 { + return Err(Error::CFFError); + } -impl Debug for Opaque<'_> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("Opaque { .. }") + r.skip::(); // minor + let header_size = r.read::().ok_or(MalformedFont)?; + + r.jump(header_size as usize); + + let names_start = r.offset(); + skip_index::(&mut r).ok_or(MalformedFont)?; + let names = cff.get(names_start..r.offset()).ok_or(MalformedFont)?; + let top_dict_data = parse_top_dict_index(&mut r).ok_or(MalformedFont)?; + + let strings = parse_index::(&mut r).ok_or(MalformedFont)?; + let global_subrs = parse_index::(&mut r).ok_or(MalformedFont)?; + + let char_strings_offset = top_dict_data.char_strings.ok_or(MalformedFont)?; + let char_strings = { + let mut r = Reader::new_at(cff, char_strings_offset); + parse_index::(&mut r).ok_or(MalformedFont)? + }; + + let number_of_glyphs = u16::try_from(char_strings.len()) + .ok() + .filter(|n| *n > 0) + .ok_or(MalformedFont)?; + + let font_kind = if top_dict_data.has_ros { + FontKind::Cid( + cid_font::parse_cid_metadata(cff, &top_dict_data, number_of_glyphs) + .ok_or(MalformedFont)?, + ) + } else { + FontKind::Sid(sid_font::parse_sid_metadata(cff, &top_dict_data)) + }; + + Ok(Self { + names, + top_dict_data, + strings, + global_subrs, + char_strings, + font_kind, + }) } } diff --git a/src/cff/number.rs b/src/cff/number.rs new file mode 100644 index 00000000..d08d7439 --- /dev/null +++ b/src/cff/number.rs @@ -0,0 +1,527 @@ +use crate::read::{Readable, Reader}; +use crate::write::{Writeable, Writer}; +use std::fmt::{Debug, Formatter}; + +const FLOAT_STACK_LEN: usize = 64; +const END_OF_FLOAT_FLAG: u8 = 0xf; + +#[derive(Clone, Copy)] +pub struct RealNumber(f32); + +impl Debug for RealNumber { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[derive(Clone, Default, Eq, Copy, PartialEq)] +pub struct IntegerNumber(pub i32); + +impl Debug for IntegerNumber { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[derive(Clone, Copy)] +pub struct FixedNumber(i32); + +impl Debug for FixedNumber { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl FixedNumber { + pub fn as_f32(&self) -> f32 { + self.0 as f32 / 65536.0 + } + + pub fn parse(r: &mut Reader<'_>) -> Option { + let b0 = r.read::()?; + + if b0 != 255 { + return None; + } + + let num = r.read::()?; + Some(FixedNumber(num)) + } +} + +impl Writeable for FixedNumber { + fn write(&self, w: &mut Writer) { + w.write::(255); + w.write(self.0); + } +} + +impl RealNumber { + // The parsing logic was taken from ttf-parser. + pub fn parse(r: &mut Reader) -> Option { + let mut data = [0u8; FLOAT_STACK_LEN]; + let mut idx = 0; + + let b0 = r.read::()?; + + if b0 != 30 { + return None; + } + + loop { + let b1: u8 = r.read()?; + let nibble1 = b1 >> 4; + let nibble2 = b1 & 15; + + if nibble1 == END_OF_FLOAT_FLAG { + break; + } + + idx = parse_float_nibble(nibble1, idx, &mut data)?; + + if nibble2 == END_OF_FLOAT_FLAG { + break; + } + + idx = parse_float_nibble(nibble2, idx, &mut data)?; + } + + let s = core::str::from_utf8(&data[..idx]).ok()?; + let n = s.parse().ok()?; + + Some(RealNumber(n)) + } +} + +impl Writeable for RealNumber { + // Not the fastest implementation, but floats don't appear that often anyway, + // so it's good enough. + fn write(&self, w: &mut Writer) { + let mut nibbles = vec![]; + + let string_form = format!("{}", self.0); + let mut r = Reader::new(string_form.as_bytes()); + + while !r.at_end() { + let byte = r.read::().unwrap(); + + match byte { + b'0'..=b'9' => nibbles.push(byte - 48), + b'.' => nibbles.push(0xA), + b'-' => nibbles.push(0xE), + _ => unreachable!(), + } + } + + nibbles.push(0xF); + + if nibbles.len() % 2 != 0 { + nibbles.push(0xF); + } + + // Prefix of fixed number. + w.write::(30); + + for (first, second) in nibbles.chunks(2).map(|pair| (pair[0], pair[1])) { + let num = (first << 4) | second; + w.write(num); + } + } +} + +impl IntegerNumber { + pub fn parse(r: &mut Reader<'_>) -> Option { + let b0 = r.read::()?; + match b0 { + 28 => Some(IntegerNumber(i32::from(r.read::()?))), + 29 => Some(IntegerNumber(r.read::()?)), + 32..=246 => { + let n = i32::from(b0) - 139; + Some(IntegerNumber(n)) + } + 247..=250 => { + let b1 = i32::from(r.read::()?); + let n = (i32::from(b0) - 247) * 256 + b1 + 108; + Some(IntegerNumber(n)) + } + 251..=254 => { + let b1 = i32::from(r.read::()?); + let n = -(i32::from(b0) - 251) * 256 - b1 - 108; + Some(IntegerNumber(n)) + } + _ => None, + } + } + + /// Write the number as a 5 byte sequence. This is necessary when writing offsets, + /// because we need to the length of the number to stay stable, since it would + /// otherwise shift everything. + pub fn write_as_5_bytes(&self, w: &mut Writer) { + let bytes = self.0.to_be_bytes(); + w.write([29, bytes[0], bytes[1], bytes[2], bytes[3]]); + } +} + +impl Writeable for IntegerNumber { + fn write(&self, w: &mut Writer) { + if (-107..=107).contains(&self.0) { + let b0 = u8::try_from(self.0 + 139).unwrap(); + w.write(b0); + } else if (108..=1131).contains(&self.0) { + let temp = self.0 - 108; + let b0 = u8::try_from(temp / 256 + 247).unwrap(); + let b1 = u8::try_from(temp % 256).unwrap(); + w.write([b0, b1]); + } else if (-1131..=-108).contains(&self.0) { + let temp = -self.0 - 108; + let b0 = u8::try_from(temp / 256 + 251).unwrap(); + let b1 = u8::try_from(temp % 256).unwrap(); + w.write([b0, b1]) + } else if (-32768..=32767).contains(&self.0) { + let bytes = i16::try_from(self.0).unwrap().to_be_bytes(); + w.write([28, bytes[0], bytes[1]]) + } else { + self.write_as_5_bytes(w) + } + } +} + +#[derive(Clone, Copy)] +pub enum Number { + Real(RealNumber), + Integer(IntegerNumber), + Fixed(FixedNumber), +} + +impl Default for Number { + fn default() -> Self { + Number::zero() + } +} + +impl Debug for Number { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_f64()) + } +} + +impl Writeable for Number { + fn write(&self, w: &mut Writer) { + match self { + Number::Real(real_num) => real_num.write(w), + Number::Integer(int_num) => int_num.write(w), + Number::Fixed(fixed_num) => fixed_num.write(w), + } + } +} + +impl Number { + pub fn parse_cff_number(r: &mut Reader) -> Option { + Self::parse_number(r, false) + } + + pub fn parse_char_string_number(r: &mut Reader) -> Option { + Self::parse_number(r, true) + } + + fn parse_number(r: &mut Reader, charstring_num: bool) -> Option { + match r.peak::()? { + 30 => Some(Number::Real(RealNumber::parse(r)?)), + // FIXED only exists in charstrings. + 255 => { + if charstring_num { + return Some(Number::Fixed(FixedNumber::parse(r)?)); + } + + None + } + _ => Some(Number::Integer(IntegerNumber::parse(r)?)), + } + } + + pub fn from_i32(num: i32) -> Self { + Number::Integer(IntegerNumber(num)) + } + + pub fn from_f32(num: f32) -> Self { + Number::Real(RealNumber(num)) + } + + pub fn zero() -> Self { + Number::Integer(IntegerNumber(0)) + } + + pub fn one() -> Self { + Number::Integer(IntegerNumber(1)) + } + + pub fn as_f64(&self) -> f64 { + match self { + Number::Integer(int) => int.0 as f64, + Number::Real(real) => real.0 as f64, + Number::Fixed(fixed) => fixed.as_f32() as f64, + } + } + + pub fn as_i32(&self) -> Option { + match self { + Number::Integer(int) => Some(int.0), + Number::Real(rn) => { + if rn.0.fract() == 0.0 { + Some(rn.0 as i32) + } else { + None + } + } + Number::Fixed(fixn) => { + let num = fixn.as_f32(); + if num.fract() == 0.0 { + Some(num as i32) + } else { + None + } + } + } + } + + pub fn as_u32(&self) -> Option { + u32::try_from(self.as_i32()?).ok() + } +} + +fn parse_float_nibble(nibble: u8, mut idx: usize, data: &mut [u8]) -> Option { + if idx == FLOAT_STACK_LEN { + return None; + } + + match nibble { + 0..=9 => { + data[idx] = b'0' + nibble; + } + 10 => { + data[idx] = b'.'; + } + 11 => { + data[idx] = b'E'; + } + 12 => { + if idx + 1 == FLOAT_STACK_LEN { + return None; + } + + data[idx] = b'E'; + idx += 1; + data[idx] = b'-'; + } + 13 => { + return None; + } + 14 => { + data[idx] = b'-'; + } + _ => { + return None; + } + } + + idx += 1; + Some(idx) +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Debug, Hash, Ord)] +pub struct StringId(pub u16); + +impl StringId { + pub const STANDARD_STRING_LEN: u16 = 391; + + pub fn is_standard_string(&self) -> bool { + self.0 < Self::STANDARD_STRING_LEN + } +} + +impl Readable<'_> for StringId { + const SIZE: usize = u16::SIZE; + + fn read(r: &mut Reader<'_>) -> Option { + Some(Self(r.read::()?)) + } +} + +impl Writeable for StringId { + fn write(&self, w: &mut Writer) { + w.write::(self.0) + } +} + +impl From for StringId { + fn from(value: u16) -> Self { + Self(value) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct U24(pub u32); + +impl U24 { + pub const MAX: u32 = 16777215; +} + +impl Readable<'_> for U24 { + const SIZE: usize = 3; + + fn read(r: &mut Reader<'_>) -> Option { + let data = r.read::<[u8; 3]>()?; + Some(U24(u32::from_be_bytes([0, data[0], data[1], data[2]]))) + } +} + +impl Writeable for U24 { + fn write(&self, w: &mut Writer) { + let data = self.0.to_be_bytes(); + w.write::<[u8; 3]>([data[1], data[2], data[3]]); + } +} + +#[cfg(test)] +mod tests { + use crate::cff::number::*; + use crate::read::Reader; + + #[test] + fn u24() { + let nums = [0u32, 45, 345, 54045, 32849324, 16777215]; + + for num in nums { + let wrapped = U24(num); + + let mut w = Writer::new(); + w.write(wrapped); + let first = w.finish(); + + let mut r = Reader::new(&first); + let rewritten = r.read::().unwrap(); + let mut w = Writer::new(); + w.write(&rewritten); + let second = w.finish(); + + assert_eq!(first, second); + } + } + + #[test] + fn size1_roundtrip() { + let nums = [0, 1, -1, 93, 107, -107]; + + for num in nums { + let integer = IntegerNumber(num); + let mut w = Writer::new(); + w.write(integer); + let buffer = w.finish(); + let mut reader = Reader::new(&buffer); + + let reparsed = IntegerNumber::parse(&mut reader).unwrap(); + let mut w = Writer::new(); + w.write(&reparsed); + let bytes = w.finish(); + assert_eq!(bytes.len(), 1); + assert_eq!(reparsed.0, num); + } + } + + #[test] + fn size2_roundtrip() { + let nums = [108, -108, 255, -255, 349, -349, 845, -845, 1131, -1131]; + + for num in nums { + let integer = IntegerNumber(num); + let mut w = Writer::new(); + w.write(integer); + let buffer = w.finish(); + let mut reader = Reader::new(&buffer); + + let reparsed = IntegerNumber::parse(&mut reader).unwrap(); + let mut w = Writer::new(); + w.write(&reparsed); + let bytes = w.finish(); + assert_eq!(bytes.len(), 2); + assert_eq!(reparsed.0, num); + } + } + + #[test] + fn size3_roundtrip() { + let nums = [1132, -1132, 2450, -2450, 4096, -4096, 8965, -8965, 32767, -32768]; + + for num in nums { + let integer = IntegerNumber(num); + let mut w = Writer::new(); + w.write(integer); + let buffer = w.finish(); + let mut reader = Reader::new(&buffer); + + let reparsed = IntegerNumber::parse(&mut reader).unwrap(); + let mut w = Writer::new(); + w.write(&reparsed); + let bytes = w.finish(); + assert_eq!(bytes.len(), 3); + assert_eq!(reparsed.0, num); + } + } + + #[test] + fn size5_roundtrip() { + let nums = [32768, -32769, i32::MAX, i32::MIN]; + + for num in nums { + let integer = IntegerNumber(num); + let mut w = Writer::new(); + w.write(integer); + let buffer = w.finish(); + let mut reader = Reader::new(&buffer); + + let reparsed = IntegerNumber::parse(&mut reader).unwrap(); + let mut w = Writer::new(); + w.write(&reparsed); + let bytes = w.finish(); + assert_eq!(bytes.len(), 5); + assert_eq!(reparsed.0, num); + } + } + + #[test] + fn parse_float() { + let num = [0x1E, 0xE2, 0x49, 0x32, 0xA1, 0x2C, 0x2F]; + let mut r = Reader::new(&num); + let real = RealNumber::parse(&mut r).unwrap(); + assert_eq!(-249.3212, real.0); + } + + #[test] + fn float_roundtrip() { + let nums = [0.58f32, -0.21, 3.98, 16.49, 159.18, 5906.2]; + + for num in nums { + let float = RealNumber(num); + let mut w = Writer::new(); + w.write(float); + let buffer = w.finish(); + let mut reader = Reader::new(&buffer); + + let reparsed = RealNumber::parse(&mut reader).unwrap(); + assert_eq!(reparsed.0, num); + } + } + + #[test] + fn fixed() { + let num = [255u8, 154, 104, 120, 40]; + + let mut r = Reader::new(&num); + let parsed = FixedNumber::parse(&mut r).unwrap(); + + let mut w = Writer::new(); + w.write(parsed); + + assert_eq!(num, w.finish().as_ref()); + } +} diff --git a/src/cff/operator.rs b/src/cff/operator.rs new file mode 100644 index 00000000..0e0c72df --- /dev/null +++ b/src/cff/operator.rs @@ -0,0 +1,41 @@ +use crate::write::{Writeable, Writer}; +use std::fmt::{Display, Formatter}; + +pub const TWO_BYTE_OPERATOR_MARK: u8 = 12; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum OperatorType { + OneByteOperator([u8; 1]), + TwoByteOperator([u8; 2]), +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Operator(pub OperatorType); + +impl Display for Operator { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self.0 { + OperatorType::OneByteOperator(b) => write!(f, "{}", b[0]), + OperatorType::TwoByteOperator(b) => write!(f, "{}{}", b[0], b[1]), + } + } +} + +impl Writeable for Operator { + fn write(&self, w: &mut Writer) { + match &self.0 { + OperatorType::OneByteOperator(b) => w.write(b), + OperatorType::TwoByteOperator(b) => w.write(b), + } + } +} + +impl Operator { + pub const fn from_one_byte(b: u8) -> Self { + Self(OperatorType::OneByteOperator([b])) + } + + pub const fn from_two_byte(b: u8) -> Self { + Self(OperatorType::TwoByteOperator([TWO_BYTE_OPERATOR_MARK, b])) + } +} diff --git a/src/cff/remapper.rs b/src/cff/remapper.rs new file mode 100644 index 00000000..f6a33bfe --- /dev/null +++ b/src/cff/remapper.rs @@ -0,0 +1,96 @@ +use crate::cff::number::StringId; +use crate::remapper::Remapper; +use std::borrow::Cow; +use std::collections::{BTreeMap, HashMap}; + +pub type FontDictRemapper = Remapper; + +/// Remap old SIDs to new SIDs, and also allow the insertion of +/// new strings. +pub struct SidRemapper<'a> { + /// Next SID to be assigned. + counter: StringId, + /// A map from SIDs to their corresponding string. + sid_to_string: BTreeMap>, + /// A map from strings to their corresponding SID (so the reverse of `sid_to_string`). + string_to_sid: HashMap, StringId>, + /// A map from old SIDs to new SIDs. + old_sid_to_new_sid: HashMap, +} + +impl<'a> SidRemapper<'a> { + pub fn new() -> Self { + Self { + counter: StringId(StringId::STANDARD_STRING_LEN), + sid_to_string: BTreeMap::new(), + string_to_sid: HashMap::new(), + old_sid_to_new_sid: HashMap::new(), + } + } + + pub fn get(&self, string: &[u8]) -> Option { + self.string_to_sid.get(string).copied() + } + + /// Get the new SID to a correpsonding old SID. + pub fn get_new_sid(&self, sid: StringId) -> Option { + self.old_sid_to_new_sid.get(&sid).copied() + } + + /// Remap a string. + pub fn remap(&mut self, string: impl Into> + Clone) -> StringId { + *self.string_to_sid.entry(string.clone().into()).or_insert_with(|| { + let value = self.counter; + self.sid_to_string.insert(value, string.into()); + self.counter = + StringId(self.counter.0.checked_add(1).expect("sid remapper overflowed")); + + value + }) + } + + /// Remap an old SID and its corresponding string. + pub fn remap_with_old_sid( + &mut self, + sid: StringId, + string: impl Into> + Clone, + ) -> StringId { + if let Some(new_sid) = self.old_sid_to_new_sid.get(&sid) { + *new_sid + } else { + let new_sid = self.remap(string); + self.old_sid_to_new_sid.insert(sid, new_sid); + new_sid + } + } + + /// Returns an iterator over the strings, ordered by their new SID. + pub fn sorted_strings(&self) -> impl Iterator> + '_ { + self.sid_to_string.values() + } +} + +#[cfg(test)] +mod tests { + use crate::cff::number::StringId; + use crate::cff::remapper::SidRemapper; + use std::borrow::Cow; + + #[test] + fn test_remap_1() { + let mut sid_remapper = SidRemapper::new(); + assert_eq!(sid_remapper.remap(b"hi".to_vec()), StringId(391)); + assert_eq!(sid_remapper.remap(b"there".to_vec()), StringId(392)); + assert_eq!(sid_remapper.remap(b"hi".to_vec()), StringId(391)); + assert_eq!(sid_remapper.remap(b"test".to_vec()), StringId(393)); + + assert_eq!( + sid_remapper.sorted_strings().cloned().collect::>(), + vec![ + Cow::<[u8]>::Owned(b"hi".to_vec()), + Cow::Owned(b"there".to_vec()), + Cow::Owned(b"test".to_vec()) + ] + ) + } +} diff --git a/src/cff/sid_font.rs b/src/cff/sid_font.rs new file mode 100644 index 00000000..58707d5a --- /dev/null +++ b/src/cff/sid_font.rs @@ -0,0 +1,57 @@ +use crate::cff::dict::private_dict::parse_subr_offset; +use crate::cff::dict::top_dict::TopDictData; +use crate::cff::index::{parse_index, Index}; +use crate::read::Reader; +use crate::write::Writer; +use crate::GlyphRemapper; + +/// Metadata required for handling SID-keyed fonts. +#[derive(Clone, Copy, Default, Debug)] +pub struct SIDMetadata<'a> { + pub local_subrs: Index<'a>, + pub private_dict_data: &'a [u8], +} + +// The parsing logic was taken from ttf-parser. +pub fn parse_sid_metadata<'a>(data: &'a [u8], top_dict: &TopDictData) -> SIDMetadata<'a> { + top_dict + .private + .clone() + .and_then(|private_dict_range| { + let mut metadata = SIDMetadata::default(); + let private_dict_data = data.get(private_dict_range.clone())?; + + metadata.local_subrs = + if let Some(subrs_offset) = parse_subr_offset(private_dict_data) { + let start = private_dict_range.start.checked_add(subrs_offset)?; + let subrs_data = data.get(start..)?; + let mut r = Reader::new(subrs_data); + parse_index::(&mut r)? + } else { + Index::default() + }; + + metadata.private_dict_data = private_dict_data; + Some(metadata) + }) + .unwrap_or_default() +} + +/// Write the FD INDEX for SID-keyed fonts. +/// They all get mapped to the font DICT 0. +pub fn generate_fd_index( + gid_remapper: &GlyphRemapper, + w: &mut Writer, +) -> crate::Result<()> { + // Format + w.write::(3); + // nRanges + w.write::(1); + // first + w.write::(0); + // fd index + w.write::(0); + // sentinel + w.write::(gid_remapper.num_gids()); + Ok(()) +} diff --git a/src/cff/subroutines.rs b/src/cff/subroutines.rs new file mode 100644 index 00000000..2eeb044d --- /dev/null +++ b/src/cff/subroutines.rs @@ -0,0 +1,77 @@ +use crate::cff::charstring::CharString; + +/// A wrapper over a vector of subroutine containers (for local subroutines, where +/// we have a list of subroutines for each font dict). +pub struct SubroutineCollection<'a> { + subroutines: Vec>, +} + +impl<'a> SubroutineCollection<'a> { + pub fn new(subroutines: Vec>>) -> Self { + debug_assert!(subroutines.len() <= 255); + Self { + subroutines: subroutines.into_iter().map(SubroutineContainer::new).collect(), + } + } + + pub fn get_handler(&self, fd_index: u8) -> Option { + self.subroutines.get(fd_index as usize).map(|s| s.get_handler()) + } +} + +/// A wrapper over a vector of charstrings (for global subroutines). +pub struct SubroutineContainer<'a> { + subroutines: Vec>, +} + +impl<'a> SubroutineContainer<'a> { + pub fn new(subroutines: Vec>) -> Self { + Self { subroutines } + } + + pub fn get_handler(&self) -> SubroutineHandler { + SubroutineHandler::new(self.subroutines.as_ref()) + } +} + +/// Wrapper over a list of subroutines to allow for convenient access to subroutines +/// given a biased or unbiased index. +#[derive(Clone)] +pub struct SubroutineHandler<'a> { + subroutines: &'a [CharString<'a>], + bias: u16, +} + +impl<'a> SubroutineHandler<'a> { + pub fn new(char_strings: &'a [CharString<'a>]) -> Self { + Self { + subroutines: char_strings, + bias: calc_subroutine_bias(char_strings.len() as u32), + } + } + + pub fn get_with_biased(&self, index: i32) -> Option> { + self.get_with_unbiased(unapply_bias(index, self.bias)?) + } + + pub fn get_with_unbiased(&self, index: u32) -> Option> { + self.subroutines.get(index as usize).copied() + } +} + +fn calc_subroutine_bias(len: u32) -> u16 { + if len < 1240 { + 107 + } else if len < 33900 { + 1131 + } else { + 32768 + } +} + +/// Unapply the bias from a biased subroutine offset. +pub fn unapply_bias(index: i32, bias: u16) -> Option { + let bias = i32::from(bias); + + u32::try_from(index.checked_add(bias)?).ok() +} diff --git a/src/glyf.rs b/src/glyf.rs index 6825a221..3f8af668 100644 --- a/src/glyf.rs +++ b/src/glyf.rs @@ -1,5 +1,82 @@ +//! The `glyf` table contains the main description of the glyphs. In order to +//! subset it, there are 5 things we need to do: +//! 1. We need to form the glyph closure. Glyphs can reference other glyphs, meaning that +//! if a user for example requests the glyph 1, and this glyph references the glyph 2, then +//! we need to include both of them in our subset. +//! 2. We need to remove glyph descriptions that are not needed for the subset, and reorder +//! the existing glyph descriptions to match the order defined by the remapper. +//! 3. For component glyphs, we need to rewrite their description so that they reference +//! the new glyph ID of the glyphs they reference. +//! 4. We need to calculate which format to use in the `loca` table. +//! 5. We need to update the `loca` table itself with the new offsets. use super::*; +/// Form the glyph closure of all glyphs in `gid_set`. +pub fn closure(face: &Face, glyph_remapper: &mut GlyphRemapper) -> Result<()> { + let table = Table::new(face).ok_or(MalformedFont)?; + + let mut process_glyphs = glyph_remapper.remapped_gids().collect::>(); + + while let Some(glyph) = process_glyphs.pop() { + glyph_remapper.remap(glyph); + + let glyph_data = match table.glyph_data(glyph) { + Some(glph_data) => glph_data, + None => continue, + }; + + if glyph_data.is_empty() { + continue; + } + + let mut r = Reader::new(glyph_data); + let num_contours = r.read::().ok_or(MalformedFont)?; + + // If we have a composite glyph, add its components to the closure. + if num_contours < 0 { + for component in component_glyphs(glyph_data).ok_or(MalformedFont)? { + if glyph_remapper.get(component).is_none() { + process_glyphs.push(component); + } + } + } + } + + Ok(()) +} + +pub fn subset(ctx: &mut Context) -> Result<()> { + let subsetted_entries = subset_glyf_entries(ctx)?; + + let mut sub_glyf = Writer::new(); + let mut sub_loca = Writer::new(); + + let mut write_offset = |offset: usize| { + if ctx.long_loca { + sub_loca.write::(offset as u32); + } else { + sub_loca.write::((offset / 2) as u16); + } + }; + + for entry in &subsetted_entries { + write_offset(sub_glyf.len()); + sub_glyf.extend(entry); + + if !ctx.long_loca { + sub_glyf.align(2); + } + } + + // Write the final offset. + write_offset(sub_glyf.len()); + + ctx.push(Tag::LOCA, sub_loca.finish()); + ctx.push(Tag::GLYF, sub_glyf.finish()); + + Ok(()) +} + /// A glyf + loca table. struct Table<'a> { loca: &'a [u8], @@ -8,145 +85,181 @@ struct Table<'a> { } impl<'a> Table<'a> { - fn new(ctx: &Context<'a>) -> Result { - let loca = ctx.expect_table(Tag::LOCA)?; - let glyf = ctx.expect_table(Tag::GLYF)?; - let head = ctx.expect_table(Tag::HEAD)?; - let long = i16::read_at(head, 50)? != 0; - Ok(Self { loca, glyf, long }) + fn new(face: &Face<'a>) -> Option { + let loca = face.table(Tag::LOCA)?; + let glyf = face.table(Tag::GLYF)?; + let head = face.table(Tag::HEAD)?; + + let mut r = Reader::new_at(head, 50); + let long = r.read::()? != 0; + Some(Self { loca, glyf, long }) } - fn glyph_data(&self, id: u16) -> Result<&'a [u8]> { + fn glyph_data(&self, id: u16) -> Option<&'a [u8]> { let read_offset = |n| { - Ok(if self.long { - u32::read_at(self.loca, 4 * n)? as usize + Some(if self.long { + let mut r = Reader::new_at(self.loca, 4 * n); + r.read::()? as usize } else { - u16::read_at(self.loca, 2 * n)? as usize * 2 + let mut r = Reader::new_at(self.loca, 2 * n); + 2 * r.read::()? as usize }) }; let from = read_offset(id as usize)?; let to = read_offset(id as usize + 1)?; - self.glyf.get(from..to).ok_or(Error::InvalidOffset) + self.glyf.get(from..to) } } -/// Find all glyphs referenced through components. -pub(crate) fn discover(ctx: &mut Context) -> Result<()> { - let table = Table::new(ctx)?; - - // Because glyphs may depend on other glyphs as components (also with - // multiple layers of nesting), we have to process all glyphs to find - // their components. - let mut iter = ctx.profile.glyphs.iter().copied(); - let mut work = vec![0]; - - // Find composite glyph descriptions. - while let Some(id) = work.pop().or_else(|| iter.next()) { - if ctx.subset.insert(id) { - let mut r = Reader::new(table.glyph_data(id)?); - if let Ok(num_contours) = r.read::() { - // Negative means this is a composite glyph. - if num_contours < 0 { - // Skip min/max metrics. - r.read::()?; - r.read::()?; - r.read::()?; - r.read::()?; - - // Read component glyphs. - work.extend(component_glyphs(r)); - } - } - } - } +fn subset_glyf_entries<'a>(ctx: &mut Context<'a>) -> Result>> { + let table = Table::new(&ctx.face).ok_or(MalformedFont)?; - // Compute combined size of all glyphs to select loca format. let mut size = 0; - for &id in &ctx.subset { - let mut len = table.glyph_data(id)?.len(); + let mut glyf_entries = vec![]; + + for old_gid in ctx.mapper.remapped_gids() { + let glyph_data = table.glyph_data(old_gid).ok_or(MalformedFont)?; + + // Empty glyph. + if glyph_data.is_empty() { + glyf_entries.push(Cow::Borrowed(glyph_data)); + continue; + } + + let mut r = Reader::new(glyph_data); + let num_contours = r.read::().ok_or(MalformedFont)?; + + let glyph_data = if num_contours < 0 { + Cow::Owned(remap_component_glyph(&ctx.mapper, glyph_data)?) + } else { + // Simple glyphs don't need any subsetting. + Cow::Borrowed(glyph_data) + }; + + let mut len = glyph_data.len(); len += (len % 2 != 0) as usize; size += len; + + glyf_entries.push(glyph_data); } + // Decide on the loca format. ctx.long_loca = size > 2 * (u16::MAX as usize); - Ok(()) + Ok(glyf_entries) } -/// Returns an iterator over the component glyphs referenced by the given -/// `glyf` table composite glyph description. -fn component_glyphs(mut r: Reader) -> impl Iterator + '_ { +fn remap_component_glyph(mapper: &GlyphRemapper, data: &[u8]) -> Result> { + let mut r = Reader::new(data); + let mut w = Writer::with_capacity(data.len()); + + // number of contours + w.write(r.read::().ok_or(MalformedFont)?); + + // xMin, yMin, xMax, yMax + w.write(r.read::().ok_or(MalformedFont)?); + w.write(r.read::().ok_or(MalformedFont)?); + w.write(r.read::().ok_or(MalformedFont)?); + w.write(r.read::().ok_or(MalformedFont)?); + const ARG_1_AND_2_ARE_WORDS: u16 = 0x0001; const WE_HAVE_A_SCALE: u16 = 0x0008; const MORE_COMPONENTS: u16 = 0x0020; const WE_HAVE_AN_X_AND_Y_SCALE: u16 = 0x0040; const WE_HAVE_A_TWO_BY_TWO: u16 = 0x0080; + const WE_HAVE_INSTRUCTIONS: u16 = 0x0100; - let mut done = false; - std::iter::from_fn(move || { - if done { - return None; - } + let mut done; - let flags = r.read::().ok()?; - let component = r.read::().ok()?; + loop { + let flags = r.read::().ok_or(MalformedFont)?; + w.write(flags); + let old_component = r.read::().ok_or(MalformedFont)?; + let new_component = mapper.get(old_component).ok_or(MalformedFont)?; + w.write(new_component); if flags & ARG_1_AND_2_ARE_WORDS != 0 { - r.read::().ok()?; - r.read::().ok()?; + w.write(r.read::().ok_or(MalformedFont)?); + w.write(r.read::().ok_or(MalformedFont)?); } else { - r.read::().ok()?; + w.write(r.read::().ok_or(MalformedFont)?); } if flags & WE_HAVE_A_SCALE != 0 { - r.read::().ok()?; + w.write(r.read::().ok_or(MalformedFont)?); } else if flags & WE_HAVE_AN_X_AND_Y_SCALE != 0 { - r.read::().ok()?; - r.read::().ok()?; + w.write(r.read::().ok_or(MalformedFont)?); + w.write(r.read::().ok_or(MalformedFont)?); } else if flags & WE_HAVE_A_TWO_BY_TWO != 0 { - r.read::().ok()?; - r.read::().ok()?; - r.read::().ok()?; - r.read::().ok()?; + w.write(r.read::().ok_or(MalformedFont)?); + w.write(r.read::().ok_or(MalformedFont)?); + w.write(r.read::().ok_or(MalformedFont)?); + w.write(r.read::().ok_or(MalformedFont)?); } done = flags & MORE_COMPONENTS == 0; - Some(component) - }) -} -/// Subset the glyf and loca tables by removing glyph data for unused glyphs. -pub(crate) fn subset(ctx: &mut Context) -> Result<()> { - let table = Table::new(ctx)?; + if done { + if flags & WE_HAVE_INSTRUCTIONS != 0 { + w.write(r.tail().ok_or(MalformedFont)?); + } - let mut sub_glyf = Writer::new(); - let mut sub_loca = Writer::new(); - let mut write_offset = |offset: usize| { - if ctx.long_loca { - sub_loca.write::(offset as u32); - } else { - sub_loca.write::((offset / 2) as u16); + break; } - }; + } - for id in 0..ctx.num_glyphs { - // If the glyph shouldn't be contained in the subset, it will - // still get a loca entry, but the glyf data is simply empty. - write_offset(sub_glyf.len()); - if ctx.subset.contains(&id) { - let data = table.glyph_data(id)?; - sub_glyf.give(data); - if !ctx.long_loca { - sub_glyf.align(2); - } + Ok(w.finish()) +} + +/// Returns an iterator over the component glyphs of a glyph. +fn component_glyphs(glyph_data: &[u8]) -> Option + '_> { + let mut r = Reader::new(glyph_data); + + // Number of contours + r.read::()?; + + // xMin, yMin, xMax, yMax + r.read::()?; + r.read::()?; + r.read::()?; + r.read::()?; + + const ARG_1_AND_2_ARE_WORDS: u16 = 0x0001; + const WE_HAVE_A_SCALE: u16 = 0x0008; + const MORE_COMPONENTS: u16 = 0x0020; + const WE_HAVE_AN_X_AND_Y_SCALE: u16 = 0x0040; + const WE_HAVE_A_TWO_BY_TWO: u16 = 0x0080; + + let mut done = false; + Some(std::iter::from_fn(move || { + if done { + return None; } - } - write_offset(sub_glyf.len()); + let flags = r.read::()?; + let component = r.read::()?; - ctx.push(Tag::LOCA, sub_loca.finish()); - ctx.push(Tag::GLYF, sub_glyf.finish()); + if flags & ARG_1_AND_2_ARE_WORDS != 0 { + r.read::(); + r.read::(); + } else { + r.read::(); + } - Ok(()) + if flags & WE_HAVE_A_SCALE != 0 { + r.read::(); + } else if flags & WE_HAVE_AN_X_AND_Y_SCALE != 0 { + r.read::(); + r.read::(); + } else if flags & WE_HAVE_A_TWO_BY_TWO != 0 { + r.read::(); + r.read::(); + r.read::(); + r.read::(); + } + + done = flags & MORE_COMPONENTS == 0; + Some(component) + })) } diff --git a/src/head.rs b/src/head.rs index ce35ba5b..9814af83 100644 --- a/src/head.rs +++ b/src/head.rs @@ -1,11 +1,12 @@ +//! The `head` table mostly contains information that can be reused from the +//! old table, except for the `loca` format, which depends on the size of the +//! glyph data. The checksum will be recalculated in the very end. + use super::*; -/// Subset the head table. -/// -/// Updates the loca format. -pub(crate) fn subset(ctx: &mut Context) -> Result<()> { - let mut head = ctx.expect_table(Tag::HEAD)?.to_vec(); - let index_to_loc = head.get_mut(50..52).ok_or(Error::InvalidOffset)?; +pub fn subset(ctx: &mut Context) -> Result<()> { + let mut head = ctx.expect_table(Tag::HEAD).ok_or(MalformedFont)?.to_vec(); + let index_to_loc = head.get_mut(50..52).ok_or(MalformedFont)?; index_to_loc[0] = 0; index_to_loc[1] = ctx.long_loca as u8; ctx.push(Tag::HEAD, head); diff --git a/src/hmtx.rs b/src/hmtx.rs index 8b9208cc..2c87d7e4 100644 --- a/src/hmtx.rs +++ b/src/hmtx.rs @@ -1,35 +1,95 @@ +//! The `hmtx` table contains the horizontal metrics for each glyph. +//! All we need to do is to rewrite the table so that it matches the +//! sequence of the new glyphs. A minor pain point is that the table +//! allows omitting the advance width for the last few glyphs if it is +//! the same. In order to keep the code simple, we do not keep this optimization +//! when rewriting the table. +//! While doing so, we also rewrite the `hhea` table, which contains +//! the number of glyphs that contain both, advance width and +//! left side bearing metrics. + +// The parsing logic was taken from ttf-parser. + use super::*; +use crate::Error::OverflowError; + +pub fn subset(ctx: &mut Context) -> Result<()> { + let hmtx = ctx.expect_table(Tag::HMTX).ok_or(MalformedFont)?; + + let new_metrics = { + let mut new_metrics = vec![]; + + // Extract the number of horizontal metrics from the `hhea` table. + let num_h_metrics = { + let hhea = ctx.expect_table(Tag::HHEA).ok_or(MalformedFont)?; + let mut r = Reader::new(hhea); + r.skip_bytes(34); + r.read::().ok_or(MalformedFont)? + }; + + let last_advance_width = { + let index = 4 * num_h_metrics.checked_sub(1).ok_or(OverflowError)? as usize; + let mut r = Reader::new(hmtx.get(index..).ok_or(MalformedFont)?); + r.read::().ok_or(MalformedFont)? + }; + + for old_gid in ctx.mapper.remapped_gids() { + let has_advance_width = old_gid < num_h_metrics; + + let offset = if has_advance_width { + old_gid as usize * 4 + } else { + let num_h_metrics = num_h_metrics as usize; + num_h_metrics * 4 + (old_gid as usize - num_h_metrics) * 2 + }; -/// Subset the htmx table. -/// -/// We can't change anything about its size, but we can zero out all metrics -/// for unused glyphs so that it compresses better when embedded into a PDF. -pub(crate) fn subset(ctx: &mut Context) -> Result<()> { - let num_h_metrics = { - let hhea = ctx.expect_table(Tag::HHEA)?; - let mut r = Reader::new(hhea); - r.skip(34)?; - r.read::()? + let mut r = Reader::new(hmtx.get(offset..).ok_or(MalformedFont)?); + + if has_advance_width { + let adv = r.read::().ok_or(MalformedFont)?; + let lsb = r.read::().ok_or(MalformedFont)?; + new_metrics.push((adv, lsb)); + } else { + new_metrics + .push((last_advance_width, r.read::().ok_or(MalformedFont)?)); + } + } + + new_metrics }; - let mut hmtx = ctx.expect_table(Tag::HMTX)?.to_vec(); + // Find out the last index we need to include the advance width for. + let mut last_advance_width_index = + u16::try_from(new_metrics.len()).map_err(|_| OverflowError)? - 1; + let last_advance_width = new_metrics[last_advance_width_index as usize].0; - let mut offset = 0; - for i in 0..num_h_metrics { - if !ctx.subset.contains(&i) { - hmtx.get_mut(offset..offset + 4).ok_or(Error::MissingData)?.fill(0); + for gid in new_metrics.iter().rev().skip(1) { + if gid.0 == last_advance_width { + last_advance_width_index -= 1; + } else { + break; } - offset += 4; } - for i in num_h_metrics..ctx.num_glyphs { - if !ctx.subset.contains(&i) { - hmtx.get_mut(offset..offset + 2).ok_or(Error::MissingData)?.fill(0); + let mut sub_hmtx = Writer::new(); + + for (index, metric) in new_metrics.iter().enumerate() { + let index = u16::try_from(index).map_err(|_| OverflowError)?; + if index <= last_advance_width_index { + sub_hmtx.write::(metric.0); } - offset += 2; + + sub_hmtx.write::(metric.1); } - ctx.push(Tag::HMTX, hmtx); + ctx.push(Tag::HMTX, sub_hmtx.finish()); + + let hhea = ctx.expect_table(Tag::HHEA).ok_or(MalformedFont)?; + let mut sub_hhea = Writer::new(); + sub_hhea.extend(&hhea[0..hhea.len() - 2]); + sub_hhea.write::(last_advance_width_index + 1); + + ctx.push(Tag::HHEA, sub_hhea.finish()); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index cff518b3..c4144608 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,22 +1,61 @@ /*! -Reduces the size and coverage of OpenType fonts with TrueType or CFF outlines. +Reduces the size and coverage of OpenType fonts with TrueType or CFF outlines for embedding +in PDFs. You can in general expect very good results in terms of font size, as most of the things +that can be subsetted are also subsetted. + +# Scope +**Note that the resulting font subsets will most likely be unusable in any other contexts than PDF writing, +since a lot of information will be removed from the font which is not necessary in PDFs, but is +necessary in other contexts.** This is on purpose, and for now, there are no plans to expand the +scope of this crate to become a general purpose subsetter, as this is a massive undertaking and +will make the already complex codebase even more complex. + +In the future, +[klippa](https://github.com/googlefonts/fontations/tree/main/klippa) will hopefully fill the gap +of a general-purpose subsetter in the Rust ecosystem. + +# Notes +A couple of important notes if you want to use this crate in combination with your own pdf writer: + +- You must write your fonts as a CID font. This is because we remove the `cmap` table from the font, +so you must provide your own cmap table in the PDF. +- Copyright information in the font will be retained. +- When writing a CID font in PDF, CIDs must be used to address glyphs. This can be pretty tricky, +because the meaning of CID depends on the type of font you are embedding (see the PDF specification +for more information). The subsetter will convert SID-keyed fonts to CID-keyed ones and an identity +mapping from GID to CID for all fonts, regardless of the previous mapping. Because of this, you can +always use the remapped GID as the CID for a glyph, and do not need to worry about the type of font +you are embedding. # Example -In the example below, we remove all glyphs except the ones with IDs 68, 69, 70. -Those correspond to the letters 'a', 'b' and 'c'. +In the example below, we remove all glyphs except the ones with IDs 68, 69, 70 from Noto Sans. +Those correspond to the letters 'a', 'b' and 'c'. We then save the resulting font to disk. ``` -use subsetter::{subset, Profile}; +use subsetter::{subset, GlyphRemapper}; # fn main() -> Result<(), Box> { // Read the raw font data. let data = std::fs::read("fonts/NotoSans-Regular.ttf")?; -// Keep only three glyphs and the OpenType tables -// required for embedding the font in a PDF file. +// These are the glyphs we want to keep. let glyphs = &[68, 69, 70]; -let profile = Profile::pdf(glyphs); -let sub = subset(&data, 0, profile)?; + +// Create a new glyph remapper that remaps our glyphs to new glyph IDs. This is necessary because +// glyph IDs in fonts must be consecutive. So if we only include the glyphs 68, 69, 70 and remove all +// other glyph IDs, in the new font they will have the glyph IDs 1, 2, 3. +let mut remapper = GlyphRemapper::new(); +for glyph in glyphs { + remapper.remap(*glyph); +} + +// Create the subset. +let sub = subset(&data, 0, &remapper)?; + +// This is how you can access the new glyph ID of a glyph in the old font. +for glyph in glyphs { + println!("Glyph {} has the ID {} in the new font", *glyph, remapper.get(*glyph).unwrap()); +} // Write the resulting file. std::fs::write("target/Noto-Small.ttf", sub)?; @@ -24,17 +63,12 @@ std::fs::write("target/Noto-Small.ttf", sub)?; # } ``` -Notably, this subsetter does not really remove glyphs, just their outlines. This -means that you don't have to worry about changed glyphs IDs. However, it also -means that the resulting font won't always be as small as possible. To somewhat -remedy this, this crate sometimes at least zeroes out unused data that it cannot -fully remove. This helps if the font gets compressed, for example when embedding -it in a PDF file. - -In the above example, the original font was 375 KB (188 KB zipped) while the -resulting font is 36 KB (5 KB zipped). +In the above example, the original font was 556 KB, while the +resulting font is 1 KB. */ +// TODO: Add example that shows how to use it in combination with PDF. + #![deny(unsafe_code)] #![deny(missing_docs)] @@ -42,98 +76,88 @@ mod cff; mod glyf; mod head; mod hmtx; +mod maxp; +mod name; mod post; -mod stream; - +mod read; +mod remapper; +mod write; + +use crate::read::{Readable, Reader}; +pub use crate::remapper::GlyphRemapper; +use crate::write::{Writeable, Writer}; +use crate::Error::{MalformedFont, UnknownKind}; use std::borrow::Cow; -use std::collections::HashSet; use std::fmt::{self, Debug, Display, Formatter}; -use crate::stream::{Reader, Structure, Writer}; - -/// Defines which things to keep in the font. -/// -/// #### Possible Future Work -/// - A setter for variation coordinates which would make the subsetter create a -/// static instance of a variable font. -/// - A profile which keeps and subsets bitmap, color and SVG tables. -/// - A profile which takes a char set instead of a glyph set and subsets the -/// layout tables. -pub struct Profile<'a> { - glyphs: &'a [u16], -} - -impl<'a> Profile<'a> { - /// Reduces the font to the subset needed for PDF embedding. - /// - /// Keeps only the basic required tables plus either the TrueType-related or - /// CFF-related tables. - /// - /// In particular, this removes: - /// - all text-layout related tables like GSUB and GPOS as it is expected - /// that text was already mapped to glyphs. - /// - all non-outline glyph descriptions like bitmaps, layered color glyphs - /// and SVGs. - /// - /// The subsetted font can be embedded in a PDF as a `FontFile3` with - /// Subtype `OpenType`. Alternatively: - /// - For TrueType outlines: You can embed it as a `FontFile2`. - /// - For CFF outlines: You can extract the CFF table and embed just the - /// table as a `FontFile3` with Subtype `Type1C` - pub fn pdf(glyphs: &'a [u16]) -> Self { - Self { glyphs } - } -} - -/// Subset a font face to include less glyphs and tables. +/// Subset the font face to include only the necessary glyphs and tables. /// /// - The `data` must be in the OpenType font format. /// - The `index` is only relevant if the data contains a font collection /// (`.ttc` or `.otc` file). Otherwise, it should be 0. -pub fn subset(data: &[u8], index: u32, profile: Profile) -> Result> { +pub fn subset(data: &[u8], index: u32, mapper: &GlyphRemapper) -> Result> { + let mapper = mapper.clone(); + let context = prepare_context(data, index, mapper)?; + _subset(context) +} + +fn prepare_context( + data: &[u8], + index: u32, + mut gid_remapper: GlyphRemapper, +) -> Result { let face = parse(data, index)?; - let kind = match face.table(Tag::CFF).or(face.table(Tag::CFF2)) { - Some(_) => FontKind::Cff, - None => FontKind::TrueType, + let kind = match (face.table(Tag::GLYF), face.table(Tag::CFF)) { + (Some(_), _) => FontKind::TrueType, + (_, Some(_)) => FontKind::Cff, + _ => return Err(UnknownKind), }; - let maxp = face.table(Tag::MAXP).ok_or(Error::MissingTable(Tag::MAXP))?; - let num_glyphs = u16::read_at(maxp, 4)?; + if kind == FontKind::TrueType { + glyf::closure(&face, &mut gid_remapper)?; + } - let mut ctx = Context { + Ok(Context { face, - num_glyphs, - subset: HashSet::new(), - profile, + mapper: gid_remapper, kind, tables: vec![], - long_loca: true, - }; + long_loca: false, + }) +} + +fn _subset(mut ctx: Context) -> Result> { + // See here for the required tables: + // https://learn.microsoft.com/en-us/typography/opentype/spec/otff#required-tables + // but some of those are not strictly needed according to the PDF specification. + + // Of the above tables, we are not including the following ones: + // - CFF2: Since we don't support CFF2 + // - VORG: PDF doesn't use that table. + // - CMAP: CID fonts in PDF define their own cmaps, so we don't need to include them in the font. + // - GASP: Not mandated by PDF specification, and ghostscript also seems to exclude them. + // - OS2: Not mandated by PDF specification, and ghostscript also seems to exclude them. if ctx.kind == FontKind::TrueType { - glyf::discover(&mut ctx)?; + // LOCA will be handled by GLYF ctx.process(Tag::GLYF)?; - ctx.process(Tag::CVT)?; - ctx.process(Tag::FPGM)?; - ctx.process(Tag::PREP)?; - ctx.process(Tag::GASP)?; + ctx.process(Tag::CVT)?; // won't be subsetted. + ctx.process(Tag::FPGM)?; // won't be subsetted. + ctx.process(Tag::PREP)?; // won't be subsetted. } if ctx.kind == FontKind::Cff { - cff::discover(&mut ctx); ctx.process(Tag::CFF)?; - ctx.process(Tag::CFF2)?; - ctx.process(Tag::VORG)?; } // Required tables. - ctx.process(Tag::CMAP)?; ctx.process(Tag::HEAD)?; - ctx.process(Tag::HHEA)?; ctx.process(Tag::HMTX)?; ctx.process(Tag::MAXP)?; + // NAME is also not strictly needed, and ghostscript removes it when subsetting. + // However, it contains copyright information which probably should not be removed... + // Even though it can free up a lot of space for some fonts. ctx.process(Tag::NAME)?; - ctx.process(Tag::OS2)?; ctx.process(Tag::POST)?; Ok(construct(ctx)) @@ -142,42 +166,44 @@ pub fn subset(data: &[u8], index: u32, profile: Profile) -> Result> { /// Parse a font face from OpenType data. fn parse(data: &[u8], index: u32) -> Result> { let mut r = Reader::new(data); - let mut kind = r.read::()?; + let mut kind = r.read::().ok_or(UnknownKind)?; // Parse font collection header if necessary. if kind == FontKind::Collection { - let offset = u32::read_at(data, 12 + 4 * (index as usize))?; - let subdata = data.get(offset as usize..).ok_or(Error::InvalidOffset)?; + r = Reader::new_at(data, 12 + 4 * (index as usize)); + let offset = r.read::().ok_or(MalformedFont)?; + let subdata = data.get(offset as usize..).ok_or(MalformedFont)?; r = Reader::new(subdata); - kind = r.read::()?; + kind = r.read::().ok_or(MalformedFont)?; + + // Cannot have nested collection if kind == FontKind::Collection { - return Err(Error::UnknownKind); + return Err(MalformedFont); } } // Read number of table records. - let count = r.read::()?; - r.read::()?; - r.read::()?; - r.read::()?; + let count = r.read::().ok_or(MalformedFont)?; + r.read::().ok_or(MalformedFont)?; + r.read::().ok_or(MalformedFont)?; + r.read::().ok_or(MalformedFont)?; // Read table records. let mut records = vec![]; for _ in 0..count { - records.push(r.read::()?); + records.push(r.read::().ok_or(MalformedFont)?); } Ok(Face { data, records }) } -/// Construct a brand new font. +/// Construct a brand-new font. fn construct(mut ctx: Context) -> Vec { + ctx.tables.sort_by_key(|&(tag, _)| tag); + let mut w = Writer::new(); w.write::(ctx.kind); - // Tables shall be sorted by tag. - ctx.tables.sort_by_key(|&(tag, _)| tag); - // Write table directory. let count = ctx.tables.len() as u16; let entry_selector = (count as f32).log2().floor() as u16; @@ -210,9 +236,6 @@ fn construct(mut ctx: Context) -> Vec { length: len as u32, }); - #[cfg(test)] - eprintln!("{}: {}", tag, len); - // Increase offset, plus padding zeros to align to 4 bytes. offset += len; while offset % 4 != 0 { @@ -223,7 +246,7 @@ fn construct(mut ctx: Context) -> Vec { // Write tables. for (_, data) in &ctx.tables { // Write data plus padding zeros to align to 4 bytes. - w.give(data); + w.extend(data); w.align(4); } @@ -253,16 +276,10 @@ fn checksum(data: &[u8]) -> u32 { /// Subsetting context. struct Context<'a> { - /// Original fa'ce. + /// Original face. face: Face<'a>, - /// The number of glyphs in the original and subsetted face. - /// - /// Subsetting doesn't actually delete glyphs, just their outlines. - num_glyphs: u16, - /// The kept glyphs. - subset: HashSet, - /// The subsetting profile. - profile: Profile<'a>, + /// A map from old gids to new gids, and the reverse + mapper: GlyphRemapper, /// The kind of face. kind: FontKind, /// Subsetted tables. @@ -273,8 +290,8 @@ struct Context<'a> { impl<'a> Context<'a> { /// Expect a table. - fn expect_table(&self, tag: Tag) -> Result<&'a [u8]> { - self.face.table(tag).ok_or(Error::MissingTable(tag)) + fn expect_table(&self, tag: Tag) -> Option<&'a [u8]> { + self.face.table(tag) } /// Process a table. @@ -289,8 +306,11 @@ impl<'a> Context<'a> { Tag::LOCA => panic!("handled by glyf"), Tag::CFF => cff::subset(self)?, Tag::HEAD => head::subset(self)?, + Tag::HHEA => panic!("handled by hmtx"), Tag::HMTX => hmtx::subset(self)?, Tag::POST => post::subset(self)?, + Tag::MAXP => maxp::subset(self)?, + Tag::NAME => name::subset(self)?, _ => self.push(tag, data), } @@ -334,16 +354,20 @@ enum FontKind { Collection, } -impl Structure<'_> for FontKind { - fn read(r: &mut Reader) -> Result { +impl Readable<'_> for FontKind { + const SIZE: usize = u32::SIZE; + + fn read(r: &mut Reader) -> Option { match r.read::()? { - 0x00010000 | 0x74727565 => Ok(FontKind::TrueType), - 0x4F54544F => Ok(FontKind::Cff), - 0x74746366 => Ok(FontKind::Collection), - _ => Err(Error::UnknownKind), + 0x00010000 | 0x74727565 => Some(FontKind::TrueType), + 0x4F54544F => Some(FontKind::Cff), + 0x74746366 => Some(FontKind::Collection), + _ => None, } } +} +impl Writeable for FontKind { fn write(&self, w: &mut Writer) { w.write::(match self { FontKind::TrueType => 0x00010000, @@ -394,11 +418,15 @@ impl Tag { const SVG: Self = Self(*b"SVG "); } -impl Structure<'_> for Tag { - fn read(r: &mut Reader) -> Result { +impl Readable<'_> for Tag { + const SIZE: usize = u8::SIZE * 4; + + fn read(r: &mut Reader) -> Option { r.read::<[u8; 4]>().map(Self) } +} +impl Writeable for Tag { fn write(&self, w: &mut Writer) { w.write::<[u8; 4]>(self.0) } @@ -425,16 +453,20 @@ struct TableRecord { length: u32, } -impl Structure<'_> for TableRecord { - fn read(r: &mut Reader) -> Result { - Ok(TableRecord { +impl Readable<'_> for TableRecord { + const SIZE: usize = Tag::SIZE + u32::SIZE + u32::SIZE + u32::SIZE; + + fn read(r: &mut Reader) -> Option { + Some(TableRecord { tag: r.read::()?, checksum: r.read::()?, offset: r.read::()?, length: r.read::()?, }) } +} +impl Writeable for TableRecord { fn write(&self, w: &mut Writer) { w.write::(self.tag); w.write::(self.checksum); @@ -446,11 +478,15 @@ impl Structure<'_> for TableRecord { /// A signed 16-bit fixed-point number. struct F2Dot14(u16); -impl Structure<'_> for F2Dot14 { - fn read(r: &mut Reader) -> Result { +impl Readable<'_> for F2Dot14 { + const SIZE: usize = u16::SIZE; + + fn read(r: &mut Reader) -> Option { r.read::().map(Self) } +} +impl Writeable for F2Dot14 { fn write(&self, w: &mut Writer) { w.write::(self.0) } @@ -464,152 +500,32 @@ type Result = std::result::Result; pub enum Error { /// The file contains an unknown kind of font. UnknownKind, - /// An offset pointed outside of the data. - InvalidOffset, - /// Parsing expected more data. - MissingData, - /// Parsed data was invalid. - InvalidData, - /// A table is missing. - /// - /// Mostly, the subsetter just ignores (i.e. not subsets) tables if they are - /// missing (even the required ones). This error only occurs if a table - /// depends on another table and that one is missing, e.g., `glyf` is - /// present but `loca` is missing. - MissingTable(Tag), + /// The font is malformed (or there is a bug in the font parsing logic). + MalformedFont, + /// The font relies on an unimplemented feature, and thus the subsetting + /// process couldn't be completed. + Unimplemented, + /// An unexpected error occurred when subsetting the font. Indicates that there + /// is a logical bug in the subsetter. + SubsetError, + /// An overflow occurred during the computation. Could be either an issue + /// with the font itself, or a bug in the subsetter logic. + OverflowError, + /// An error occurred while processing the CFF table. + CFFError, } impl Display for Error { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Self::UnknownKind => f.pad("unknown font kind"), - Self::InvalidOffset => f.pad("invalid offset"), - Self::MissingData => f.pad("missing more data"), - Self::InvalidData => f.pad("invalid data"), - Self::MissingTable(tag) => write!(f, "missing {tag} table"), + Self::UnknownKind => f.write_str("unknown font kind"), + Self::MalformedFont => f.write_str("malformed font"), + Self::Unimplemented => f.write_str("unsupported feature in font"), + Self::SubsetError => f.write_str("subsetting of font failed"), + Self::OverflowError => f.write_str("overflow occurred"), + Self::CFFError => f.write_str("processing CFF table failed"), } } } impl std::error::Error for Error {} - -#[cfg(test)] -mod tests { - use std::path::Path; - - use super::{subset, Profile}; - - const FEW: &str = "Hällo<.!fi12"; - - #[test] - fn test_subset_truetype() { - test("NotoSans-Regular.ttf", FEW); - test("ClickerScript-Regular.ttf", FEW); - } - - #[test] - fn test_subset_cff() { - test("LatinModernRoman-Regular.otf", FEW); - test("NewCMMath-Regular.otf", "1+2=3;π∫∑"); - test("NotoSansCJKsc-Regular.otf", "ABC你好"); - } - - #[test] - fn test_subset_full() { - test_full("NotoSans-Regular.ttf"); - test_full("ClickerScript-Regular.ttf"); - test_full("NewCMMath-Regular.otf"); - test_full("NotoSansCJKsc-Regular.otf"); - } - - fn test(path: &str, text: &str) { - test_impl(path, text, true); - } - - fn test_full(path: &str) { - let data = std::fs::read(Path::new("fonts").join(path)).unwrap(); - let ttf = ttf_parser::Face::from_slice(&data, 0).unwrap(); - let mut text = String::new(); - for subtable in ttf.tables().cmap.unwrap().subtables { - if subtable.is_unicode() { - subtable.codepoints(|c| text.push(char::try_from(c).unwrap())); - } - } - test_impl(path, &text, false); - } - - fn test_impl(path: &str, text: &str, write: bool) { - eprintln!("=============================================="); - eprintln!("Testing {path}"); - - let data = std::fs::read(Path::new("fonts").join(path)).unwrap(); - let ttf = ttf_parser::Face::from_slice(&data, 0).unwrap(); - let glyphs: Vec<_> = - text.chars().filter_map(|c| Some(ttf.glyph_index(c)?.0)).collect(); - - let profile = Profile::pdf(&glyphs); - let subs = subset(&data, 0, profile).unwrap(); - let stem = Path::new(path).file_stem().unwrap().to_str().unwrap(); - let out = Path::new("target").join(Path::new(stem)).with_extension("ttf"); - - if write { - std::fs::write(out, &subs).unwrap(); - } - - let ttfs = ttf_parser::Face::from_slice(&subs, 0).unwrap(); - let cff = ttfs.tables().cff; - for c in text.chars() { - let id = ttf.glyph_index(c).unwrap(); - let bbox = ttf.glyph_bounding_box(id); - - if let Some(cff) = &cff { - if bbox.is_some() { - cff.outline(id, &mut Sink::default()).unwrap(); - } - } - - let mut sink1 = Sink::default(); - let mut sink2 = Sink::default(); - ttf.outline_glyph(id, &mut sink1); - ttfs.outline_glyph(id, &mut sink2); - assert_eq!(sink1, sink2); - assert_eq!(ttf.glyph_hor_advance(id), ttfs.glyph_hor_advance(id)); - assert_eq!(ttf.glyph_name(id), ttfs.glyph_name(id)); - assert_eq!(ttf.glyph_hor_side_bearing(id), ttfs.glyph_hor_side_bearing(id)); - } - } - - #[derive(Debug, Default, PartialEq)] - struct Sink(Vec); - - #[derive(Debug, PartialEq)] - enum Inst { - MoveTo(f32, f32), - LineTo(f32, f32), - QuadTo(f32, f32, f32, f32), - CurveTo(f32, f32, f32, f32, f32, f32), - Close, - } - - impl ttf_parser::OutlineBuilder for Sink { - fn move_to(&mut self, x: f32, y: f32) { - self.0.push(Inst::MoveTo(x, y)); - } - - fn line_to(&mut self, x: f32, y: f32) { - self.0.push(Inst::LineTo(x, y)); - } - - fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { - self.0.push(Inst::QuadTo(x1, y1, x, y)); - } - - fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { - self.0.push(Inst::CurveTo(x1, y1, x2, y2, x, y)); - } - - fn close(&mut self) { - self.0.push(Inst::Close); - } - } -} diff --git a/src/maxp.rs b/src/maxp.rs new file mode 100644 index 00000000..76d42c1f --- /dev/null +++ b/src/maxp.rs @@ -0,0 +1,24 @@ +//! The `maxp` table contains the number of glyphs (and some additional information +//! depending on the version). All we need to do is rewrite the number of glyphs, the rest +//! can be copied from the old table. + +use super::*; + +pub fn subset(ctx: &mut Context) -> Result<()> { + let maxp = ctx.expect_table(Tag::MAXP).ok_or(MalformedFont)?; + let mut r = Reader::new(maxp); + let version = r.read::().ok_or(MalformedFont)?; + // number of glyphs + r.read::().ok_or(MalformedFont)?; + + let mut sub_maxp = Writer::new(); + sub_maxp.write::(version); + sub_maxp.write::(ctx.mapper.num_gids()); + + if version == 0x00010000 { + sub_maxp.extend(r.tail().ok_or(MalformedFont)?); + } + + ctx.push(Tag::MAXP, sub_maxp.finish()); + Ok(()) +} diff --git a/src/name.rs b/src/name.rs new file mode 100644 index 00000000..8542cacb --- /dev/null +++ b/src/name.rs @@ -0,0 +1,158 @@ +use super::*; +use crate::Error::{MalformedFont, SubsetError}; +use std::collections::HashMap; + +pub fn subset(ctx: &mut Context) -> Result<()> { + let name = ctx.expect_table(Tag::NAME).ok_or(MalformedFont)?; + let mut r = Reader::new(name); + + let version = r.read::().ok_or(MalformedFont)?; + + // From my personal experiments, version 1 isn't used at all, so we + // don't bother subsetting it. + if version != 0 { + ctx.push(Tag::NAME, name); + return Ok(()); + } + + let table = Table::parse(name).ok_or(MalformedFont)?; + let subsetted_table = subset_table(&table).ok_or(SubsetError)?; + + let mut w = Writer::new(); + w.write(subsetted_table); + + ctx.push(Tag::NAME, w.finish()); + Ok(()) +} + +pub fn subset_table<'a>(table: &Table<'a>) -> Option> { + let mut names = table + .names + .iter() + .copied() + .filter(|record| { + record.is_unicode() && [0, 1, 2, 3, 4, 5, 6].contains(&record.name_id) + }) + .collect::>(); + + let mut storage = Vec::new(); + let mut cur_storage_offset = 0; + + let mut name_deduplicator: HashMap<&[u8], u16> = HashMap::new(); + + for record in &mut names { + let name = table.storage.get( + (record.string_offset as usize) + ..((record.string_offset + record.length) as usize), + )?; + let offset = *name_deduplicator.entry(name).or_insert_with(|| { + storage.extend(name); + let offset = cur_storage_offset; + cur_storage_offset += record.length; + offset + }); + + record.string_offset = offset; + } + + Some(Table { names, storage: Cow::Owned(storage) }) +} + +impl Writeable for Table<'_> { + fn write(&self, w: &mut Writer) { + let count = u16::try_from(self.names.len()).unwrap(); + + // version + w.write::(0); + // count + w.write::(count); + // storage offset + w.write::(u16::SIZE as u16 * 3 + count * NameRecord::SIZE as u16); + for name in &self.names { + w.write(name); + } + w.extend(&self.storage); + } +} + +impl Writeable for &NameRecord { + fn write(&self, w: &mut Writer) { + w.write::(self.platform_id); + w.write::(self.encoding_id); + w.write::(self.language_id); + w.write::(self.name_id); + w.write::(self.length); + w.write::(self.string_offset); + } +} + +#[derive(Clone, Debug)] +pub struct Table<'a> { + pub names: Vec, + pub storage: Cow<'a, [u8]>, +} + +impl<'a> Table<'a> { + // The parsing logic was adapted from ttf-parser. + pub fn parse(data: &'a [u8]) -> Option { + let mut r = Reader::new(data); + + let version = r.read::()?; + + if version != 0 { + return None; + } + + let count = r.read::()?; + r.read::()?; // storage offset + + let mut names = Vec::with_capacity(count as usize); + + for _ in 0..count { + names.push(r.read::()?); + } + + let storage = Cow::Borrowed(r.tail()?); + + Some(Self { names, storage }) + } +} + +impl Readable<'_> for NameRecord { + const SIZE: usize = u16::SIZE * 6; + + fn read(r: &mut Reader<'_>) -> Option { + let platform_id = r.read::()?; + let encoding_id = r.read::()?; + let language_id = r.read::()?; + let name_id = r.read::()?; + let length = r.read::()?; + let string_offset = r.read::()?; + + Some(Self { + platform_id, + encoding_id, + language_id, + name_id, + length, + string_offset, + }) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct NameRecord { + pub platform_id: u16, + pub encoding_id: u16, + pub language_id: u16, + pub name_id: u16, + pub length: u16, + pub string_offset: u16, +} + +impl NameRecord { + pub fn is_unicode(&self) -> bool { + self.platform_id == 0 + || (self.platform_id == 3 && [0, 1, 10].contains(&self.encoding_id)) + } +} diff --git a/src/post.rs b/src/post.rs index a39c8f39..2d89f75f 100644 --- a/src/post.rs +++ b/src/post.rs @@ -1,63 +1,123 @@ +//! Subset the `post` table. The `post` table contains name information for glyphs +//! needed for some PostScript printers. Only version 2 table contains actual custom names, +//! so this is the only version that we need to subset. All we need to do is to extract +//! the strings for all requested glyphs and write them into a new `post` table in the +//! given order. + use super::*; +use crate::read::LazyArray16; +use crate::Error::OverflowError; -/// Subset the glyf and loca tables by removing glyph data for unused glyphs. -pub(crate) fn subset(ctx: &mut Context) -> Result<()> { - let post = ctx.expect_table(Tag::POST)?; +pub fn subset(ctx: &mut Context) -> Result<()> { + let post = ctx.expect_table(Tag::POST).ok_or(MalformedFont)?; let mut r = Reader::new(post); - // Version 2 is the only one worth subsetting. - let version = r.read::()?; + let version = r.read::().ok_or(MalformedFont)?; if version != 0x00020000 { ctx.push(Tag::POST, post); return Ok(()); } - // Reader remaining header. - let header = r.take(28)?; + let table = Version2Table::parse(post).ok_or(MalformedFont)?; + let names = table.names().collect::>(); - // Read glyph name table. - let num_glyphs = r.read::()?; - let mut indices = vec![]; - for _ in 0..num_glyphs { - indices.push(r.read::()?); - } + let mut sub_post = Writer::new(); + sub_post.extend(table.header); + sub_post.write(ctx.mapper.num_gids()); + + let mut string_storage = Writer::new(); + let mut string_index = 0; - // Read the strings. - let mut strings = vec![]; - while !r.eof() { - let len = r.read::()?; - strings.push(r.take(len as usize)?); + for old_gid in ctx.mapper.remapped_gids() { + let index = table.glyph_indexes.get(old_gid).ok_or(MalformedFont)?; + + // IDs smaller than 258 refer to the names in the Macintosh TrueType file. + if index <= 257 { + sub_post.write(index); + } else { + let index = index - 258; + // Phetsarath-Regular.ttf from Google Fonts seems to have a wrong name table. + // If name cannot be fetched, use empty name instead. + let name = names.get(index as usize).copied().unwrap_or(&[][..]); + let name_len = u8::try_from(name.len()).map_err(|_| OverflowError)?; + let index = u16::try_from(string_index + 258).map_err(|_| OverflowError)?; + sub_post.write(index); + + string_storage.write(name_len); + string_storage.write(name); + string_index += 1; + } } - // Start writing a new subsetted post table. - let mut sub_post = Writer::new(); - sub_post.write::(0x00020000); - sub_post.give(header); - sub_post.write::(num_glyphs); - - let mut sub_strings = Writer::new(); - let mut count = 0; - for (i, mut index) in indices.into_iter().enumerate() { - // Rewrite unused glyphs to .notdef. - if !ctx.subset.contains(&(i as u16)) { - index = 0; + sub_post.extend(&string_storage.finish()); + + ctx.push(Tag::POST, sub_post.finish()); + Ok(()) +} + +/// An iterator over glyph names. +/// +/// The `post` table doesn't provide the glyph names count, +/// so we have to simply iterate over all of them to find it out. +#[derive(Clone, Copy, Default)] +pub struct Names<'a> { + data: &'a [u8], + offset: usize, +} + +impl<'a> Iterator for Names<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + if self.offset >= self.data.len() { + return None; } - if index <= 257 { - sub_post.write::(index); - continue; + let len = self.data[self.offset]; + self.offset += 1; + + // An empty name is an error. + if len == 0 { + return None; } - let index = index - 258; - let name = strings.get(index as usize).ok_or(Error::InvalidOffset)?; - sub_post.write::(count + 258); - sub_strings.write::(name.len() as u8); - sub_strings.give(name); - count += 1; + let name = self.data.get(self.offset..self.offset + usize::from(len))?; + self.offset += usize::from(len); + Some(name) } +} - sub_post.give(&sub_strings.finish()); - ctx.push(Tag::POST, sub_post.finish()); +/// A version 2 `name` table. +#[derive(Clone, Debug)] +pub struct Version2Table<'a> { + pub header: &'a [u8], + pub glyph_indexes: LazyArray16<'a, u16>, + pub names_data: &'a [u8], +} - Ok(()) +impl<'a> Version2Table<'a> { + /// Parse a version 2 table. + pub fn parse(data: &'a [u8]) -> Option { + // Do not check the exact length, because some fonts include + // padding in table's length in table records, which is incorrect. + if data.len() < 32 || Reader::new(data).read::()? != 0x00020000 { + return None; + } + + let mut r = Reader::new(data); + let header = r.read_bytes(32)?; + + let indexes_count = r.read::()?; + let glyph_indexes = r.read_array16::(indexes_count)?; + let names_data = r.tail()?; + + Some(Version2Table { header, glyph_indexes, names_data }) + } + + /// Returns an iterator over glyph names. + /// + /// Default/predefined names are not included. Just the one in the font file. + pub fn names(&self) -> Names<'a> { + Names { data: self.names_data, offset: 0 } + } } diff --git a/src/read.rs b/src/read.rs new file mode 100644 index 00000000..e43c2825 --- /dev/null +++ b/src/read.rs @@ -0,0 +1,239 @@ +use std::convert::TryInto; + +#[derive(Clone, Debug)] +/// A readable stream of binary data. +pub struct Reader<'a> { + /// The underlying data of the reader. + data: &'a [u8], + /// The current offset in bytes. Is not guaranteed to be in range. + offset: usize, +} + +impl<'a> Reader<'a> { + /// Create a new readable stream of binary data. + #[inline] + pub fn new(data: &'a [u8]) -> Self { + Self { data, offset: 0 } + } + + /// Create a new readable stream of binary data at a specific position. + #[inline] + pub fn new_at(data: &'a [u8], offset: usize) -> Self { + Self { data, offset } + } + + /// The remaining data from the current offset. + #[inline] + pub fn tail(&self) -> Option<&'a [u8]> { + self.data.get(self.offset..) + } + + /// Returns the current offset. + #[inline] + pub fn offset(&self) -> usize { + self.offset + } + + /// Try to read `T` from the data. + #[inline] + pub fn read>(&mut self) -> Option { + T::read(self) + } + + /// Try to read `T` from the data. + #[inline] + pub fn peak>(&mut self) -> Option { + let mut r = self.clone(); + T::read(&mut r) + } + + /// Read a certain number of bytes. + #[inline] + pub fn read_bytes(&mut self, len: usize) -> Option<&'a [u8]> { + let v = self.data.get(self.offset..self.offset + len)?; + self.offset += len; + Some(v) + } + + /// Reads the next `count` types as a slice. + #[inline] + pub fn read_array16>( + &mut self, + count: u16, + ) -> Option> { + let len = usize::from(count) * T::SIZE; + self.read_bytes(len).map(LazyArray16::new) + } + + /// Advances by `Readable::SIZE`. + #[inline] + pub fn skip>(&mut self) { + self.skip_bytes(T::SIZE); + } + + /// Check whether the reader is at the end of the buffer. + #[inline] + pub fn at_end(&self) -> bool { + self.offset >= self.data.len() + } + + /// Jump to a specific location. + #[inline] + pub fn jump(&mut self, offset: usize) { + self.offset = offset; + } + + /// Skip the next `n` bytes from the stream. + #[inline] + pub fn skip_bytes(&mut self, n: usize) { + self.read_bytes(n); + } +} + +/// Trait for an object that can be read from a byte stream with a fixed size. +pub trait Readable<'a>: Sized { + const SIZE: usize; + + fn read(r: &mut Reader<'a>) -> Option; +} + +impl Readable<'_> for [u8; N] { + const SIZE: usize = u8::SIZE * N; + + fn read(r: &mut Reader) -> Option { + Some(r.read_bytes(N)?.try_into().unwrap_or([0; N])) + } +} + +impl Readable<'_> for u8 { + const SIZE: usize = 1; + + fn read(r: &mut Reader) -> Option { + r.read::<[u8; 1]>().map(Self::from_be_bytes) + } +} + +impl Readable<'_> for u16 { + const SIZE: usize = 2; + + fn read(r: &mut Reader) -> Option { + r.read::<[u8; 2]>().map(Self::from_be_bytes) + } +} + +impl Readable<'_> for i16 { + const SIZE: usize = 2; + + fn read(r: &mut Reader) -> Option { + r.read::<[u8; 2]>().map(Self::from_be_bytes) + } +} + +impl Readable<'_> for u32 { + const SIZE: usize = 4; + + fn read(r: &mut Reader) -> Option { + r.read::<[u8; 4]>().map(Self::from_be_bytes) + } +} + +impl Readable<'_> for i32 { + const SIZE: usize = 4; + + fn read(r: &mut Reader) -> Option { + r.read::<[u8; 4]>().map(Self::from_be_bytes) + } +} + +/// A slice-like container that converts internal binary data only on access. +/// +/// Array values are stored in a continuous data chunk. +#[derive(Clone, Copy)] +pub struct LazyArray16<'a, T> { + data: &'a [u8], + data_type: core::marker::PhantomData, +} + +impl Default for LazyArray16<'_, T> { + #[inline] + fn default() -> Self { + LazyArray16 { data: &[], data_type: core::marker::PhantomData } + } +} + +impl<'a, T: Readable<'a>> LazyArray16<'a, T> { + /// Creates a new `LazyArray`. + #[inline] + pub fn new(data: &'a [u8]) -> Self { + LazyArray16 { data, data_type: core::marker::PhantomData } + } + + /// Returns a value at `index`. + #[inline] + pub fn get(&self, index: u16) -> Option { + if index < self.len() { + let start = usize::from(index) * T::SIZE; + let end = start + T::SIZE; + self.data + .get(start..end) + .map(Reader::new) + .and_then(|mut r| T::read(&mut r)) + } else { + None + } + } + + /// Returns array's length. + #[inline] + pub fn len(&self) -> u16 { + (self.data.len() / T::SIZE) as u16 + } +} + +impl<'a, T: Readable<'a> + core::fmt::Debug + Copy> core::fmt::Debug + for LazyArray16<'a, T> +{ + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_list().entries(*self).finish() + } +} + +impl<'a, T: Readable<'a>> IntoIterator for LazyArray16<'a, T> { + type Item = T; + type IntoIter = LazyArrayIter16<'a, T>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + LazyArrayIter16 { data: self, index: 0 } + } +} + +/// An iterator over `LazyArray16`. +#[derive(Clone, Copy)] +#[allow(missing_debug_implementations)] +pub struct LazyArrayIter16<'a, T> { + data: LazyArray16<'a, T>, + index: u16, +} + +impl<'a, T: Readable<'a>> Default for LazyArrayIter16<'a, T> { + #[inline] + fn default() -> Self { + LazyArrayIter16 { data: LazyArray16::new(&[]), index: 0 } + } +} + +impl<'a, T: Readable<'a>> Iterator for LazyArrayIter16<'a, T> { + type Item = T; + + #[inline] + fn next(&mut self) -> Option { + self.index += 1; + self.data.get(self.index - 1) + } + + #[inline] + fn count(self) -> usize { + usize::from(self.data.len().saturating_sub(self.index)) + } +} diff --git a/src/remapper.rs b/src/remapper.rs new file mode 100644 index 00000000..32d6773b --- /dev/null +++ b/src/remapper.rs @@ -0,0 +1,167 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::ops::Add; + +/// A structure that allows to remap numeric types to new +/// numbers so that they form a contiguous sequence of numbers. +#[derive(Debug, Clone, Hash)] +pub struct Remapper { + /// The counter that keeps track of the next number to be assigned. + /// Should always start with 0. + counter: C, + /// The map that maps numbers from their old value to their new value. + forward: BTreeMap, + /// The vector that stores the "reverse" mapping, i.e. given a new number, + /// it allows to map back to the old one. + backward: Vec, +} + +/// A wrapper trait around `checked_add` so we can require it for the remapper. +pub trait CheckedAdd: Sized + Add { + fn checked_add(&self, v: &Self) -> Option; +} + +impl CheckedAdd for u8 { + fn checked_add(&self, v: &Self) -> Option { + u8::checked_add(*self, *v) + } +} + +impl CheckedAdd for u16 { + fn checked_add(&self, v: &Self) -> Option { + u16::checked_add(*self, *v) + } +} + +impl CheckedAdd for u32 { + fn checked_add(&self, v: &Self) -> Option { + u32::checked_add(*self, *v) + } +} + +impl, T: Ord + Copy + From + From> Remapper { + /// Create a new instance of a remapper. + pub fn new() -> Self + where + C: Default, + { + Self { + counter: C::default(), + forward: BTreeMap::new(), + backward: Vec::new(), + } + } + + /// Get the new mapping of a value that has been remapped before. + /// Returns `None` if it has not been remapped. + pub fn get(&self, old: T) -> Option { + self.forward.get(&old).copied() + } + + /// Remap a new value, either returning the previously assigned number + /// if it already has been remapped, and assigning a new number if it + /// has not been remapped. + pub fn remap(&mut self, old: T) -> T { + *self.forward.entry(old).or_insert_with(|| { + let value = self.counter; + self.backward.push(old); + self.counter = self + .counter + .checked_add(&C::from(1)) + .expect("remapper was overflowed"); + value.into() + }) + } + + /// Get the number of elements that have been remapped. Assumes that + /// the remapper was constructed with a type where `C::default` yields 0. + pub fn len(&self) -> C { + self.counter + } + + /// Returns an iterator over the old values, in ascending order that is defined + /// by the remapping. + pub fn sorted_iter(&self) -> impl Iterator + '_ { + self.backward.iter().copied() + } +} + +/// A remapper that allows to assign a new ordering to a subset of glyphs. +/// +/// For example, let's say that we want to subset a font that only contains the +/// glyphs 4, 9 and 16. In this case, the remapper could yield a remapping +/// that assigns the following glyph IDs: +/// - 0 -> 0 (The .notdef glyph will always be included) +/// - 4 -> 1 +/// - 9 -> 2 +/// - 16 -> 3 +/// +/// This is necessary because a font needs to have a contiguous sequence of +/// glyph IDs that start from 0, so we cannot just reuse the old ones, but we +/// need to define a mapping. +#[derive(Debug, Clone, Hash)] +pub struct GlyphRemapper(Remapper); + +impl Default for GlyphRemapper { + fn default() -> Self { + let mut remapper = Remapper::new(); + // .notdef is always a part of a subset. + remapper.remap(0); + Self(remapper) + } +} + +impl GlyphRemapper { + /// Create a new instance of a glyph remapper. `.notdef` will always be a member + /// of the subset. + pub fn new() -> Self { + Self::default() + } + + /// Create a remapper from an existing set of glyphs + pub fn new_from_glyphs(glyphs: &[u16]) -> Self { + let mut map = Self::new(); + + for glyph in glyphs { + map.remap(*glyph); + } + + map + } + + /// Create a remapper from an existing set of glyphs. The method + /// will ensure that the mapping is monotonically increasing. + pub fn new_from_glyphs_sorted(glyphs: &[u16]) -> Self { + let mut sorted = + BTreeSet::from_iter(glyphs).iter().map(|g| **g).collect::>(); + sorted.sort(); + GlyphRemapper::new_from_glyphs(&sorted) + } + + /// Get the number of gids that have been remapped. + pub fn num_gids(&self) -> u16 { + self.0.len() + } + + /// Remap a glyph ID, or return the existing mapping if the + /// glyph ID has already been remapped before. + pub fn remap(&mut self, old: u16) -> u16 { + self.0.remap(old) + } + + /// Get the mapping of a glyph ID, if it has been remapped before. + pub fn get(&self, old: u16) -> Option { + self.0.get(old) + } + + /// Return an iterator that yields the old glyphs, in ascending order that + /// is defined by the remapping. For example, if we perform the following remappings: + /// 3, 39, 8, 3, 10, 2 + /// + /// Then the iterator will yield the following items in the order below. The order + /// also implicitly defines the glyph IDs in the new mapping: + /// + /// 0 (0), 3 (1), 39 (2), 8 (3), 10 (4), 2 (5) + pub fn remapped_gids(&self) -> impl Iterator + '_ { + self.0.backward.iter().copied() + } +} diff --git a/src/stream.rs b/src/stream.rs deleted file mode 100644 index 2514c37d..00000000 --- a/src/stream.rs +++ /dev/null @@ -1,180 +0,0 @@ -use super::{Error, Result}; - -/// A readable stream of binary data. -pub struct Reader<'a>(&'a [u8]); - -impl<'a> Reader<'a> { - /// Create a new readable stream of binary data. - pub fn new(data: &'a [u8]) -> Self { - Self(data) - } - - /// The remaining data. - pub fn data(&self) -> &'a [u8] { - self.0 - } - - /// Whether there is no data remaining. - pub fn eof(&self) -> bool { - self.0.is_empty() - } - - /// Try to read `T` from the data. - pub fn read>(&mut self) -> Result { - T::read(self) - } - - /// Take the first `n` bytes from the stream. - pub fn take(&mut self, n: usize) -> Result<&'a [u8]> { - if n <= self.0.len() { - let head = &self.0[..n]; - self.0 = &self.0[n..]; - Ok(head) - } else { - Err(Error::MissingData) - } - } - - /// Skip the first `n` bytes from the stream. - pub fn skip(&mut self, n: usize) -> Result<()> { - if n <= self.0.len() { - self.0 = &self.0[n..]; - Ok(()) - } else { - Err(Error::MissingData) - } - } -} - -/// A writable stream of binary data. -pub struct Writer(Vec, #[cfg(test)] usize); - -impl Writer { - /// Create a new writable stream of binary data. - pub fn new() -> Self { - Self( - Vec::with_capacity(1024), - #[cfg(test)] - 0, - ) - } - - /// Write `T` into the data. - pub fn write<'a, T: Structure<'a>>(&mut self, data: T) { - data.write(self); - } - - /// Write `T` into the data, passing it by reference. - pub fn write_ref<'a, T: Structure<'a>>(&mut self, data: &T) { - data.write(self); - } - - /// Give bytes into the writer. - pub fn give(&mut self, bytes: &[u8]) { - self.0.extend(bytes); - } - - /// Align the contents to a byte boundary. - pub fn align(&mut self, to: usize) { - while self.0.len() % to != 0 { - self.0.push(0); - } - } - - /// The number of written bytes. - pub fn len(&self) -> usize { - self.0.len() - } - - /// Return the written bytes. - pub fn finish(self) -> Vec { - self.0 - } - - /// Print how many bytes were written since the last inspect call. - pub fn inspect(&mut self, _name: &str) { - #[cfg(test)] - { - eprintln!("{_name} took {} bytes", self.len() - self.1); - self.1 = self.len(); - } - } -} - -/// Decode structures from a stream of binary data. -pub trait Structure<'a>: Sized { - /// Try to read `Self` from the reader. - fn read(r: &mut Reader<'a>) -> Result; - - /// Write `Self` into the writer. - fn write(&self, w: &mut Writer); - - /// Read self at the given offset in the binary data. - fn read_at(data: &'a [u8], offset: usize) -> Result { - if let Some(sub) = data.get(offset..) { - Self::read(&mut Reader::new(sub)) - } else { - Err(Error::InvalidOffset) - } - } -} - -impl Structure<'_> for [u8; N] { - fn read(r: &mut Reader) -> Result { - Ok(r.take(N)?.try_into().unwrap_or([0; N])) - } - - fn write(&self, w: &mut Writer) { - w.give(self) - } -} - -impl Structure<'_> for u8 { - fn read(r: &mut Reader) -> Result { - r.read::<[u8; 1]>().map(Self::from_be_bytes) - } - - fn write(&self, w: &mut Writer) { - w.write::<[u8; 1]>(self.to_be_bytes()); - } -} - -impl Structure<'_> for u16 { - fn read(r: &mut Reader) -> Result { - r.read::<[u8; 2]>().map(Self::from_be_bytes) - } - - fn write(&self, w: &mut Writer) { - w.write::<[u8; 2]>(self.to_be_bytes()); - } -} - -impl Structure<'_> for i16 { - fn read(r: &mut Reader) -> Result { - r.read::<[u8; 2]>().map(Self::from_be_bytes) - } - - fn write(&self, w: &mut Writer) { - w.write::<[u8; 2]>(self.to_be_bytes()); - } -} - -impl Structure<'_> for u32 { - fn read(r: &mut Reader) -> Result { - r.read::<[u8; 4]>().map(Self::from_be_bytes) - } - - fn write(&self, w: &mut Writer) { - w.write::<[u8; 4]>(self.to_be_bytes()); - } -} - -impl Structure<'_> for i32 { - fn read(r: &mut Reader) -> Result { - r.read::<[u8; 4]>().map(Self::from_be_bytes) - } - - fn write(&self, w: &mut Writer) { - w.write::<[u8; 4]>(self.to_be_bytes()); - } -} diff --git a/src/write.rs b/src/write.rs new file mode 100644 index 00000000..ef048b3b --- /dev/null +++ b/src/write.rs @@ -0,0 +1,111 @@ +/// A writable stream of binary data. +pub struct Writer(Vec); + +impl Writer { + /// Create a new writable stream of binary data. + #[inline] + pub fn new() -> Self { + Self(Vec::with_capacity(1024)) + } + + /// Create a new writable stream of binary data with a capacity. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Self(Vec::with_capacity(capacity)) + } + + /// Write `T` into the data. + #[inline] + pub fn write(&mut self, data: T) { + data.write(self); + } + + /// Give bytes into the writer. + #[inline] + pub fn extend(&mut self, bytes: &[u8]) { + self.0.extend(bytes); + } + + /// Align the contents to a byte boundary. + #[inline] + pub fn align(&mut self, to: usize) { + while self.0.len() % to != 0 { + self.0.push(0); + } + } + + /// The number of written bytes. + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + + /// Return the written bytes. + #[inline] + pub fn finish(self) -> Vec { + self.0 + } +} + +/// Trait for an object that can be written into a byte stream. +pub trait Writeable: Sized { + fn write(&self, w: &mut Writer); +} + +impl Writeable for [T; N] { + fn write(&self, w: &mut Writer) { + for i in self { + w.write(i); + } + } +} + +impl Writeable for u8 { + fn write(&self, w: &mut Writer) { + w.extend(&self.to_be_bytes()); + } +} + +impl Writeable for &[T] +where + T: Writeable, +{ + fn write(&self, w: &mut Writer) { + for el in *self { + w.write(el); + } + } +} + +impl Writeable for &T +where + T: Writeable, +{ + fn write(&self, w: &mut Writer) { + T::write(self, w) + } +} + +impl Writeable for u16 { + fn write(&self, w: &mut Writer) { + w.write::<[u8; 2]>(self.to_be_bytes()); + } +} + +impl Writeable for i16 { + fn write(&self, w: &mut Writer) { + w.write::<[u8; 2]>(self.to_be_bytes()); + } +} + +impl Writeable for u32 { + fn write(&self, w: &mut Writer) { + w.write::<[u8; 4]>(self.to_be_bytes()); + } +} + +impl Writeable for i32 { + fn write(&self, w: &mut Writer) { + w.write::<[u8; 4]>(self.to_be_bytes()); + } +} diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..942fa886 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,44 @@ +# Testing + +## Requirements +You need to have `fonttools 4.50` installed on your system and in your PATH. Note that you need to have that +exact version, otherwise the tests will fail. + +## Generating tests +In order to create new fonttools tests, you can edit `data/fonttools.tests`. For subset tests, you can edit +`data/subsets.tests` + +In order to generate the tests, run `scripts/gen-tests.py`. + +## Description +Testing is very important, as having errors in the subsetting logic could have fatal consequences. +Because of this, we have three different testing approaches that cover 4 different +font readers and 7 different PDF readers in total. + +### Subset tests +We use `fontations`, `ttf-parser` and `freetype` to ensure that the outlines in the new font are the same as in the +old font. By checking 3 different implementations, we can assert with relatively high confidence that there are +no issues in this regard. For each font, we test a selected subset of glyphs (see `data/subsets.tests`), but +for each font we also make one subset where all glyphs are included, to make sure that "rewriting the whole font" +also works as expected. + +Using `ttf-parser`, we also check that the metrics for a glyph still match. This is especially useful for testing +`hmtx` subsetting. + +### fonttools tests +`fonttools` has a feature that allows us to dump a font's internal structure as an XML file. This is +_incredibly_ useful, because it allows us to easily inspect the structure of a font file. We use this to +dump small subsets of fonts and compare the output to how fonttools would subset the font. This allows us +to identify other kinds of potential issues in the implementation. And it conveniently also allows us to +have a fourth implementation to test against. + +### Fuzzing tests +In `examples`, we have a binary that takes an environment variable `FONT_DIR` and recursively iterates over all fonts +in that directory and basically performs the same test as in #1, but on a randomly selected sets of glyphs. We currently +try to run the fuzzer every once in a while on a set of 1000+ fonts to make sure it that the subsetter also works with +other fonts than the one included in this repository. + +### PDF tests +Occasionally, we will also use a subset of fonts and use `typst` to create a PDF file with it and check +that the output looks correct in Adobe Acrobat, mupdf, xpdf, Firefox, Chrome, Apple Preview and pdfbox. These tests +only happen manually though. \ No newline at end of file diff --git a/tests/cli/Cargo.toml b/tests/cli/Cargo.toml new file mode 100644 index 00000000..c4754a55 --- /dev/null +++ b/tests/cli/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "cli" +version = { workspace = true } +edition = { workspace = true } +publish = false + +[dependencies] +subsetter = { path = "../.." } diff --git a/tests/cli/src/main.rs b/tests/cli/src/main.rs new file mode 100644 index 00000000..7723e80c --- /dev/null +++ b/tests/cli/src/main.rs @@ -0,0 +1,37 @@ +use std::env; +use subsetter::{subset, GlyphRemapper}; + +fn parse_gids(gids: &str) -> Vec { + if gids == "*" { + return (0..u16::MAX).collect(); + } + + let split = gids.split(',').filter(|s| !s.is_empty()).collect::>(); + let mut gids = vec![]; + + for el in &split { + if el.contains('-') { + let range = el.split('-').collect::>(); + let first = range[0].parse::().unwrap(); + let second = range[1].parse::().unwrap(); + + gids.extend(first..=second); + } else { + gids.push(el.parse::().unwrap()); + } + } + + gids +} + +// Note that this is more of an experimental CLI used for testing. +fn main() { + let args: Vec = env::args().collect(); + let data = std::fs::read(&args[1]).unwrap(); + let gids = parse_gids(args.get(3).to_owned().unwrap_or(&"0-5".to_owned())); + let remapper = GlyphRemapper::new_from_glyphs(gids.as_slice()); + + let sub = subset(&data, 0, &remapper).unwrap(); + + std::fs::write(args.get(2).unwrap_or(&"res.otf".to_owned()), sub).unwrap(); +} diff --git a/tests/data/fonttools.tests b/tests/data/fonttools.tests new file mode 100644 index 00000000..9c7ec39b --- /dev/null +++ b/tests/data/fonttools.tests @@ -0,0 +1,13 @@ +// These tests will check whether the fonttools output of a subsetted fonts +// matches the expected output. + +ClickerScript-Regular.ttf;5,8,10,100-104 +DejaVuSansMono.ttf;140-155,100-105 +LatinModernRoman-Regular.otf;307,309,314,221 +MPLUS1p-Regular.ttf;3,45-50 +NotoSansCJKsc-Regular.otf;6543-6550,371-375 +NotoSans-Regular.ttf;567-570,2345-2350 +Roboto-Regular.ttf;456,460-463 +NewCMMath-Regular.otf;803-806,950-952,5600-5602 +NotoSansCJKsc-Bold-subset1.otf;1 + diff --git a/tests/data/subsets.tests b/tests/data/subsets.tests new file mode 100644 index 00000000..52a2fc07 --- /dev/null +++ b/tests/data/subsets.tests @@ -0,0 +1,67 @@ +// These tests will check whether the face metrics of the subsetted fonts +// stay the same, as well as whether the outlines created by ttf-parser and skrifa +// stay the same. + +// 372 glyphs in total +ClickerScript-Regular.ttf;0 +ClickerScript-Regular.ttf;2 +ClickerScript-Regular.ttf;5,8,10 +ClickerScript-Regular.ttf;0-20,33,35,36,41-47 +ClickerScript-Regular.ttf;100-200,202,301,304 +ClickerScript-Regular.ttf;10,100-105,108,130-145,234,247,253,267,278-283,340 +ClickerScript-Regular.ttf;* + +// 3377 glyphs in total +DejaVuSansMono.ttf;100-105,140-155 +DejaVuSansMono.ttf;10-30,40-80 +DejaVuSansMono.ttf;100-245,2456-2609,800-900 +DejaVuSansMono.ttf;600-620,3000-3146 +DejaVuSansMono.ttf;* + +// 821 glyphs in total +LatinModernRoman-Regular.otf;5-15,60-57 +LatinModernRoman-Regular.otf;100-130,200-280 +LatinModernRoman-Regular.otf;370-400,526-612,701,710,800 +LatinModernRoman-Regular.otf;* + +// 8662 glyphs in total +MPLUS1p-Regular.ttf;0,1 +MPLUS1p-Regular.ttf;346 +MPLUS1p-Regular.ttf;3,45,98,120,245,389,1043,1055-1063 +MPLUS1p-Regular.ttf;2030-2310,4590-5120 +MPLUS1p-Regular.ttf;* + +// 65535 glyphs in total +NotoSansCJKsc-Regular.otf;1-2 +NotoSansCJKsc-Regular.otf;4500-5746 +NotoSansCJKsc-Regular.otf;12000-14000,34-60,80-90 +NotoSansCJKsc-Regular.otf;45-90,16000-16897,23569-25697 +NotoSansCJKsc-Regular.otf;65532 +NotoSansCJKsc-Regular.otf;* + +// 4688 glyphs in total +NotoSans-Regular.ttf;567-570 +NotoSans-Regular.ttf;1034-1037,3020-3045 +NotoSans-Regular.ttf;3,6,8,9,11 +NotoSans-Regular.ttf;10-30 +NotoSans-Regular.ttf;30-50,132,137 +NotoSans-Regular.ttf;20-25,30,40,45,47,48,52-70,300-350,500-522,3001 +NotoSans-Regular.ttf;* + +// 1294 glyphs in total +Roboto-Regular.ttf;3,4 +Roboto-Regular.ttf;40-60,90-130,156,180 +Roboto-Regular.ttf;45-60,120-145,750-780,810,812,830,850,900 +Roboto-Regular.ttf;757 +Roboto-Regular.ttf;* + +// 7601 glyphs in total +NewCMMath-Regular.otf;400-402 +NewCMMath-Regular.otf;256-300,113-117 +NewCMMath-Regular.otf;137-400,890-900,2345-2400 +NewCMMath-Regular.otf;8 +NewCMMath-Regular.otf;4622 +NewCMMath-Regular.otf;* + +Syne-Regular_subset.otf;5 +TestTTC.ttc;* diff --git a/tests/fuzz/Cargo.toml b/tests/fuzz/Cargo.toml new file mode 100644 index 00000000..5d63d40b --- /dev/null +++ b/tests/fuzz/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "fuzz" +version = { workspace = true } +edition = { workspace = true } +publish = false + +[dependencies] +subsetter = { path = "../.." } +freetype-rs = { git = "https://github.com/LaurenzV/freetype-rs", rev = "d27ea29" } +rand = "0.8.5" +rand_distr = "0.4.3" +rayon = "1.10.0" +skrifa = "0.19.1" +ttf-parser = { git = "https://github.com/RazrFalcon/ttf-parser", rev = "9bbd432" } +walkdir = "2.5.0" diff --git a/tests/fuzz/src/main.rs b/tests/fuzz/src/main.rs new file mode 100644 index 00000000..4fedcbcf --- /dev/null +++ b/tests/fuzz/src/main.rs @@ -0,0 +1,422 @@ +use freetype::face::LoadFlag; +use freetype::Library; +use rand_distr::Distribution; +use std::ffi::OsStr; +use std::fs; +use std::path::Path; + +use rand::distributions::WeightedIndex; +use rand::prelude::{IteratorRandom, ThreadRng}; +use rand::thread_rng; +use rayon::iter::IntoParallelRefIterator; +use rayon::iter::ParallelIterator; +use skrifa::instance::{LocationRef, Size}; +use skrifa::outline::{DrawSettings, OutlinePen}; +use skrifa::MetadataProvider; +use subsetter::{subset, GlyphRemapper}; +use ttf_parser::GlyphId; + +// Note that this is not really meant as an example for how to use this crate, but +// rather just so that we can conveniently run the fuzzer. + +const NUM_ITERATIONS: usize = 200; + +fn main() { + let exclude_fonts = vec![ + // Seems to be an invalid font for some reason, fonttools can't read it either. + // Glyph 822 doesn't seem to draw properly with ttf-parser... But most likely a ttf-parser + // bug because it does work with skrifa and freetype. fonttools ttx subset matches + // the output you get when subsetting with fonttools. + "Souliyo-Regular.ttf", + // Has `seac` operator. + "waltograph42.otf", + // Color font. + "NotoColorEmojiCompatTest-Regular.ttf", + ]; + + let paths = walkdir::WalkDir::new(std::env::var("FONTS_DIR").unwrap()) + .into_iter() + .map(|p| p.unwrap().path().to_path_buf()) + .filter(|p| { + let extension = p.extension().and_then(OsStr::to_str); + (extension == Some("ttf") || extension == Some("otf")) + && !exclude_fonts.contains(&p.file_name().unwrap().to_str().unwrap()) + }) + .collect::>(); + + loop { + println!("Starting an iteration..."); + + paths.par_iter().for_each(|path| { + let ft_library = freetype::Library::init().unwrap(); + let mut rng = thread_rng(); + let extension = path.extension().and_then(OsStr::to_str); + let is_font_file = extension == Some("ttf") || extension == Some("otf"); + + if is_font_file { + match run_test(&path, &mut rng, &ft_library) { + Ok(_) => {} + Err(msg) => { + println!("Error while fuzzing {:?}: {:}", path.clone(), msg) + } + } + } + }); + } +} + +fn run_test( + path: &Path, + rng: &mut ThreadRng, + ft_library: &Library, +) -> Result<(), String> { + let data = fs::read(path).map_err(|_| "failed to read file".to_string())?; + let old_ttf_face = ttf_parser::Face::parse(&data, 0) + .map_err(|_| "failed to parse old face".to_string())?; + + let num_glyphs = old_ttf_face.number_of_glyphs(); + let possible_gids = (0..num_glyphs).collect::>(); + let dist = get_distribution(num_glyphs); + + let old_skrifa_face = skrifa::FontRef::new(&data).unwrap(); + let old_freetype_face = ft_library.new_memory_face2(data.as_slice(), 0).unwrap(); + + for _ in 0..NUM_ITERATIONS { + let num = dist.sample(rng); + let sample = possible_gids.clone().into_iter().choose_multiple(rng, num); + let remapper = GlyphRemapper::new_from_glyphs(sample.as_slice()); + let sample_strings = sample.iter().map(|g| g.to_string()).collect::>(); + let subset = subset(&data, 0, &remapper).map_err(|_| { + format!("subset failed for gids {:?}", sample_strings.join(",")) + })?; + let new_ttf_face = ttf_parser::Face::parse(&subset, 0).map_err(|_| { + format!( + "failed to parse new ttf face with gids {:?}", + sample_strings.join(",") + ) + })?; + let new_skrifa_face = skrifa::FontRef::new(&subset).map_err(|_| { + format!( + "failed to parse new skrifa face with gids {:?}", + sample_strings.join(",") + ) + })?; + + let new_freetype_face = + ft_library.new_memory_face2(subset.as_slice(), 0).map_err(|_| { + format!( + "failed to parse new freetype face with gids {:?}", + sample_strings.join(",") + ) + })?; + + glyph_outlines_ttf_parser(&old_ttf_face, &new_ttf_face, &remapper, &sample) + .map_err(|g| { + format!( + "outlines didn't match for gid {:?} with ttf-parser, with sample {:?}", + g, + sample_strings.join(",") + ) + })?; + + glyph_outlines_skrifa(&old_skrifa_face, &new_skrifa_face, &remapper, &sample) + .map_err(|g| { + format!( + "outlines didn't match for gid {:?} with skrifa, with sample {:?}", + g, + sample_strings.join(",") + ) + })?; + + glyph_outlines_freetype( + &old_freetype_face, + &new_freetype_face, + &remapper, + &sample, + ) + .map_err(|g| { + format!( + "outlines didn't match for gid {:?} with freetype, with sample {:?}", + g, + sample_strings.join(",") + ) + })?; + + ttf_parser_glyph_metrics(&old_ttf_face, &new_ttf_face, &remapper, &sample) + .map_err(|e| { + format!( + "glyph metrics for sample {:?} didn't match: {:?}", + sample_strings.join(","), + e + ) + })?; + } + + Ok(()) +} + +fn get_distribution(num_glyphs: u16) -> WeightedIndex { + let mut weights = vec![0]; + + for i in 1..num_glyphs { + if i <= 10 { + weights.push(8000); + } else if i <= 50 { + weights.push(16000); + } else if i <= 200 { + weights.push(6000); + } else if i <= 2000 { + weights.push(100); + } else if i <= 5000 { + weights.push(2); + } + } + + WeightedIndex::new(&weights).unwrap() +} + +fn ttf_parser_glyph_metrics( + old_face: &ttf_parser::Face, + new_face: &ttf_parser::Face, + mapper: &GlyphRemapper, + gids: &[u16], +) -> Result<(), String> { + for glyph in gids.iter().copied() { + let mapped = mapper.get(glyph).unwrap(); + + // For some reason the glyph bounding box differs sometimes, so we don't check + // that anymore. I verified via fonttools that our subset matches theirs. So it is + // probably a ttf-parser issue... + // if old_face.glyph_bounding_box(GlyphId(glyph)) + // != new_face.glyph_bounding_box(GlyphId(mapped)) + // { + // return Err(format!("glyph bounding box for glyph {:?} didn't match.", glyph)); + // } + + if old_face.glyph_hor_side_bearing(GlyphId(glyph)) + != new_face.glyph_hor_side_bearing(GlyphId(mapped)) + { + return Err(format!( + "glyph hor side bearing for glyph {:?} didn't match.", + glyph + )); + } + + if old_face.glyph_hor_advance(GlyphId(glyph)) + != new_face.glyph_hor_advance(GlyphId(mapped)) + { + return Err(format!("glyph hor advance for glyph {:?} didn't match.", glyph)); + } + } + + Ok(()) +} + +fn glyph_outlines_skrifa( + old_face: &skrifa::FontRef, + new_face: &skrifa::FontRef, + mapper: &GlyphRemapper, + gids: &[u16], +) -> Result<(), String> { + // let hinting_instance_old = HintingInstance::new( + // &old_face.outline_glyphs(), + // Size::new(150.0), + // LocationRef::default(), + // HintingMode::Smooth { lcd_subpixel: None, preserve_linear_metrics: false }, + // ).map_err(|_| "failed to create old hinting instance".to_string())?; + // + // let hinting_instance_new = HintingInstance::new( + // &new_face.outline_glyphs(), + // Size::new(150.0), + // LocationRef::default(), + // HintingMode::Smooth { lcd_subpixel: None, preserve_linear_metrics: false }, + // ).map_err(|_| "failed to create new hinting instance".to_string())?; + + let mut sink1 = Sink(vec![]); + let mut sink2 = Sink(vec![]); + + for glyph in gids.iter().copied() { + let new_glyph = mapper.get(glyph).ok_or("failed to remap glyph".to_string())?; + // We don't to hinted because for some reason skrifa fails to do so even on the old face in many + // cases. So it's not a subsetting issue. + let settings = DrawSettings::unhinted(Size::new(150.0), LocationRef::default()); + + if let Some(glyph1) = old_face.outline_glyphs().get(skrifa::GlyphId::new(glyph)) { + glyph1 + .draw(settings, &mut sink1) + .map_err(|e| format!("failed to draw old glyph {}: {}", glyph, e))?; + + let settings = + DrawSettings::unhinted(Size::new(150.0), LocationRef::default()); + let glyph2 = new_face + .outline_glyphs() + .get(skrifa::GlyphId::new(new_glyph)) + .expect(&format!("failed to find glyph {} in new face", glyph)); + glyph2 + .draw(settings, &mut sink2) + .map_err(|e| format!("failed to draw new glyph {}: {}", glyph, e))?; + + if sink1 != sink2 { + return Err(format!("{}", glyph)); + } else { + return Ok(()); + } + } + } + + Ok(()) +} + +fn glyph_outlines_freetype( + old_face: &freetype::Face<&[u8]>, + new_face: &freetype::Face<&[u8]>, + mapper: &GlyphRemapper, + gids: &[u16], +) -> Result<(), String> { + for glyph in gids { + let new_glyph = mapper.get(*glyph).unwrap(); + + if old_face.load_glyph(*glyph as u32, LoadFlag::DEFAULT).is_ok() { + let old_outline = old_face + .glyph() + .outline() + .ok_or(format!("failed to load outline for old glyph {}", glyph))?; + + new_face + .load_glyph(new_glyph as u32, LoadFlag::DEFAULT) + .map_err(|_| { + format!("failed to load glyph for new glyph {}", new_glyph) + })?; + let new_outline = new_face + .glyph() + .outline() + .ok_or(format!("failed to load outline for new glyph {}", new_glyph))?; + + let sink1 = Sink::from_freetype(&old_outline); + let sink2 = Sink::from_freetype(&new_outline); + + if sink1 != sink2 { + return Err(format!("{}", glyph)); + } else { + return Ok(()); + } + } + } + + return Ok(()); +} + +fn glyph_outlines_ttf_parser( + old_face: &ttf_parser::Face, + new_face: &ttf_parser::Face, + mapper: &GlyphRemapper, + gids: &[u16], +) -> Result<(), u16> { + for glyph in gids { + let new_glyph = mapper.get(*glyph).unwrap(); + let mut sink1 = Sink::default(); + let mut sink2 = Sink::default(); + + if let Some(_) = old_face.outline_glyph(GlyphId(*glyph), &mut sink1) { + new_face.outline_glyph(GlyphId(new_glyph), &mut sink2); + if sink1 != sink2 { + return Err(*glyph); + } else { + return Ok(()); + } + } else { + return Ok(()); + } + } + + return Ok(()); +} + +#[derive(Debug, Default, PartialEq)] +struct Sink(Vec); + +impl Sink { + fn from_freetype(outline: &freetype::Outline) -> Self { + let mut insts = vec![]; + + for contour in outline.contours_iter() { + for curve in contour { + insts.push(Inst::from_freetype_curve(curve)) + } + } + + Self(insts) + } +} + +#[derive(Debug, PartialEq)] +enum Inst { + MoveTo(f32, f32), + LineTo(f32, f32), + QuadTo(f32, f32, f32, f32), + CurveTo(f32, f32, f32, f32, f32, f32), + Close, +} + +impl Inst { + fn from_freetype_curve(curve: freetype::outline::Curve) -> Self { + match curve { + freetype::outline::Curve::Line(pt) => Inst::LineTo(pt.x as f32, pt.y as f32), + freetype::outline::Curve::Bezier2(pt1, pt2) => { + Inst::QuadTo(pt1.x as f32, pt1.y as f32, pt2.x as f32, pt2.y as f32) + } + freetype::outline::Curve::Bezier3(pt1, pt2, pt3) => Inst::CurveTo( + pt1.x as f32, + pt1.y as f32, + pt2.x as f32, + pt2.y as f32, + pt3.x as f32, + pt3.y as f32, + ), + } + } +} + +impl OutlinePen for Sink { + fn move_to(&mut self, x: f32, y: f32) { + self.0.push(Inst::MoveTo(x, y)); + } + + fn line_to(&mut self, x: f32, y: f32) { + self.0.push(Inst::LineTo(x, y)); + } + + fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { + self.0.push(Inst::QuadTo(x1, y1, x, y)); + } + + fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { + self.0.push(Inst::CurveTo(x1, y1, x2, y2, x, y)); + } + + fn close(&mut self) { + self.0.push(Inst::Close); + } +} + +impl ttf_parser::OutlineBuilder for Sink { + fn move_to(&mut self, x: f32, y: f32) { + self.0.push(Inst::MoveTo(x, y)); + } + + fn line_to(&mut self, x: f32, y: f32) { + self.0.push(Inst::LineTo(x, y)); + } + + fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { + self.0.push(Inst::QuadTo(x1, y1, x, y)); + } + + fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { + self.0.push(Inst::CurveTo(x1, y1, x2, y2, x, y)); + } + + fn close(&mut self) { + self.0.push(Inst::Close); + } +} diff --git a/tests/scripts/gen-tests.py b/tests/scripts/gen-tests.py new file mode 100755 index 00000000..e80a7585 --- /dev/null +++ b/tests/scripts/gen-tests.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +import re + +from pathlib import Path + + +ROOT = Path(__file__).parent.parent +DATA_DIR = ROOT / "data" +FONT_DIR = ROOT / ".." / "fonts" +SUBSETS_PATH = ROOT / "src" / "subsets.rs" +FONT_TOOLS_PATH = ROOT / "src" / "font_tools.rs" + + +def main(): + gen_font_tools_tests() + gen_subset_tests() + + +def gen_font_tools_tests(): + test_string = f"// This file was auto-generated by `{Path(__file__).name}`, do not edit manually.\n\n" + test_string += "#[allow(non_snake_case)]\n\n" + test_string += f"use crate::*;\n\n" + + counters = {} + with open(DATA_DIR / "fonttools.tests") as file: + content = file.read().splitlines() + for line in content: + if line.startswith("//") or len(line.strip()) == 0: + continue + + parts = line.split(";") + + font_file = parts[0] + gids = parts[1] + + if font_file not in counters: + counters[font_file] = 1 + + counter = counters[font_file] + counters[font_file] += 1 + + function_name = f"{font_name_to_function(font_file)}_{counter}" + + test_string += "#[test] " + test_string += f'fn {function_name}() {{test_font_tools("{font_file}", "{gids}", {counter})}}\n' + + with open(Path(FONT_TOOLS_PATH), "w+") as file: + file.write(test_string) + + +def gen_subset_tests(): + test_string = f"// This file was auto-generated by `{Path(__file__).name}`, do not edit manually.\n\n" + test_string += "#[allow(non_snake_case)]\n\n" + test_string += f"use crate::*;\n\n" + + counters = {} + with open(DATA_DIR / "subsets.tests") as file: + content = file.read().splitlines() + for line in content: + if line.startswith("//") or len(line.strip()) == 0: + continue + + parts = line.split(";") + + font_file = parts[0] + gids = parts[1] + + if font_file not in counters: + counters[font_file] = 1 + + counter = counters[font_file] + counters[font_file] += 1 + + functions = ["glyph_metrics", "glyph_outlines_ttf_parser", "glyph_outlines_skrifa", "glyph_outlines_freetype"] + + for function in functions: + function_name = f"{font_name_to_function(font_file)}_{counter}_{function}" + + test_string += "#[test] " + test_string += f'fn {function_name}() {{{function}("{font_file}", "{gids}")}}\n' + + with open(Path(SUBSETS_PATH), "w+") as file: + file.write(test_string) + + +def font_name_to_function(font_name: str): + camel_case_pattern = re.compile(r'(? = std::result::Result>; + +const FONT_TOOLS_REF: bool = false; +const OVERWRITE_REFS: bool = false; + +struct TestContext { + font: Vec, + subset: Vec, + mapper: GlyphRemapper, + gids: Vec, +} + +fn test_font_tools(font_file: &str, gids: &str, num: u16) { + let mut font_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + font_path.push("tests/ttx"); + let _ = std::fs::create_dir_all(&font_path); + + let name = Path::new(font_file); + let stem = name.file_stem().unwrap().to_string_lossy().to_string(); + let ttx_name = format!("{}_{}.ttx", stem, num); + let ttx_ref_name = format!("{}_{}_ref.ttx", stem, num); + let otf_name = format!("{}_{}.otf", stem, num); + let otf_ref_name = format!("{}_{}_ref.otf", stem, num); + let otf_path: PathBuf = [font_path.to_string_lossy().to_string(), otf_name.clone()] + .iter() + .collect(); + let otf_ref_path: PathBuf = + [font_path.to_string_lossy().to_string(), otf_ref_name.clone()] + .iter() + .collect(); + let ttx_path: PathBuf = [font_path.to_string_lossy().to_string(), ttx_name.clone()] + .iter() + .collect(); + let ttx_ref_path: PathBuf = + [font_path.to_string_lossy().to_string(), ttx_ref_name.clone()] + .iter() + .collect(); + + let data = read_file(font_file); + let face = ttf_parser::Face::parse(&data, 0).unwrap(); + let gids_vec: Vec<_> = parse_gids(gids, face.number_of_glyphs()); + let remapper = GlyphRemapper::new_from_glyphs(gids_vec.as_slice()); + let subset = subset(&data, 0, &remapper).unwrap(); + + std::fs::write(otf_path.clone(), subset).unwrap(); + + if FONT_TOOLS_REF { + let font_path = get_font_path(font_file); + Command::new("fonttools") + .args([ + "subset", + font_path.to_str().unwrap(), + "--drop-tables=GSUB,GPOS,GDEF,FFTM,vhea,vmtx,DSIG,VORG,hdmx,cmap,MATH", + &format!("--gids={}", gids), + "--glyph-names", + "--desubroutinize", + "--notdef-outline", + "--no-prune-unicode-ranges", + "--no-prune-codepage-ranges", + &format!("--output-file={}", otf_ref_path.to_str().unwrap()), + ]) + .output() + .unwrap(); + + Command::new("fonttools") + .args([ + "ttx", + "-f", + "-o", + ttx_ref_path.clone().to_str().unwrap(), + otf_ref_path.clone().to_str().unwrap(), + ]) + .output() + .unwrap(); + } + + if !ttx_path.exists() || OVERWRITE_REFS { + Command::new("fonttools") + .args([ + "ttx", + "-f", + "-o", + ttx_path.clone().to_str().unwrap(), + otf_path.clone().to_str().unwrap(), + ]) + .output() + .unwrap(); + } else { + let output = Command::new("fonttools") + .args(["ttx", "-f", "-o", "-", otf_path.clone().to_str().unwrap()]) + .output() + .unwrap() + .stdout; + + let reference = std::fs::read(ttx_path).unwrap(); + assert_eq!( + reference.len(), + output.len(), + "fonttools output didn't match in length." + ); + assert!( + reference.iter().zip(output.iter()).all(|(a, b)| a == b), + "fonttools output didn't match." + ); + } +} + +fn get_font_path(font_file: &str) -> PathBuf { + let mut font_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + font_path.push("fonts"); + font_path.push(font_file); + font_path +} + +fn read_file(font_file: &str) -> Vec { + let font_path = get_font_path(font_file); + std::fs::read(font_path).unwrap() +} + +fn get_test_context(font_file: &str, gids: &str) -> Result { + let data = read_file(font_file); + let face = ttf_parser::Face::parse(&data, 0).unwrap(); + let gids: Vec<_> = parse_gids(gids, face.number_of_glyphs()); + let glyph_remapepr = GlyphRemapper::new_from_glyphs(gids.as_slice()); + let subset = subset(&data, 0, &glyph_remapepr)?; + + Ok(TestContext { font: data, subset, mapper: glyph_remapepr, gids }) +} + +fn parse_gids(gids: &str, max: u16) -> Vec { + if gids == "*" { + return (0..max).collect(); + } + + let split = gids.split(",").filter(|s| !s.is_empty()).collect::>(); + let mut gids = vec![]; + + for el in &split { + if el.contains("-") { + let range = el.split("-").collect::>(); + let first = range[0].parse::().unwrap(); + let second = range[1].parse::().unwrap(); + + gids.extend(first..=second); + } else { + gids.push(el.parse::().unwrap()); + } + } + + gids +} + +fn glyph_metrics(font_file: &str, gids: &str) { + let ctx = get_test_context(font_file, gids).unwrap(); + let old_face = ttf_parser::Face::parse(&ctx.font, 0).unwrap(); + let new_face = ttf_parser::Face::parse(&ctx.subset, 0).unwrap(); + + for glyph in ctx + .gids + .iter() + .copied() + .filter(|g| ctx.gids.contains(g) && *g < old_face.number_of_glyphs()) + { + let mapped = ctx.mapper.get(glyph).unwrap(); + + assert_eq!( + old_face.glyph_bounding_box(GlyphId(glyph)), + new_face.glyph_bounding_box(GlyphId(mapped)), + "{:?}", + format!("metric glyph bounding box didn't match for glyph {}.", glyph) + ); + + assert_eq!( + old_face.glyph_hor_side_bearing(GlyphId(glyph)), + new_face.glyph_hor_side_bearing(GlyphId(mapped)), + "{:?}", + format!( + "metric glyph horizontal side bearing didn't match for glyph {}.", + glyph + ) + ); + + assert_eq!( + old_face.glyph_hor_advance(GlyphId(glyph)), + new_face.glyph_hor_advance(GlyphId(mapped)), + "{:?}", + format!("metric glyph horizontal advance didn't match for glyph {}.", glyph) + ); + + // Assert that each glyph has an identity CID-to-GID mapping. + if let Some(cff) = new_face.tables().cff { + assert_eq!(cff.glyph_cid(GlyphId(mapped)), Some(mapped)) + } + } +} + +fn glyph_outlines_skrifa(font_file: &str, gids: &str) { + let ctx = get_test_context(font_file, gids).unwrap(); + let old_face = skrifa::FontRef::from_index(&ctx.font, 0).unwrap(); + let new_face = skrifa::FontRef::from_index(&ctx.subset, 0).unwrap(); + + let num_glyphs = old_face.maxp().unwrap().num_glyphs(); + + for glyph in (0..num_glyphs).filter(|g| ctx.gids.contains(g)) { + let mut sink1 = Sink(vec![]); + let mut sink2 = Sink(vec![]); + + let new_glyph = ctx.mapper.get(glyph).unwrap(); + let settings = DrawSettings::unhinted(Size::unscaled(), LocationRef::default()); + + if let Some(glyph1) = old_face.outline_glyphs().get(skrifa::GlyphId::new(glyph)) { + glyph1.draw(settings, &mut sink1).unwrap(); + + let settings = + DrawSettings::unhinted(Size::unscaled(), LocationRef::default()); + let glyph2 = new_face + .outline_glyphs() + .get(skrifa::GlyphId::new(new_glyph)) + .expect(&format!("failed to find glyph {} in new face", glyph)); + glyph2.draw(settings, &mut sink2).unwrap(); + assert_eq!(sink1, sink2, "glyph {} drawn with skrifa didn't match.", glyph); + } + } +} + +fn glyph_outlines_ttf_parser(font_file: &str, gids: &str) { + let ctx = get_test_context(font_file, gids).unwrap(); + let old_face = ttf_parser::Face::parse(&ctx.font, 0).unwrap(); + let new_face = ttf_parser::Face::parse(&ctx.subset, 0).unwrap(); + + for glyph in (0..old_face.number_of_glyphs()).filter(|g| ctx.gids.contains(g)) { + let new_glyph = ctx.mapper.get(glyph).unwrap(); + let mut sink1 = Sink::default(); + let mut sink2 = Sink::default(); + + if let Some(_) = old_face.outline_glyph(GlyphId(glyph), &mut sink1) { + new_face.outline_glyph(GlyphId(new_glyph), &mut sink2); + assert_eq!( + sink1, sink2, + "glyph {} drawn with ttf-parser didn't match.", + glyph + ); + } + } +} + +fn glyph_outlines_freetype(font_file: &str, gids: &str) { + let ctx = get_test_context(font_file, gids).unwrap(); + let library = freetype::Library::init().unwrap(); + let old_face = library.new_memory_face2(ctx.font, 0).unwrap(); + let new_face = library.new_memory_face2(ctx.subset, 0).unwrap(); + let num_glyphs = old_face.num_glyphs() as u16; + + for glyph in (0..num_glyphs).filter(|g| ctx.gids.contains(g)) { + let new_glyph = ctx.mapper.get(glyph).unwrap(); + + old_face.load_glyph(glyph as u32, LoadFlag::DEFAULT).unwrap(); + let old_outline = old_face.glyph().outline().unwrap(); + + new_face.load_glyph(new_glyph as u32, LoadFlag::DEFAULT).unwrap(); + let new_outline = new_face.glyph().outline().unwrap(); + + let sink1 = Sink::from_freetype(&old_outline); + let sink2 = Sink::from_freetype(&new_outline); + + assert_eq!(sink1, sink2, "glyph {} drawn with freetype didn't match.", glyph); + } +} + +#[derive(Debug, Default, PartialEq)] +struct Sink(Vec); + +impl Sink { + fn from_freetype(outline: &freetype::Outline) -> Self { + let mut insts = vec![]; + + for contour in outline.contours_iter() { + for curve in contour { + insts.push(Inst::from_freetype_curve(curve)) + } + } + + Self(insts) + } +} + +#[derive(Debug, PartialEq)] +enum Inst { + MoveTo(f32, f32), + LineTo(f32, f32), + QuadTo(f32, f32, f32, f32), + CurveTo(f32, f32, f32, f32, f32, f32), + Close, +} + +impl Inst { + fn from_freetype_curve(curve: freetype::outline::Curve) -> Self { + match curve { + freetype::outline::Curve::Line(pt) => LineTo(pt.x as f32, pt.y as f32), + freetype::outline::Curve::Bezier2(pt1, pt2) => { + QuadTo(pt1.x as f32, pt1.y as f32, pt2.x as f32, pt2.y as f32) + } + freetype::outline::Curve::Bezier3(pt1, pt2, pt3) => CurveTo( + pt1.x as f32, + pt1.y as f32, + pt2.x as f32, + pt2.y as f32, + pt3.x as f32, + pt3.y as f32, + ), + } + } +} + +impl OutlinePen for Sink { + fn move_to(&mut self, x: f32, y: f32) { + self.0.push(Inst::MoveTo(x, y)); + } + + fn line_to(&mut self, x: f32, y: f32) { + self.0.push(Inst::LineTo(x, y)); + } + + fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { + self.0.push(Inst::QuadTo(x1, y1, x, y)); + } + + fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { + self.0.push(Inst::CurveTo(x1, y1, x2, y2, x, y)); + } + + fn close(&mut self) { + self.0.push(Inst::Close); + } +} + +impl ttf_parser::OutlineBuilder for Sink { + fn move_to(&mut self, x: f32, y: f32) { + self.0.push(Inst::MoveTo(x, y)); + } + + fn line_to(&mut self, x: f32, y: f32) { + self.0.push(Inst::LineTo(x, y)); + } + + fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { + self.0.push(Inst::QuadTo(x1, y1, x, y)); + } + + fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { + self.0.push(Inst::CurveTo(x1, y1, x2, y2, x, y)); + } + + fn close(&mut self) { + self.0.push(Inst::Close); + } +} diff --git a/tests/src/subsets.rs b/tests/src/subsets.rs new file mode 100644 index 00000000..a8aaf016 --- /dev/null +++ b/tests/src/subsets.rs @@ -0,0 +1,194 @@ +// This file was auto-generated by `gen-tests.py`, do not edit manually. + +#[allow(non_snake_case)] + +use crate::*; + +#[test] fn clicker_script_regular_1_glyph_metrics() {glyph_metrics("ClickerScript-Regular.ttf", "0")} +#[test] fn clicker_script_regular_1_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("ClickerScript-Regular.ttf", "0")} +#[test] fn clicker_script_regular_1_glyph_outlines_skrifa() {glyph_outlines_skrifa("ClickerScript-Regular.ttf", "0")} +#[test] fn clicker_script_regular_1_glyph_outlines_freetype() {glyph_outlines_freetype("ClickerScript-Regular.ttf", "0")} +#[test] fn clicker_script_regular_2_glyph_metrics() {glyph_metrics("ClickerScript-Regular.ttf", "2")} +#[test] fn clicker_script_regular_2_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("ClickerScript-Regular.ttf", "2")} +#[test] fn clicker_script_regular_2_glyph_outlines_skrifa() {glyph_outlines_skrifa("ClickerScript-Regular.ttf", "2")} +#[test] fn clicker_script_regular_2_glyph_outlines_freetype() {glyph_outlines_freetype("ClickerScript-Regular.ttf", "2")} +#[test] fn clicker_script_regular_3_glyph_metrics() {glyph_metrics("ClickerScript-Regular.ttf", "5,8,10")} +#[test] fn clicker_script_regular_3_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("ClickerScript-Regular.ttf", "5,8,10")} +#[test] fn clicker_script_regular_3_glyph_outlines_skrifa() {glyph_outlines_skrifa("ClickerScript-Regular.ttf", "5,8,10")} +#[test] fn clicker_script_regular_3_glyph_outlines_freetype() {glyph_outlines_freetype("ClickerScript-Regular.ttf", "5,8,10")} +#[test] fn clicker_script_regular_4_glyph_metrics() {glyph_metrics("ClickerScript-Regular.ttf", "0-20,33,35,36,41-47")} +#[test] fn clicker_script_regular_4_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("ClickerScript-Regular.ttf", "0-20,33,35,36,41-47")} +#[test] fn clicker_script_regular_4_glyph_outlines_skrifa() {glyph_outlines_skrifa("ClickerScript-Regular.ttf", "0-20,33,35,36,41-47")} +#[test] fn clicker_script_regular_4_glyph_outlines_freetype() {glyph_outlines_freetype("ClickerScript-Regular.ttf", "0-20,33,35,36,41-47")} +#[test] fn clicker_script_regular_5_glyph_metrics() {glyph_metrics("ClickerScript-Regular.ttf", "100-200,202,301,304")} +#[test] fn clicker_script_regular_5_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("ClickerScript-Regular.ttf", "100-200,202,301,304")} +#[test] fn clicker_script_regular_5_glyph_outlines_skrifa() {glyph_outlines_skrifa("ClickerScript-Regular.ttf", "100-200,202,301,304")} +#[test] fn clicker_script_regular_5_glyph_outlines_freetype() {glyph_outlines_freetype("ClickerScript-Regular.ttf", "100-200,202,301,304")} +#[test] fn clicker_script_regular_6_glyph_metrics() {glyph_metrics("ClickerScript-Regular.ttf", "10,100-105,108,130-145,234,247,253,267,278-283,340")} +#[test] fn clicker_script_regular_6_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("ClickerScript-Regular.ttf", "10,100-105,108,130-145,234,247,253,267,278-283,340")} +#[test] fn clicker_script_regular_6_glyph_outlines_skrifa() {glyph_outlines_skrifa("ClickerScript-Regular.ttf", "10,100-105,108,130-145,234,247,253,267,278-283,340")} +#[test] fn clicker_script_regular_6_glyph_outlines_freetype() {glyph_outlines_freetype("ClickerScript-Regular.ttf", "10,100-105,108,130-145,234,247,253,267,278-283,340")} +#[test] fn clicker_script_regular_7_glyph_metrics() {glyph_metrics("ClickerScript-Regular.ttf", "*")} +#[test] fn clicker_script_regular_7_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("ClickerScript-Regular.ttf", "*")} +#[test] fn clicker_script_regular_7_glyph_outlines_skrifa() {glyph_outlines_skrifa("ClickerScript-Regular.ttf", "*")} +#[test] fn clicker_script_regular_7_glyph_outlines_freetype() {glyph_outlines_freetype("ClickerScript-Regular.ttf", "*")} +#[test] fn deja_vu_sans_mono_1_glyph_metrics() {glyph_metrics("DejaVuSansMono.ttf", "100-105,140-155")} +#[test] fn deja_vu_sans_mono_1_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("DejaVuSansMono.ttf", "100-105,140-155")} +#[test] fn deja_vu_sans_mono_1_glyph_outlines_skrifa() {glyph_outlines_skrifa("DejaVuSansMono.ttf", "100-105,140-155")} +#[test] fn deja_vu_sans_mono_1_glyph_outlines_freetype() {glyph_outlines_freetype("DejaVuSansMono.ttf", "100-105,140-155")} +#[test] fn deja_vu_sans_mono_2_glyph_metrics() {glyph_metrics("DejaVuSansMono.ttf", "10-30,40-80")} +#[test] fn deja_vu_sans_mono_2_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("DejaVuSansMono.ttf", "10-30,40-80")} +#[test] fn deja_vu_sans_mono_2_glyph_outlines_skrifa() {glyph_outlines_skrifa("DejaVuSansMono.ttf", "10-30,40-80")} +#[test] fn deja_vu_sans_mono_2_glyph_outlines_freetype() {glyph_outlines_freetype("DejaVuSansMono.ttf", "10-30,40-80")} +#[test] fn deja_vu_sans_mono_3_glyph_metrics() {glyph_metrics("DejaVuSansMono.ttf", "100-245,2456-2609,800-900")} +#[test] fn deja_vu_sans_mono_3_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("DejaVuSansMono.ttf", "100-245,2456-2609,800-900")} +#[test] fn deja_vu_sans_mono_3_glyph_outlines_skrifa() {glyph_outlines_skrifa("DejaVuSansMono.ttf", "100-245,2456-2609,800-900")} +#[test] fn deja_vu_sans_mono_3_glyph_outlines_freetype() {glyph_outlines_freetype("DejaVuSansMono.ttf", "100-245,2456-2609,800-900")} +#[test] fn deja_vu_sans_mono_4_glyph_metrics() {glyph_metrics("DejaVuSansMono.ttf", "600-620,3000-3146")} +#[test] fn deja_vu_sans_mono_4_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("DejaVuSansMono.ttf", "600-620,3000-3146")} +#[test] fn deja_vu_sans_mono_4_glyph_outlines_skrifa() {glyph_outlines_skrifa("DejaVuSansMono.ttf", "600-620,3000-3146")} +#[test] fn deja_vu_sans_mono_4_glyph_outlines_freetype() {glyph_outlines_freetype("DejaVuSansMono.ttf", "600-620,3000-3146")} +#[test] fn deja_vu_sans_mono_5_glyph_metrics() {glyph_metrics("DejaVuSansMono.ttf", "*")} +#[test] fn deja_vu_sans_mono_5_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("DejaVuSansMono.ttf", "*")} +#[test] fn deja_vu_sans_mono_5_glyph_outlines_skrifa() {glyph_outlines_skrifa("DejaVuSansMono.ttf", "*")} +#[test] fn deja_vu_sans_mono_5_glyph_outlines_freetype() {glyph_outlines_freetype("DejaVuSansMono.ttf", "*")} +#[test] fn latin_modern_roman_regular_1_glyph_metrics() {glyph_metrics("LatinModernRoman-Regular.otf", "5-15,60-57")} +#[test] fn latin_modern_roman_regular_1_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("LatinModernRoman-Regular.otf", "5-15,60-57")} +#[test] fn latin_modern_roman_regular_1_glyph_outlines_skrifa() {glyph_outlines_skrifa("LatinModernRoman-Regular.otf", "5-15,60-57")} +#[test] fn latin_modern_roman_regular_1_glyph_outlines_freetype() {glyph_outlines_freetype("LatinModernRoman-Regular.otf", "5-15,60-57")} +#[test] fn latin_modern_roman_regular_2_glyph_metrics() {glyph_metrics("LatinModernRoman-Regular.otf", "100-130,200-280")} +#[test] fn latin_modern_roman_regular_2_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("LatinModernRoman-Regular.otf", "100-130,200-280")} +#[test] fn latin_modern_roman_regular_2_glyph_outlines_skrifa() {glyph_outlines_skrifa("LatinModernRoman-Regular.otf", "100-130,200-280")} +#[test] fn latin_modern_roman_regular_2_glyph_outlines_freetype() {glyph_outlines_freetype("LatinModernRoman-Regular.otf", "100-130,200-280")} +#[test] fn latin_modern_roman_regular_3_glyph_metrics() {glyph_metrics("LatinModernRoman-Regular.otf", "370-400,526-612,701,710,800")} +#[test] fn latin_modern_roman_regular_3_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("LatinModernRoman-Regular.otf", "370-400,526-612,701,710,800")} +#[test] fn latin_modern_roman_regular_3_glyph_outlines_skrifa() {glyph_outlines_skrifa("LatinModernRoman-Regular.otf", "370-400,526-612,701,710,800")} +#[test] fn latin_modern_roman_regular_3_glyph_outlines_freetype() {glyph_outlines_freetype("LatinModernRoman-Regular.otf", "370-400,526-612,701,710,800")} +#[test] fn latin_modern_roman_regular_4_glyph_metrics() {glyph_metrics("LatinModernRoman-Regular.otf", "*")} +#[test] fn latin_modern_roman_regular_4_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("LatinModernRoman-Regular.otf", "*")} +#[test] fn latin_modern_roman_regular_4_glyph_outlines_skrifa() {glyph_outlines_skrifa("LatinModernRoman-Regular.otf", "*")} +#[test] fn latin_modern_roman_regular_4_glyph_outlines_freetype() {glyph_outlines_freetype("LatinModernRoman-Regular.otf", "*")} +#[test] fn m_p_l_u_s1p_regular_1_glyph_metrics() {glyph_metrics("MPLUS1p-Regular.ttf", "0,1")} +#[test] fn m_p_l_u_s1p_regular_1_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("MPLUS1p-Regular.ttf", "0,1")} +#[test] fn m_p_l_u_s1p_regular_1_glyph_outlines_skrifa() {glyph_outlines_skrifa("MPLUS1p-Regular.ttf", "0,1")} +#[test] fn m_p_l_u_s1p_regular_1_glyph_outlines_freetype() {glyph_outlines_freetype("MPLUS1p-Regular.ttf", "0,1")} +#[test] fn m_p_l_u_s1p_regular_2_glyph_metrics() {glyph_metrics("MPLUS1p-Regular.ttf", "346")} +#[test] fn m_p_l_u_s1p_regular_2_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("MPLUS1p-Regular.ttf", "346")} +#[test] fn m_p_l_u_s1p_regular_2_glyph_outlines_skrifa() {glyph_outlines_skrifa("MPLUS1p-Regular.ttf", "346")} +#[test] fn m_p_l_u_s1p_regular_2_glyph_outlines_freetype() {glyph_outlines_freetype("MPLUS1p-Regular.ttf", "346")} +#[test] fn m_p_l_u_s1p_regular_3_glyph_metrics() {glyph_metrics("MPLUS1p-Regular.ttf", "3,45,98,120,245,389,1043,1055-1063")} +#[test] fn m_p_l_u_s1p_regular_3_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("MPLUS1p-Regular.ttf", "3,45,98,120,245,389,1043,1055-1063")} +#[test] fn m_p_l_u_s1p_regular_3_glyph_outlines_skrifa() {glyph_outlines_skrifa("MPLUS1p-Regular.ttf", "3,45,98,120,245,389,1043,1055-1063")} +#[test] fn m_p_l_u_s1p_regular_3_glyph_outlines_freetype() {glyph_outlines_freetype("MPLUS1p-Regular.ttf", "3,45,98,120,245,389,1043,1055-1063")} +#[test] fn m_p_l_u_s1p_regular_4_glyph_metrics() {glyph_metrics("MPLUS1p-Regular.ttf", "2030-2310,4590-5120")} +#[test] fn m_p_l_u_s1p_regular_4_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("MPLUS1p-Regular.ttf", "2030-2310,4590-5120")} +#[test] fn m_p_l_u_s1p_regular_4_glyph_outlines_skrifa() {glyph_outlines_skrifa("MPLUS1p-Regular.ttf", "2030-2310,4590-5120")} +#[test] fn m_p_l_u_s1p_regular_4_glyph_outlines_freetype() {glyph_outlines_freetype("MPLUS1p-Regular.ttf", "2030-2310,4590-5120")} +#[test] fn m_p_l_u_s1p_regular_5_glyph_metrics() {glyph_metrics("MPLUS1p-Regular.ttf", "*")} +#[test] fn m_p_l_u_s1p_regular_5_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("MPLUS1p-Regular.ttf", "*")} +#[test] fn m_p_l_u_s1p_regular_5_glyph_outlines_skrifa() {glyph_outlines_skrifa("MPLUS1p-Regular.ttf", "*")} +#[test] fn m_p_l_u_s1p_regular_5_glyph_outlines_freetype() {glyph_outlines_freetype("MPLUS1p-Regular.ttf", "*")} +#[test] fn noto_sans_c_j_ksc_regular_1_glyph_metrics() {glyph_metrics("NotoSansCJKsc-Regular.otf", "1-2")} +#[test] fn noto_sans_c_j_ksc_regular_1_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NotoSansCJKsc-Regular.otf", "1-2")} +#[test] fn noto_sans_c_j_ksc_regular_1_glyph_outlines_skrifa() {glyph_outlines_skrifa("NotoSansCJKsc-Regular.otf", "1-2")} +#[test] fn noto_sans_c_j_ksc_regular_1_glyph_outlines_freetype() {glyph_outlines_freetype("NotoSansCJKsc-Regular.otf", "1-2")} +#[test] fn noto_sans_c_j_ksc_regular_2_glyph_metrics() {glyph_metrics("NotoSansCJKsc-Regular.otf", "4500-5746")} +#[test] fn noto_sans_c_j_ksc_regular_2_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NotoSansCJKsc-Regular.otf", "4500-5746")} +#[test] fn noto_sans_c_j_ksc_regular_2_glyph_outlines_skrifa() {glyph_outlines_skrifa("NotoSansCJKsc-Regular.otf", "4500-5746")} +#[test] fn noto_sans_c_j_ksc_regular_2_glyph_outlines_freetype() {glyph_outlines_freetype("NotoSansCJKsc-Regular.otf", "4500-5746")} +#[test] fn noto_sans_c_j_ksc_regular_3_glyph_metrics() {glyph_metrics("NotoSansCJKsc-Regular.otf", "12000-14000,34-60,80-90")} +#[test] fn noto_sans_c_j_ksc_regular_3_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NotoSansCJKsc-Regular.otf", "12000-14000,34-60,80-90")} +#[test] fn noto_sans_c_j_ksc_regular_3_glyph_outlines_skrifa() {glyph_outlines_skrifa("NotoSansCJKsc-Regular.otf", "12000-14000,34-60,80-90")} +#[test] fn noto_sans_c_j_ksc_regular_3_glyph_outlines_freetype() {glyph_outlines_freetype("NotoSansCJKsc-Regular.otf", "12000-14000,34-60,80-90")} +#[test] fn noto_sans_c_j_ksc_regular_4_glyph_metrics() {glyph_metrics("NotoSansCJKsc-Regular.otf", "45-90,16000-16897,23569-25697")} +#[test] fn noto_sans_c_j_ksc_regular_4_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NotoSansCJKsc-Regular.otf", "45-90,16000-16897,23569-25697")} +#[test] fn noto_sans_c_j_ksc_regular_4_glyph_outlines_skrifa() {glyph_outlines_skrifa("NotoSansCJKsc-Regular.otf", "45-90,16000-16897,23569-25697")} +#[test] fn noto_sans_c_j_ksc_regular_4_glyph_outlines_freetype() {glyph_outlines_freetype("NotoSansCJKsc-Regular.otf", "45-90,16000-16897,23569-25697")} +#[test] fn noto_sans_c_j_ksc_regular_5_glyph_metrics() {glyph_metrics("NotoSansCJKsc-Regular.otf", "65532")} +#[test] fn noto_sans_c_j_ksc_regular_5_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NotoSansCJKsc-Regular.otf", "65532")} +#[test] fn noto_sans_c_j_ksc_regular_5_glyph_outlines_skrifa() {glyph_outlines_skrifa("NotoSansCJKsc-Regular.otf", "65532")} +#[test] fn noto_sans_c_j_ksc_regular_5_glyph_outlines_freetype() {glyph_outlines_freetype("NotoSansCJKsc-Regular.otf", "65532")} +#[test] fn noto_sans_c_j_ksc_regular_6_glyph_metrics() {glyph_metrics("NotoSansCJKsc-Regular.otf", "*")} +#[test] fn noto_sans_c_j_ksc_regular_6_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NotoSansCJKsc-Regular.otf", "*")} +#[test] fn noto_sans_c_j_ksc_regular_6_glyph_outlines_skrifa() {glyph_outlines_skrifa("NotoSansCJKsc-Regular.otf", "*")} +#[test] fn noto_sans_c_j_ksc_regular_6_glyph_outlines_freetype() {glyph_outlines_freetype("NotoSansCJKsc-Regular.otf", "*")} +#[test] fn noto_sans_regular_1_glyph_metrics() {glyph_metrics("NotoSans-Regular.ttf", "567-570")} +#[test] fn noto_sans_regular_1_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NotoSans-Regular.ttf", "567-570")} +#[test] fn noto_sans_regular_1_glyph_outlines_skrifa() {glyph_outlines_skrifa("NotoSans-Regular.ttf", "567-570")} +#[test] fn noto_sans_regular_1_glyph_outlines_freetype() {glyph_outlines_freetype("NotoSans-Regular.ttf", "567-570")} +#[test] fn noto_sans_regular_2_glyph_metrics() {glyph_metrics("NotoSans-Regular.ttf", "1034-1037,3020-3045")} +#[test] fn noto_sans_regular_2_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NotoSans-Regular.ttf", "1034-1037,3020-3045")} +#[test] fn noto_sans_regular_2_glyph_outlines_skrifa() {glyph_outlines_skrifa("NotoSans-Regular.ttf", "1034-1037,3020-3045")} +#[test] fn noto_sans_regular_2_glyph_outlines_freetype() {glyph_outlines_freetype("NotoSans-Regular.ttf", "1034-1037,3020-3045")} +#[test] fn noto_sans_regular_3_glyph_metrics() {glyph_metrics("NotoSans-Regular.ttf", "3,6,8,9,11")} +#[test] fn noto_sans_regular_3_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NotoSans-Regular.ttf", "3,6,8,9,11")} +#[test] fn noto_sans_regular_3_glyph_outlines_skrifa() {glyph_outlines_skrifa("NotoSans-Regular.ttf", "3,6,8,9,11")} +#[test] fn noto_sans_regular_3_glyph_outlines_freetype() {glyph_outlines_freetype("NotoSans-Regular.ttf", "3,6,8,9,11")} +#[test] fn noto_sans_regular_4_glyph_metrics() {glyph_metrics("NotoSans-Regular.ttf", "10-30")} +#[test] fn noto_sans_regular_4_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NotoSans-Regular.ttf", "10-30")} +#[test] fn noto_sans_regular_4_glyph_outlines_skrifa() {glyph_outlines_skrifa("NotoSans-Regular.ttf", "10-30")} +#[test] fn noto_sans_regular_4_glyph_outlines_freetype() {glyph_outlines_freetype("NotoSans-Regular.ttf", "10-30")} +#[test] fn noto_sans_regular_5_glyph_metrics() {glyph_metrics("NotoSans-Regular.ttf", "30-50,132,137")} +#[test] fn noto_sans_regular_5_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NotoSans-Regular.ttf", "30-50,132,137")} +#[test] fn noto_sans_regular_5_glyph_outlines_skrifa() {glyph_outlines_skrifa("NotoSans-Regular.ttf", "30-50,132,137")} +#[test] fn noto_sans_regular_5_glyph_outlines_freetype() {glyph_outlines_freetype("NotoSans-Regular.ttf", "30-50,132,137")} +#[test] fn noto_sans_regular_6_glyph_metrics() {glyph_metrics("NotoSans-Regular.ttf", "20-25,30,40,45,47,48,52-70,300-350,500-522,3001")} +#[test] fn noto_sans_regular_6_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NotoSans-Regular.ttf", "20-25,30,40,45,47,48,52-70,300-350,500-522,3001")} +#[test] fn noto_sans_regular_6_glyph_outlines_skrifa() {glyph_outlines_skrifa("NotoSans-Regular.ttf", "20-25,30,40,45,47,48,52-70,300-350,500-522,3001")} +#[test] fn noto_sans_regular_6_glyph_outlines_freetype() {glyph_outlines_freetype("NotoSans-Regular.ttf", "20-25,30,40,45,47,48,52-70,300-350,500-522,3001")} +#[test] fn noto_sans_regular_7_glyph_metrics() {glyph_metrics("NotoSans-Regular.ttf", "*")} +#[test] fn noto_sans_regular_7_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NotoSans-Regular.ttf", "*")} +#[test] fn noto_sans_regular_7_glyph_outlines_skrifa() {glyph_outlines_skrifa("NotoSans-Regular.ttf", "*")} +#[test] fn noto_sans_regular_7_glyph_outlines_freetype() {glyph_outlines_freetype("NotoSans-Regular.ttf", "*")} +#[test] fn roboto_regular_1_glyph_metrics() {glyph_metrics("Roboto-Regular.ttf", "3,4")} +#[test] fn roboto_regular_1_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("Roboto-Regular.ttf", "3,4")} +#[test] fn roboto_regular_1_glyph_outlines_skrifa() {glyph_outlines_skrifa("Roboto-Regular.ttf", "3,4")} +#[test] fn roboto_regular_1_glyph_outlines_freetype() {glyph_outlines_freetype("Roboto-Regular.ttf", "3,4")} +#[test] fn roboto_regular_2_glyph_metrics() {glyph_metrics("Roboto-Regular.ttf", "40-60,90-130,156,180")} +#[test] fn roboto_regular_2_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("Roboto-Regular.ttf", "40-60,90-130,156,180")} +#[test] fn roboto_regular_2_glyph_outlines_skrifa() {glyph_outlines_skrifa("Roboto-Regular.ttf", "40-60,90-130,156,180")} +#[test] fn roboto_regular_2_glyph_outlines_freetype() {glyph_outlines_freetype("Roboto-Regular.ttf", "40-60,90-130,156,180")} +#[test] fn roboto_regular_3_glyph_metrics() {glyph_metrics("Roboto-Regular.ttf", "45-60,120-145,750-780,810,812,830,850,900")} +#[test] fn roboto_regular_3_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("Roboto-Regular.ttf", "45-60,120-145,750-780,810,812,830,850,900")} +#[test] fn roboto_regular_3_glyph_outlines_skrifa() {glyph_outlines_skrifa("Roboto-Regular.ttf", "45-60,120-145,750-780,810,812,830,850,900")} +#[test] fn roboto_regular_3_glyph_outlines_freetype() {glyph_outlines_freetype("Roboto-Regular.ttf", "45-60,120-145,750-780,810,812,830,850,900")} +#[test] fn roboto_regular_4_glyph_metrics() {glyph_metrics("Roboto-Regular.ttf", "757")} +#[test] fn roboto_regular_4_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("Roboto-Regular.ttf", "757")} +#[test] fn roboto_regular_4_glyph_outlines_skrifa() {glyph_outlines_skrifa("Roboto-Regular.ttf", "757")} +#[test] fn roboto_regular_4_glyph_outlines_freetype() {glyph_outlines_freetype("Roboto-Regular.ttf", "757")} +#[test] fn roboto_regular_5_glyph_metrics() {glyph_metrics("Roboto-Regular.ttf", "*")} +#[test] fn roboto_regular_5_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("Roboto-Regular.ttf", "*")} +#[test] fn roboto_regular_5_glyph_outlines_skrifa() {glyph_outlines_skrifa("Roboto-Regular.ttf", "*")} +#[test] fn roboto_regular_5_glyph_outlines_freetype() {glyph_outlines_freetype("Roboto-Regular.ttf", "*")} +#[test] fn new_c_m_math_regular_1_glyph_metrics() {glyph_metrics("NewCMMath-Regular.otf", "400-402")} +#[test] fn new_c_m_math_regular_1_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NewCMMath-Regular.otf", "400-402")} +#[test] fn new_c_m_math_regular_1_glyph_outlines_skrifa() {glyph_outlines_skrifa("NewCMMath-Regular.otf", "400-402")} +#[test] fn new_c_m_math_regular_1_glyph_outlines_freetype() {glyph_outlines_freetype("NewCMMath-Regular.otf", "400-402")} +#[test] fn new_c_m_math_regular_2_glyph_metrics() {glyph_metrics("NewCMMath-Regular.otf", "256-300,113-117")} +#[test] fn new_c_m_math_regular_2_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NewCMMath-Regular.otf", "256-300,113-117")} +#[test] fn new_c_m_math_regular_2_glyph_outlines_skrifa() {glyph_outlines_skrifa("NewCMMath-Regular.otf", "256-300,113-117")} +#[test] fn new_c_m_math_regular_2_glyph_outlines_freetype() {glyph_outlines_freetype("NewCMMath-Regular.otf", "256-300,113-117")} +#[test] fn new_c_m_math_regular_3_glyph_metrics() {glyph_metrics("NewCMMath-Regular.otf", "137-400,890-900,2345-2400")} +#[test] fn new_c_m_math_regular_3_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NewCMMath-Regular.otf", "137-400,890-900,2345-2400")} +#[test] fn new_c_m_math_regular_3_glyph_outlines_skrifa() {glyph_outlines_skrifa("NewCMMath-Regular.otf", "137-400,890-900,2345-2400")} +#[test] fn new_c_m_math_regular_3_glyph_outlines_freetype() {glyph_outlines_freetype("NewCMMath-Regular.otf", "137-400,890-900,2345-2400")} +#[test] fn new_c_m_math_regular_4_glyph_metrics() {glyph_metrics("NewCMMath-Regular.otf", "8")} +#[test] fn new_c_m_math_regular_4_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NewCMMath-Regular.otf", "8")} +#[test] fn new_c_m_math_regular_4_glyph_outlines_skrifa() {glyph_outlines_skrifa("NewCMMath-Regular.otf", "8")} +#[test] fn new_c_m_math_regular_4_glyph_outlines_freetype() {glyph_outlines_freetype("NewCMMath-Regular.otf", "8")} +#[test] fn new_c_m_math_regular_5_glyph_metrics() {glyph_metrics("NewCMMath-Regular.otf", "4622")} +#[test] fn new_c_m_math_regular_5_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NewCMMath-Regular.otf", "4622")} +#[test] fn new_c_m_math_regular_5_glyph_outlines_skrifa() {glyph_outlines_skrifa("NewCMMath-Regular.otf", "4622")} +#[test] fn new_c_m_math_regular_5_glyph_outlines_freetype() {glyph_outlines_freetype("NewCMMath-Regular.otf", "4622")} +#[test] fn new_c_m_math_regular_6_glyph_metrics() {glyph_metrics("NewCMMath-Regular.otf", "*")} +#[test] fn new_c_m_math_regular_6_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("NewCMMath-Regular.otf", "*")} +#[test] fn new_c_m_math_regular_6_glyph_outlines_skrifa() {glyph_outlines_skrifa("NewCMMath-Regular.otf", "*")} +#[test] fn new_c_m_math_regular_6_glyph_outlines_freetype() {glyph_outlines_freetype("NewCMMath-Regular.otf", "*")} +#[test] fn syne_regular_subset_1_glyph_metrics() {glyph_metrics("Syne-Regular_subset.otf", "5")} +#[test] fn syne_regular_subset_1_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("Syne-Regular_subset.otf", "5")} +#[test] fn syne_regular_subset_1_glyph_outlines_skrifa() {glyph_outlines_skrifa("Syne-Regular_subset.otf", "5")} +#[test] fn syne_regular_subset_1_glyph_outlines_freetype() {glyph_outlines_freetype("Syne-Regular_subset.otf", "5")} +#[test] fn test_t_t_cttc_1_glyph_metrics() {glyph_metrics("TestTTC.ttc", "*")} +#[test] fn test_t_t_cttc_1_glyph_outlines_ttf_parser() {glyph_outlines_ttf_parser("TestTTC.ttc", "*")} +#[test] fn test_t_t_cttc_1_glyph_outlines_skrifa() {glyph_outlines_skrifa("TestTTC.ttc", "*")} +#[test] fn test_t_t_cttc_1_glyph_outlines_freetype() {glyph_outlines_freetype("TestTTC.ttc", "*")} diff --git a/tests/ttx/ClickerScript-Regular_1.ttx b/tests/ttx/ClickerScript-Regular_1.ttx new file mode 100644 index 00000000..5a450812 --- /dev/null +++ b/tests/ttx/ClickerScript-Regular_1.ttx @@ -0,0 +1,1052 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PUSHB[ ] /* 1 value pushed */ + 0 + FDEF[ ] /* FunctionDefinition */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 9 + LT[ ] /* LessThan */ + IF[ ] /* If */ + PUSHB[ ] /* 2 values pushed */ + 1 1 + INSTCTRL[ ] /* SetInstrExecControl */ + EIF[ ] /* EndIf */ + PUSHW[ ] /* 1 value pushed */ + 511 + SCANCTRL[ ] /* ScanConversionControl */ + PUSHB[ ] /* 1 value pushed */ + 68 + SCVTCI[ ] /* SetCVTCutIn */ + PUSHB[ ] /* 2 values pushed */ + 9 3 + SDS[ ] /* SetDeltaShiftInGState */ + SDB[ ] /* SetDeltaBaseInGState */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 1 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + DUP[ ] /* DuplicateTopStack */ + RCVT[ ] /* ReadCVT */ + ROUND[01] /* Round */ + WCVTP[ ] /* WriteCVTInPixels */ + PUSHB[ ] /* 1 value pushed */ + 1 + ADD[ ] /* Add */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 2 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 1 + LOOPCALL[ ] /* LoopAndCallFunction */ + POP[ ] /* PopTopStack */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 3 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + GC[0] /* GetCoordOnPVector */ + PUSHB[ ] /* 1 value pushed */ + 3 + CINDEX[ ] /* CopyXToTopStack */ + GC[0] /* GetCoordOnPVector */ + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + MD[0] /* MeasureDistance */ + ABS[ ] /* Absolute */ + ROLL[ ] /* RollTopThreeStack */ + DUP[ ] /* DuplicateTopStack */ + GC[0] /* GetCoordOnPVector */ + DUP[ ] /* DuplicateTopStack */ + ROUND[00] /* Round */ + SUB[ ] /* Subtract */ + ABS[ ] /* Absolute */ + PUSHB[ ] /* 1 value pushed */ + 4 + CINDEX[ ] /* CopyXToTopStack */ + GC[0] /* GetCoordOnPVector */ + DUP[ ] /* DuplicateTopStack */ + ROUND[00] /* Round */ + SUB[ ] /* Subtract */ + ABS[ ] /* Absolute */ + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + NEG[ ] /* Negate */ + ROLL[ ] /* RollTopThreeStack */ + EIF[ ] /* EndIf */ + MDAP[1] /* MoveDirectAbsPt */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + ROUND[01] /* Round */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + EQ[ ] /* Equal */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + PUSHB[ ] /* 1 value pushed */ + 64 + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + ROUND[01] /* Round */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + EQ[ ] /* Equal */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + PUSHB[ ] /* 1 value pushed */ + 64 + NEG[ ] /* Negate */ + EIF[ ] /* EndIf */ + EIF[ ] /* EndIf */ + MSIRP[0] /* MoveStackIndirRelPt */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 4 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + GC[0] /* GetCoordOnPVector */ + PUSHB[ ] /* 1 value pushed */ + 4 + CINDEX[ ] /* CopyXToTopStack */ + GC[0] /* GetCoordOnPVector */ + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + ROLL[ ] /* RollTopThreeStack */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + GC[0] /* GetCoordOnPVector */ + DUP[ ] /* DuplicateTopStack */ + ROUND[10] /* Round */ + SUB[ ] /* Subtract */ + ABS[ ] /* Absolute */ + PUSHB[ ] /* 1 value pushed */ + 4 + CINDEX[ ] /* CopyXToTopStack */ + GC[0] /* GetCoordOnPVector */ + DUP[ ] /* DuplicateTopStack */ + ROUND[10] /* Round */ + SUB[ ] /* Subtract */ + ABS[ ] /* Absolute */ + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + ROLL[ ] /* RollTopThreeStack */ + EIF[ ] /* EndIf */ + MDAP[1] /* MoveDirectAbsPt */ + MIRP[11101] /* MoveIndirectRelPt */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 5 + FDEF[ ] /* FunctionDefinition */ + MPPEM[ ] /* MeasurePixelPerEm */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + LT[ ] /* LessThan */ + IF[ ] /* If */ + LTEQ[ ] /* LessThenOrEqual */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 128 + WCVTP[ ] /* WriteCVTInPixels */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 1 value pushed */ + 64 + WCVTP[ ] /* WriteCVTInPixels */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + DUP[ ] /* DuplicateTopStack */ + RCVT[ ] /* ReadCVT */ + PUSHB[ ] /* 1 value pushed */ + 192 + LT[ ] /* LessThan */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 192 + WCVTP[ ] /* WriteCVTInPixels */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 6 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + DUP[ ] /* DuplicateTopStack */ + RCVT[ ] /* ReadCVT */ + ROUND[01] /* Round */ + WCVTP[ ] /* WriteCVTInPixels */ + PUSHB[ ] /* 1 value pushed */ + 1 + ADD[ ] /* Add */ + DUP[ ] /* DuplicateTopStack */ + DUP[ ] /* DuplicateTopStack */ + RCVT[ ] /* ReadCVT */ + RDTG[ ] /* RoundDownToGrid */ + ROUND[01] /* Round */ + RTG[ ] /* RoundToGrid */ + WCVTP[ ] /* WriteCVTInPixels */ + PUSHB[ ] /* 1 value pushed */ + 1 + ADD[ ] /* Add */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 7 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 6 + LOOPCALL[ ] /* LoopAndCallFunction */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 8 + FDEF[ ] /* FunctionDefinition */ + MPPEM[ ] /* MeasurePixelPerEm */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 64 + ELSE[ ] /* Else */ + PUSHB[ ] /* 1 value pushed */ + 0 + EIF[ ] /* EndIf */ + ROLL[ ] /* RollTopThreeStack */ + ROLL[ ] /* RollTopThreeStack */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + PUSHB[ ] /* 1 value pushed */ + 128 + ROLL[ ] /* RollTopThreeStack */ + ROLL[ ] /* RollTopThreeStack */ + ELSE[ ] /* Else */ + ROLL[ ] /* RollTopThreeStack */ + SWAP[ ] /* SwapTopStack */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + PUSHB[ ] /* 1 value pushed */ + 192 + ROLL[ ] /* RollTopThreeStack */ + ROLL[ ] /* RollTopThreeStack */ + ELSE[ ] /* Else */ + ROLL[ ] /* RollTopThreeStack */ + SWAP[ ] /* SwapTopStack */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + PUSHW[ ] /* 1 value pushed */ + 256 + ROLL[ ] /* RollTopThreeStack */ + ROLL[ ] /* RollTopThreeStack */ + ELSE[ ] /* Else */ + ROLL[ ] /* RollTopThreeStack */ + SWAP[ ] /* SwapTopStack */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + PUSHW[ ] /* 1 value pushed */ + 320 + ROLL[ ] /* RollTopThreeStack */ + ROLL[ ] /* RollTopThreeStack */ + ELSE[ ] /* Else */ + ROLL[ ] /* RollTopThreeStack */ + SWAP[ ] /* SwapTopStack */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 3 + CINDEX[ ] /* CopyXToTopStack */ + RCVT[ ] /* ReadCVT */ + PUSHW[ ] /* 1 value pushed */ + 384 + LT[ ] /* LessThan */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + PUSHW[ ] /* 1 value pushed */ + 384 + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 1 value pushed */ + 3 + CINDEX[ ] /* CopyXToTopStack */ + RCVT[ ] /* ReadCVT */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + WCVTP[ ] /* WriteCVTInPixels */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 9 + FDEF[ ] /* FunctionDefinition */ + MPPEM[ ] /* MeasurePixelPerEm */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + RCVT[ ] /* ReadCVT */ + WCVTP[ ] /* WriteCVTInPixels */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + + + + + + PUSHW[ ] /* 1 value pushed */ + 511 + SCANCTRL[ ] /* ScanConversionControl */ + PUSHB[ ] /* 1 value pushed */ + 4 + SCANTYPE[ ] /* ScanType */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright (c) 2012 by Brian J. Bonislawsky and Jim Lyles for Astigmatic (AOETI) (astigma@astigmatic.com), with Reserved Font Name "Clicker Script" + + + Clicker Script + + + Regular + + + Astigmatic(AOETI): Risque: 2012 + + + Clicker Script + + + Version 1.000 + + + ClickerScript-Regular + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/ttx/DejaVuSansMono_1.ttx b/tests/ttx/DejaVuSansMono_1.ttx new file mode 100644 index 00000000..0544361f --- /dev/null +++ b/tests/ttx/DejaVuSansMono_1.ttx @@ -0,0 +1,2690 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PUSHB[ ] /* 8 values pushed */ + 7 6 5 4 3 2 1 0 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 1 value pushed */ + 2 + CINDEX[ ] /* CopyXToTopStack */ + MD[0] /* MeasureDistance */ + ABS[ ] /* Absolute */ + PUSHB[ ] /* 1 value pushed */ + 64 + LTEQ[ ] /* LessThenOrEqual */ + IF[ ] /* If */ + DUP[ ] /* DuplicateTopStack */ + MDRP[01000] /* MoveDirectRelPt */ + EIF[ ] /* EndIf */ + POP[ ] /* PopTopStack */ + ENDF[ ] /* EndFunctionDefinition */ + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 2 + CINDEX[ ] /* CopyXToTopStack */ + MD[0] /* MeasureDistance */ + ABS[ ] /* Absolute */ + PUSHB[ ] /* 1 value pushed */ + 64 + LTEQ[ ] /* LessThenOrEqual */ + IF[ ] /* If */ + DUP[ ] /* DuplicateTopStack */ + MDRP[01000] /* MoveDirectRelPt */ + EIF[ ] /* EndIf */ + POP[ ] /* PopTopStack */ + ENDF[ ] /* EndFunctionDefinition */ + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + SRP0[ ] /* SetRefPoint0 */ + SPVTL[1] /* SetPVectorToLine */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + LT[ ] /* LessThan */ + PUSHB[ ] /* 1 value pushed */ + 13 + JROF[ ] /* JumpRelativeOnFalse */ + DUP[ ] /* DuplicateTopStack */ + PUSHW[ ] /* 1 value pushed */ + -1 + LT[ ] /* LessThan */ + IF[ ] /* If */ + SFVTCA[0] /* SetFVectorToAxis */ + ELSE[ ] /* Else */ + SFVTCA[1] /* SetFVectorToAxis */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 5 + JMPR[ ] /* Jump */ + PUSHB[ ] /* 1 value pushed */ + 3 + CINDEX[ ] /* CopyXToTopStack */ + SFVTL[0] /* SetFVectorToLine */ + PUSHB[ ] /* 1 value pushed */ + 4 + CINDEX[ ] /* CopyXToTopStack */ + SWAP[ ] /* SwapTopStack */ + MIRP[00001] /* MoveIndirectRelPt */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + LT[ ] /* LessThan */ + PUSHB[ ] /* 1 value pushed */ + 13 + JROF[ ] /* JumpRelativeOnFalse */ + DUP[ ] /* DuplicateTopStack */ + PUSHW[ ] /* 1 value pushed */ + -1 + LT[ ] /* LessThan */ + IF[ ] /* If */ + SFVTCA[0] /* SetFVectorToAxis */ + ELSE[ ] /* Else */ + SFVTCA[1] /* SetFVectorToAxis */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 5 + JMPR[ ] /* Jump */ + PUSHB[ ] /* 1 value pushed */ + 3 + CINDEX[ ] /* CopyXToTopStack */ + SFVTL[0] /* SetFVectorToLine */ + MIRP[00001] /* MoveIndirectRelPt */ + ENDF[ ] /* EndFunctionDefinition */ + FDEF[ ] /* FunctionDefinition */ + MPPEM[ ] /* MeasurePixelPerEm */ + LT[ ] /* LessThan */ + IF[ ] /* If */ + DUP[ ] /* DuplicateTopStack */ + PUSHW[ ] /* 1 value pushed */ + 279 + RCVT[ ] /* ReadCVT */ + WCVTP[ ] /* WriteCVTInPixels */ + EIF[ ] /* EndIf */ + POP[ ] /* PopTopStack */ + ENDF[ ] /* EndFunctionDefinition */ + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 2 + CINDEX[ ] /* CopyXToTopStack */ + RCVT[ ] /* ReadCVT */ + ADD[ ] /* Add */ + WCVTP[ ] /* WriteCVTInPixels */ + ENDF[ ] /* EndFunctionDefinition */ + FDEF[ ] /* FunctionDefinition */ + MPPEM[ ] /* MeasurePixelPerEm */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 2 + CINDEX[ ] /* CopyXToTopStack */ + PUSHB[ ] /* 1 value pushed */ + 2 + CINDEX[ ] /* CopyXToTopStack */ + RCVT[ ] /* ReadCVT */ + WCVTP[ ] /* WriteCVTInPixels */ + EIF[ ] /* EndIf */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + ENDF[ ] /* EndFunctionDefinition */ + FDEF[ ] /* FunctionDefinition */ + RCVT[ ] /* ReadCVT */ + WCVTP[ ] /* WriteCVTInPixels */ + ENDF[ ] /* EndFunctionDefinition */ + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 2 + CINDEX[ ] /* CopyXToTopStack */ + PUSHB[ ] /* 1 value pushed */ + 2 + CINDEX[ ] /* CopyXToTopStack */ + MD[0] /* MeasureDistance */ + PUSHB[ ] /* 1 value pushed */ + 5 + CINDEX[ ] /* CopyXToTopStack */ + PUSHB[ ] /* 1 value pushed */ + 5 + CINDEX[ ] /* CopyXToTopStack */ + MD[0] /* MeasureDistance */ + ADD[ ] /* Add */ + PUSHB[ ] /* 1 value pushed */ + 32 + MUL[ ] /* Multiply */ + ROUND[00] /* Round */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + SRP0[ ] /* SetRefPoint0 */ + ROLL[ ] /* RollTopThreeStack */ + SWAP[ ] /* SwapTopStack */ + MSIRP[0] /* MoveStackIndirRelPt */ + ROLL[ ] /* RollTopThreeStack */ + SRP0[ ] /* SetRefPoint0 */ + NEG[ ] /* Negate */ + MSIRP[0] /* MoveStackIndirRelPt */ + ENDF[ ] /* EndFunctionDefinition */ + + + + + + PUSHW[ ] /* 2 values pushed */ + 640 277 + PUSHB[ ] /* 3 values pushed */ + 148 93 5 + NPUSHW[ ] /* 28 values pushed */ + 277 150 3 277 128 4 276 254 3 275 254 3 274 18 3 273 254 3 272 254 3 271 154 3 270 + 254 3 269 + PUSHB[ ] /* 3 values pushed */ + 235 71 5 + NPUSHW[ ] /* 37 values pushed */ + 269 125 3 268 37 3 267 50 3 266 150 3 265 254 3 264 14 3 263 254 3 262 37 3 261 + 254 3 260 14 3 259 37 3 258 254 3 257 + NPUSHB[ ] /* 89 values pushed */ + 254 3 254 254 3 253 125 3 252 254 3 251 254 3 250 50 3 249 187 3 248 125 3 247 246 + 140 5 247 254 3 247 192 4 246 245 89 5 246 140 3 246 128 4 245 244 38 5 245 89 3 + 245 64 4 244 38 3 243 242 47 5 243 250 3 242 47 3 241 254 3 240 254 3 239 50 3 + 238 20 3 237 150 3 236 235 71 5 236 254 3 236 + PUSHW[ ] /* 1 value pushed */ + -47 + NPUSHB[ ] /* 255 values pushed */ + 4 235 71 3 234 233 100 5 234 150 3 233 100 3 232 254 3 231 230 27 5 231 254 3 230 + 27 3 229 254 3 228 107 3 227 254 3 226 187 3 225 224 25 5 225 250 3 224 25 3 223 + 150 3 222 254 3 221 254 3 220 219 21 5 220 254 3 219 21 3 218 150 3 217 216 21 5 + 217 254 3 216 141 11 5 216 21 3 215 125 3 214 58 3 213 141 11 5 213 58 3 212 254 + 3 211 210 10 5 211 254 3 210 10 3 209 254 3 208 254 3 207 138 17 5 207 28 3 206 + 22 3 205 254 3 204 150 3 203 139 37 5 203 254 3 202 254 3 201 125 3 200 254 3 199 + 254 3 198 254 3 197 154 13 5 196 254 3 195 254 3 194 254 3 193 254 3 192 141 11 5 + 192 20 3 191 12 3 190 189 187 5 190 254 3 189 188 93 5 189 187 3 189 128 4 188 187 + 37 5 188 93 3 188 64 4 187 37 3 186 254 3 185 150 3 184 143 65 5 183 254 3 182 + 143 65 5 182 250 3 181 154 13 5 180 254 3 179 100 3 178 100 3 177 14 3 176 18 3 + 175 254 3 174 254 + NPUSHB[ ] /* 253 values pushed */ + 3 173 254 3 172 254 3 171 18 3 170 254 3 169 168 14 5 169 50 3 168 14 3 167 166 + 17 5 167 40 3 166 17 3 165 164 45 5 165 125 3 164 45 3 163 254 3 162 254 3 161 + 254 3 160 159 25 5 160 100 3 159 158 16 5 159 25 3 158 16 3 157 10 3 156 254 3 + 155 154 13 5 155 254 3 154 13 3 153 152 46 5 153 254 3 152 46 3 151 143 65 5 151 + 150 3 150 149 187 5 150 254 3 149 148 93 5 149 187 3 149 128 4 148 144 37 5 148 93 + 3 148 64 4 147 254 3 146 254 3 145 144 37 5 145 187 3 144 37 3 143 139 37 5 143 + 65 3 142 141 11 5 142 20 3 141 11 3 140 139 37 5 140 100 3 139 138 17 5 139 37 + 3 138 17 3 137 254 3 136 254 3 135 254 3 134 133 17 5 134 254 3 133 17 3 132 254 + 3 131 254 3 130 17 66 5 130 83 3 129 254 3 128 120 3 127 126 125 5 127 254 3 126 + 125 3 125 30 3 124 254 3 123 14 3 122 254 3 119 254 3 118 254 3 117 116 12 5 117 + 15 3 117 + PUSHW[ ] /* 1 value pushed */ + 256 + NPUSHB[ ] /* 218 values pushed */ + 4 116 12 3 116 192 4 115 18 3 115 64 4 114 254 3 113 254 3 112 254 3 111 110 83 + 5 111 150 3 110 109 40 5 110 83 3 109 40 3 108 254 3 107 50 3 106 254 3 105 50 + 3 104 250 3 103 187 3 102 254 3 101 254 3 100 254 3 99 98 30 5 99 254 3 98 0 + 16 5 98 30 3 97 254 3 96 254 3 95 254 3 94 90 11 5 94 14 3 93 100 3 92 + 200 3 91 90 11 5 91 20 3 90 11 3 89 254 3 88 20 3 87 254 3 86 254 3 85 + 27 25 5 85 50 3 84 254 3 83 254 3 82 254 3 81 125 3 80 254 3 79 20 3 78 + 254 3 77 1 45 5 77 254 3 76 187 3 75 40 3 74 73 24 5 74 55 3 73 67 18 + 5 73 24 3 72 69 24 5 72 254 3 71 67 18 5 71 100 3 70 69 24 5 70 187 3 + 69 24 3 68 67 18 5 68 55 3 67 66 17 5 67 18 3 67 + PUSHW[ ] /* 1 value pushed */ + 576 + NPUSHB[ ] /* 9 values pushed */ + 4 66 65 15 5 66 17 3 66 + PUSHW[ ] /* 1 value pushed */ + 512 + NPUSHB[ ] /* 9 values pushed */ + 4 65 64 14 5 65 15 3 65 + PUSHW[ ] /* 1 value pushed */ + 448 + NPUSHB[ ] /* 9 values pushed */ + 4 64 63 12 5 64 14 3 64 + PUSHW[ ] /* 1 value pushed */ + 384 + NPUSHB[ ] /* 9 values pushed */ + 4 63 12 9 5 63 12 3 63 + PUSHW[ ] /* 1 value pushed */ + 320 + NPUSHB[ ] /* 100 values pushed */ + 4 62 254 3 61 1 45 5 61 250 3 60 254 3 59 40 3 58 254 3 57 17 66 5 57 + 100 3 56 49 26 5 56 75 3 55 254 3 54 45 20 5 54 254 3 53 75 3 52 48 26 + 5 52 75 3 51 48 26 5 51 254 3 50 17 66 5 50 254 3 49 45 20 5 49 26 3 + 48 26 3 47 45 20 5 47 24 3 46 9 22 5 46 187 3 45 44 19 5 45 20 3 45 + PUSHW[ ] /* 1 value pushed */ + 640 + NPUSHB[ ] /* 9 values pushed */ + 4 44 16 17 5 44 19 3 44 + PUSHW[ ] /* 1 value pushed */ + 576 + NPUSHB[ ] /* 150 values pushed */ + 4 43 42 37 5 43 254 3 42 9 22 5 42 37 3 41 2 58 5 41 254 3 40 254 3 + 39 254 3 38 15 3 37 22 66 5 37 69 3 36 15 3 35 254 3 34 15 15 5 34 254 + 3 33 32 45 5 33 125 3 32 45 3 31 75 3 30 17 66 5 30 254 3 29 254 3 28 + 27 25 5 28 254 3 27 0 16 5 27 25 3 26 254 3 25 254 3 24 254 3 23 22 66 + 5 23 70 3 22 21 45 5 22 66 3 21 20 16 5 21 45 3 20 16 3 19 0 16 5 + 19 20 3 18 17 66 5 18 254 3 17 1 45 5 17 66 3 16 15 15 5 16 17 3 16 + PUSHW[ ] /* 1 value pushed */ + 512 + NPUSHB[ ] /* 9 values pushed */ + 4 15 14 12 5 15 15 3 15 + PUSHW[ ] /* 1 value pushed */ + 448 + NPUSHB[ ] /* 9 values pushed */ + 4 14 13 10 5 14 12 3 14 + PUSHW[ ] /* 1 value pushed */ + 384 + NPUSHB[ ] /* 9 values pushed */ + 4 13 12 9 5 13 10 3 13 + PUSHW[ ] /* 1 value pushed */ + 320 + PUSHB[ ] /* 5 values pushed */ + 4 12 9 3 12 + PUSHW[ ] /* 1 value pushed */ + 256 + NPUSHB[ ] /* 55 values pushed */ + 4 11 254 3 10 9 22 5 10 254 3 9 22 3 8 16 3 7 254 3 6 1 45 5 6 + 254 3 5 20 3 3 2 58 5 3 250 3 2 1 45 5 2 58 3 1 0 16 5 1 45 + 3 0 16 3 1 + PUSHW[ ] /* 1 value pushed */ + 356 + SCANCTRL[ ] /* ScanConversionControl */ + SCANTYPE[ ] /* ScanType */ + SVTCA[1] /* SetFPVectorToAxis */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + SVTCA[0] /* SetFPVectorToAxis */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + CALL[ ] /* CallFunction */ + SCVTCI[ ] /* SetCVTCutIn */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PUSHW[ ] /* 5 values pushed */ + 4 276 0 6 276 + PUSHB[ ] /* 7 values pushed */ + 1 8 5 131 2 4 0 + MDAP[1] /* MoveDirectAbsPt */ + MDRP[00100] /* MoveDirectRelPt */ + MDRP[10100] /* MoveDirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MDRP[10100] /* MoveDirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + IUP[0] /* InterpolateUntPts */ + + + + + + + + + + + + + + PUSHB[ ] /* 6 values pushed */ + 0 2 4 1 3 4 + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + IUP[0] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 12 + EQ[ ] /* Equal */ + IF[ ] /* If */ + PUSHW[ ] /* 6 values pushed */ + 4 -64 1 4 4 64 + SHPIX[ ] /* ShiftZoneByPixel */ + SRP1[ ] /* SetRefPoint1 */ + SHZ[1] /* ShiftZoneByLastPoint */ + SHPIX[ ] /* ShiftZoneByPixel */ + EIF[ ] /* EndIf */ + SVTCA[0] /* SetFPVectorToAxis */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 14 + EQ[ ] /* Equal */ + IF[ ] /* If */ + PUSHW[ ] /* 6 values pushed */ + 4 64 1 4 4 -64 + SHPIX[ ] /* ShiftZoneByPixel */ + SRP1[ ] /* SetRefPoint1 */ + SHZ[1] /* ShiftZoneByLastPoint */ + SHPIX[ ] /* ShiftZoneByPixel */ + EIF[ ] /* EndIf */ + NPUSHB[ ] /* 38 values pushed */ + 15 0 15 1 10 2 10 3 31 0 31 1 31 2 31 3 47 0 47 1 47 2 47 3 12 + 15 0 15 1 31 0 31 1 47 0 47 1 6 + DELTAP1[ ] /* DeltaExceptionP1 */ + SVTCA[1] /* SetFPVectorToAxis */ + DELTAP1[ ] /* DeltaExceptionP1 */ + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 9 values pushed */ + 4 0 5 2 7 4 2 6 7 + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + IP[ ] /* InterpolatePts */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MDRP[00100] /* MoveDirectRelPt */ + IP[ ] /* InterpolatePts */ + IUP[0] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 12 + EQ[ ] /* Equal */ + IF[ ] /* If */ + PUSHW[ ] /* 6 values pushed */ + 7 -64 1 7 7 64 + SHPIX[ ] /* ShiftZoneByPixel */ + SRP1[ ] /* SetRefPoint1 */ + SHZ[1] /* ShiftZoneByLastPoint */ + SHPIX[ ] /* ShiftZoneByPixel */ + EIF[ ] /* EndIf */ + SVTCA[0] /* SetFPVectorToAxis */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 14 + EQ[ ] /* Equal */ + IF[ ] /* If */ + PUSHW[ ] /* 6 values pushed */ + 7 64 1 7 7 -64 + SHPIX[ ] /* ShiftZoneByPixel */ + SRP1[ ] /* SetRefPoint1 */ + SHZ[1] /* ShiftZoneByLastPoint */ + SHPIX[ ] /* ShiftZoneByPixel */ + EIF[ ] /* EndIf */ + NPUSHB[ ] /* 19 values pushed */ + 15 0 15 1 12 4 31 0 31 1 28 4 47 0 47 1 44 4 9 + DELTAP1[ ] /* DeltaExceptionP1 */ + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 13 values pushed */ + 4 0 222 6 2 8 5 97 4 1 97 0 8 + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + MIRP[11100] /* MoveIndirectRelPt */ + MDRP[10100] /* MoveDirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + IUP[0] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 14 + EQ[ ] /* Equal */ + IF[ ] /* If */ + PUSHW[ ] /* 6 values pushed */ + 8 64 1 8 8 -64 + SHPIX[ ] /* ShiftZoneByPixel */ + SRP1[ ] /* SetRefPoint1 */ + SHZ[1] /* ShiftZoneByLastPoint */ + SHPIX[ ] /* ShiftZoneByPixel */ + EIF[ ] /* EndIf */ + SVTCA[1] /* SetFPVectorToAxis */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 14 + EQ[ ] /* Equal */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 13 + EQ[ ] /* Equal */ + OR[ ] /* LogicalOr */ + IF[ ] /* If */ + PUSHW[ ] /* 6 values pushed */ + 8 -64 1 8 8 64 + SHPIX[ ] /* ShiftZoneByPixel */ + SRP1[ ] /* SetRefPoint1 */ + SHZ[1] /* ShiftZoneByLastPoint */ + SHPIX[ ] /* ShiftZoneByPixel */ + EIF[ ] /* EndIf */ + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 22 values pushed */ + 6 151 4 2 151 0 136 8 151 4 177 10 1 5 9 49 7 3 30 0 51 12 + SRP0[ ] /* SetRefPoint0 */ + MIRP[11100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[11100] /* MoveIndirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + MDAP[1] /* MoveDirectAbsPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MIRP[10100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SRP0[ ] /* SetRefPoint0 */ + MIRP[01110] /* MoveIndirectRelPt */ + IUP[0] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 33 values pushed */ + 23 8 182 10 19 151 0 136 13 151 21 10 6 19 13 6 0 4 22 20 16 50 3 49 9 + 48 24 20 30 11 7 48 26 + SRP0[ ] /* SetRefPoint0 */ + MIRP[11100] /* MoveIndirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[01100] /* MoveIndirectRelPt */ + MIRP[11100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[00100] /* MoveDirectRelPt */ + SLOOP[ ] /* SetLoopVariable */ + IP[ ] /* InterpolatePts */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + MDAP[1] /* MoveDirectAbsPt */ + MDRP[00110] /* MoveDirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[01110] /* MoveIndirectRelPt */ + MIRP[10110] /* MoveIndirectRelPt */ + MIRP[01110] /* MoveIndirectRelPt */ + SRP0[ ] /* SetRefPoint0 */ + MIRP[01110] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + IUP[0] /* InterpolateUntPts */ + + + + + + + + + + + + + + PUSHB[ ] /* 6 values pushed */ + 0 1 4 1 3 4 + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + IUP[0] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 12 + EQ[ ] /* Equal */ + IF[ ] /* If */ + PUSHW[ ] /* 6 values pushed */ + 4 -64 1 4 4 64 + SHPIX[ ] /* ShiftZoneByPixel */ + SRP1[ ] /* SetRefPoint1 */ + SHZ[1] /* ShiftZoneByLastPoint */ + SHPIX[ ] /* ShiftZoneByPixel */ + EIF[ ] /* EndIf */ + SVTCA[0] /* SetFPVectorToAxis */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 14 + EQ[ ] /* Equal */ + IF[ ] /* If */ + PUSHW[ ] /* 6 values pushed */ + 4 64 1 4 4 -64 + SHPIX[ ] /* ShiftZoneByPixel */ + SRP1[ ] /* SetRefPoint1 */ + SHZ[1] /* ShiftZoneByLastPoint */ + SHPIX[ ] /* ShiftZoneByPixel */ + EIF[ ] /* EndIf */ + SVTCA[1] /* SetFPVectorToAxis */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 14 + EQ[ ] /* Equal */ + IF[ ] /* If */ + PUSHW[ ] /* 6 values pushed */ + 4 64 1 4 4 -64 + SHPIX[ ] /* ShiftZoneByPixel */ + SRP1[ ] /* SetRefPoint1 */ + SHZ[1] /* ShiftZoneByLastPoint */ + SHPIX[ ] /* ShiftZoneByPixel */ + EIF[ ] /* EndIf */ + NPUSHB[ ] /* 13 values pushed */ + 15 0 15 3 31 0 31 3 47 0 47 3 6 + SVTCA[0] /* SetFPVectorToAxis */ + DELTAP1[ ] /* DeltaExceptionP1 */ + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 19 values pushed */ + 10 2 151 0 136 8 4 151 6 5 1 55 3 30 7 0 55 9 12 + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[11100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + MDAP[1] /* MoveDirectAbsPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[10100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + IUP[0] /* InterpolateUntPts */ + + + + + + + + + + + + + + + PUSHB[ ] /* 5 values pushed */ + 16 32 1 0 0 + SRP0[ ] /* SetRefPoint0 */ + MD[0] /* MeasureDistance */ + MUL[ ] /* Multiply */ + MSIRP[0] /* MoveStackIndirRelPt */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + PUSHB[ ] /* 5 values pushed */ + 1 18 15 0 7 + CALL[ ] /* CallFunction */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 28 values pushed */ + 7 1 2 1 2 6 7 6 66 7 2 3 0 180 8 5 6 1 7 2 17 4 49 7 17 + 0 48 10 + SRP0[ ] /* SetRefPoint0 */ + MIRP[11100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MIRP[11100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SRP1[ ] /* SetRefPoint1 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + MDAP[1] /* MoveDirectAbsPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + IUP[0] /* InterpolateUntPts */ + MPPEM[ ] /* MeasurePixelPerEm */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + SPVTL[1] /* SetPVectorToLine */ + SRP0[ ] /* SetRefPoint0 */ + SFVTCA[0] /* SetFVectorToAxis */ + MDRP[01001] /* MoveDirectRelPt */ + SPVTL[1] /* SetPVectorToLine */ + SRP0[ ] /* SetRefPoint0 */ + SFVTCA[0] /* SetFVectorToAxis */ + MDRP[01001] /* MoveDirectRelPt */ + EIF[ ] /* EndIf */ + CLEAR[ ] /* ClearStack */ + PUSHB[ ] /* 3 values pushed */ + 23 1 1 + SVTCA[1] /* SetFPVectorToAxis */ + DELTAP1[ ] /* DeltaExceptionP1 */ + NPUSHB[ ] /* 38 values pushed */ + 23 2 24 7 41 2 38 7 56 7 87 2 100 2 106 7 117 2 122 7 10 24 6 38 1 + 41 6 70 1 73 6 87 1 103 1 104 6 8 + DELTAP1[ ] /* DeltaExceptionP1 */ + SVTCA[0] /* SetFPVectorToAxis */ + DELTAP1[ ] /* DeltaExceptionP1 */ + + + + + + + + + + PUSHB[ ] /* 5 values pushed */ + 4 34 19 0 7 + CALL[ ] /* CallFunction */ + NPUSHB[ ] /* 5 values pushed */ + 79 34 64 19 2 + DELTAP1[ ] /* DeltaExceptionP1 */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 19 values pushed */ + 9 151 15 3 151 21 150 15 153 24 0 50 12 54 6 50 18 53 24 + SRP0[ ] /* SetRefPoint0 */ + MIRP[11100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MIRP[11100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + SRP0[ ] /* SetRefPoint0 */ + MIRP[00100] /* MoveIndirectRelPt */ + MIRP[10100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SRP0[ ] /* SetRefPoint0 */ + MIRP[01110] /* MoveIndirectRelPt */ + IUP[0] /* InterpolateUntPts */ + + + + + + + + + + NPUSHB[ ] /* 3 values pushed */ + 79 24 1 + DELTAP1[ ] /* DeltaExceptionP1 */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + PUSHB[ ] /* 5 values pushed */ + 12 26 30 18 7 + CALL[ ] /* CallFunction */ + NPUSHB[ ] /* 9 values pushed */ + 32 30 47 26 0 30 15 26 4 + DELTAP1[ ] /* DeltaExceptionP1 */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + PUSHB[ ] /* 5 values pushed */ + 12 30 27 18 7 + CALL[ ] /* CallFunction */ + NPUSHB[ ] /* 17 values pushed */ + 112 27 127 30 48 27 63 30 32 27 47 30 0 27 15 30 8 + DELTAP1[ ] /* DeltaExceptionP1 */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + NPUSHB[ ] /* 3 values pushed */ + 79 24 1 + DELTAP1[ ] /* DeltaExceptionP1 */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 58 values pushed */ + 43 41 38 11 10 9 0 4 14 29 31 32 20 14 3 42 38 30 3 151 26 14 151 38 150 + 26 153 31 31 30 44 32 35 17 42 20 23 11 10 9 0 4 6 29 35 17 41 6 43 6 + 50 23 54 17 50 35 53 44 + SRP0[ ] /* SetRefPoint0 */ + MIRP[11100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MIRP[11100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MDRP[00000] /* MoveDirectRelPt */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + SRP2[ ] /* SetRefPoint2 */ + SLOOP[ ] /* SetLoopVariable */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + MDAP[1] /* MoveDirectAbsPt */ + MIRP[00100] /* MoveIndirectRelPt */ + MIRP[10100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SRP0[ ] /* SetRefPoint0 */ + MIRP[01110] /* MoveIndirectRelPt */ + MDRP[00000] /* MoveDirectRelPt */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[00000] /* MoveDirectRelPt */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + SRP2[ ] /* SetRefPoint2 */ + SLOOP[ ] /* SetLoopVariable */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + IUP[0] /* InterpolateUntPts */ + + + + + + + + + + PUSHB[ ] /* 5 values pushed */ + 12 48 33 18 7 + CALL[ ] /* CallFunction */ + NPUSHB[ ] /* 5 values pushed */ + 79 48 64 33 2 + DELTAP1[ ] /* DeltaExceptionP1 */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 30 values pushed */ + 22 16 15 3 19 12 7 1 0 3 8 4 204 23 12 19 204 27 8 30 16 1 15 0 7 + 22 24 7 9 30 + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + MDRP[10100] /* MoveDirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + SRP1[ ] /* SetRefPoint1 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MIRP[11100] /* MoveIndirectRelPt */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MIRP[01100] /* MoveIndirectRelPt */ + SRP1[ ] /* SetRefPoint1 */ + SLOOP[ ] /* SetLoopVariable */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + SLOOP[ ] /* SetLoopVariable */ + IP[ ] /* InterpolatePts */ + IUP[0] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 14 + EQ[ ] /* Equal */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 17 + EQ[ ] /* Equal */ + OR[ ] /* LogicalOr */ + IF[ ] /* If */ + PUSHW[ ] /* 6 values pushed */ + 30 64 1 30 30 -64 + SHPIX[ ] /* ShiftZoneByPixel */ + SRP1[ ] /* SetRefPoint1 */ + SHZ[1] /* ShiftZoneByLastPoint */ + SHPIX[ ] /* ShiftZoneByPixel */ + EIF[ ] /* EndIf */ + NPUSHB[ ] /* 116 values pushed */ + 9 0 9 1 15 11 15 12 15 13 13 14 15 15 15 16 15 17 15 18 15 19 15 20 15 + 21 15 22 15 23 15 24 11 25 26 0 26 1 29 11 29 12 29 13 30 14 31 15 31 16 + 31 17 31 18 31 19 31 20 31 21 31 22 31 23 31 24 33 15 1 15 2 15 3 15 4 + 15 5 15 11 15 12 15 13 15 21 15 22 15 23 15 24 31 1 31 2 31 3 31 4 31 + 5 31 11 31 12 31 13 31 21 31 22 31 23 31 24 24 + DELTAP1[ ] /* DeltaExceptionP1 */ + SVTCA[1] /* SetFPVectorToAxis */ + DELTAP1[ ] /* DeltaExceptionP1 */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 23 values pushed */ + 15 3 18 0 4 1 9 151 24 153 16 1 136 30 15 30 17 49 2 30 0 48 30 + SRP0[ ] /* SetRefPoint0 */ + MIRP[11100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MIRP[11100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + SRP0[ ] /* SetRefPoint0 */ + MIRP[00100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[10100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SRP1[ ] /* SetRefPoint1 */ + SLOOP[ ] /* SetLoopVariable */ + IP[ ] /* InterpolatePts */ + IUP[0] /* InterpolateUntPts */ + + + + + + + + + + NPUSHB[ ] /* 3 values pushed */ + 79 31 1 + DELTAP1[ ] /* DeltaExceptionP1 */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + PUSHW[ ] /* 5 values pushed */ + 5 263 4 0 263 + PUSHB[ ] /* 8 values pushed */ + 1 8 4 0 4 6 2 8 + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MDRP[10100] /* MoveDirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + IUP[0] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 42 values pushed */ + 27 8 5 4 28 0 139 1 141 28 9 139 8 141 5 203 12 28 203 22 24 21 190 15 12 + 153 13 34 27 21 14 7 23 12 4 8 0 31 30 18 69 34 + SRP0[ ] /* SetRefPoint0 */ + MIRP[10100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + ALIGNRP[ ] /* AlignRelativePt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + SHP[0] /* ShiftPointByLastPoint */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[00100] /* MoveDirectRelPt */ + MIRP[00100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[10100] /* MoveIndirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MDRP[00100] /* MoveDirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SRP0[ ] /* SetRefPoint0 */ + MIRP[11110] /* MoveIndirectRelPt */ + MIRP[10100] /* MoveIndirectRelPt */ + MIRP[01110] /* MoveIndirectRelPt */ + SRP0[ ] /* SetRefPoint0 */ + MIRP[10101] /* MoveIndirectRelPt */ + MIRP[01110] /* MoveIndirectRelPt */ + SRP1[ ] /* SetRefPoint1 */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IUP[0] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 26 values pushed */ + 32 22 27 23 14 40 45 13 24 30 27 9 42 12 45 39 33 21 15 4 3 9 255 31 23 + 27 + PUSHW[ ] /* 1 value pushed */ + 256 + NPUSHB[ ] /* 39 values pushed */ + 3 255 41 13 45 48 31 41 36 32 13 23 18 14 33 39 36 6 15 21 18 42 30 24 12 + 4 6 0 120 22 14 18 121 6 120 40 32 36 48 + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[01100] /* MoveIndirectRelPt */ + MIRP[11100] /* MoveIndirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[01100] /* MoveIndirectRelPt */ + SRP1[ ] /* SetRefPoint1 */ + SLOOP[ ] /* SetLoopVariable */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[01100] /* MoveIndirectRelPt */ + MIRP[11100] /* MoveIndirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[01100] /* MoveIndirectRelPt */ + SRP1[ ] /* SetRefPoint1 */ + SLOOP[ ] /* SetLoopVariable */ + IP[ ] /* InterpolatePts */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + IUP[0] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 28 values pushed */ + 10 8 7 6 4 2 1 0 8 11 9 5 3 12 11 10 9 8 7 5 4 3 1 9 6 + 2 0 12 + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MDRP[01100] /* MoveDirectRelPt */ + SLOOP[ ] /* SetLoopVariable */ + IP[ ] /* InterpolatePts */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MDRP[01100] /* MoveDirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + SLOOP[ ] /* SetLoopVariable */ + IP[ ] /* InterpolatePts */ + IUP[0] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 60 values pushed */ + 35 36 30 57 51 11 17 36 42 4 1 27 143 26 206 30 207 23 1 143 0 206 4 207 48 + 150 23 63 35 7 17 60 33 42 54 11 36 57 51 4 7 33 85 20 7 85 45 20 60 85 + 0 38 14 45 26 38 54 85 39 63 + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MIRP[00100] /* MoveIndirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + MDRP[10100] /* MoveDirectRelPt */ + MIRP[00100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + SRP0[ ] /* SetRefPoint0 */ + MIRP[01110] /* MoveIndirectRelPt */ + SRP0[ ] /* SetRefPoint0 */ + MIRP[01110] /* MoveIndirectRelPt */ + SRP1[ ] /* SetRefPoint1 */ + SLOOP[ ] /* SetLoopVariable */ + IP[ ] /* InterpolatePts */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[00100] /* MoveDirectRelPt */ + MIRP[10100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MIRP[10100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SRP0[ ] /* SetRefPoint0 */ + MIRP[01110] /* MoveIndirectRelPt */ + MIRP[10110] /* MoveIndirectRelPt */ + MIRP[01110] /* MoveIndirectRelPt */ + SRP1[ ] /* SetRefPoint1 */ + SLOOP[ ] /* SetLoopVariable */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IUP[0] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 32 values pushed */ + 7 22 1 18 10 182 20 8 12 1 139 0 167 4 151 25 150 16 12 151 14 0 13 9 11 + 7 30 15 19 21 17 28 + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MDRP[00100] /* MoveDirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + MIRP[11100] /* MoveIndirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MDRP[00100] /* MoveDirectRelPt */ + MDRP[10100] /* MoveDirectRelPt */ + MDRP[00100] /* MoveDirectRelPt */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + MDAP[1] /* MoveDirectAbsPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[10100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + MIRP[10100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MIRP[01110] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + IUP[0] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NPUSHB[ ] /* 68 values pushed */ + 3 37 4 9 4 2 37 1 2 9 9 4 2 37 3 2 20 0 20 1 37 0 0 20 66 + 2 7 5 16 12 231 18 10 23 5 231 21 7 3 0 136 14 24 1 17 2 15 8 5 3 + 3 6 4 60 11 6 101 13 9 30 22 17 101 0 60 19 15 25 + SRP0[ ] /* SetRefPoint0 */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MIRP[01100] /* MoveIndirectRelPt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[11100] /* MoveIndirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MIRP[01100] /* MoveIndirectRelPt */ + SRP2[ ] /* SetRefPoint2 */ + SLOOP[ ] /* SetLoopVariable */ + IP[ ] /* InterpolatePts */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + SRP1[ ] /* SetRefPoint1 */ + IP[ ] /* InterpolatePts */ + IP[ ] /* InterpolatePts */ + IUP[1] /* InterpolateUntPts */ + SVTCA[0] /* SetFPVectorToAxis */ + MDAP[1] /* MoveDirectAbsPt */ + MIRP[00100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + MDRP[10100] /* MoveDirectRelPt */ + ALIGNRP[ ] /* AlignRelativePt */ + MIRP[01100] /* MoveIndirectRelPt */ + SHP[0] /* ShiftPointByLastPoint */ + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IUP[0] /* InterpolateUntPts */ + MPPEM[ ] /* MeasurePixelPerEm */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + SPVTL[1] /* SetPVectorToLine */ + SRP0[ ] /* SetRefPoint0 */ + SFVTCA[1] /* SetFVectorToAxis */ + MIRP[01101] /* MoveIndirectRelPt */ + SPVTL[1] /* SetPVectorToLine */ + SRP0[ ] /* SetRefPoint0 */ + SFVTL[0] /* SetFVectorToLine */ + MIRP[01101] /* MoveIndirectRelPt */ + SPVTL[1] /* SetPVectorToLine */ + SRP0[ ] /* SetRefPoint0 */ + SFVTL[0] /* SetFVectorToLine */ + MIRP[01101] /* MoveIndirectRelPt */ + SPVTL[1] /* SetPVectorToLine */ + SRP0[ ] /* SetRefPoint0 */ + SFVTCA[1] /* SetFVectorToAxis */ + MIRP[01101] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + CLEAR[ ] /* ClearStack */ + + + + + + + + + Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. +DejaVu changes are in public domain + + + + DejaVu Sans Mono + + + Book + + + DejaVu Sans Mono + + + DejaVu Sans Mono + + + Version 2.37 + + + DejaVuSansMono + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/ttx/LatinModernRoman-Regular_1.ttx b/tests/ttx/LatinModernRoman-Regular_1.ttx new file mode 100644 index 00000000..c417e2c1 --- /dev/null +++ b/tests/ttx/LatinModernRoman-Regular_1.ttx @@ -0,0 +1,290 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright 2003, 2009 B. Jackowski and J. M. Nowacki (on behalf of TeX users groups). This work is released under the GUST Font License -- see http://tug.org/fonts/licenses/GUST-FONT-LICENSE.txt for details. + + + LM Roman 10 + + + Regular + + + 2.004;UKWN;LMRoman10-Regular + + + LMRoman10-Regular + + + Version 2.004;PS 2.004;hotconv 1.0.49;makeotf.lib2.0.14853 + + + LMRoman10-Regular + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 280 endchar + + + 785 -22 31 233 31 401 31 41 38 134 -20 hstemhm + 56 103 418 89 hintmask 11100110 + 735 242 rmoveto + 31 vlineto + -122 -3 rlineto + -40 -85 0 3 -36 hvcurveto + -31 32 vlineto + 90 3 -11 -37 hvcurveto + -64 vlineto + -112 -127 -9 -28 -65 -198 35 298 299 197 33 60 107 91 -90 -147 20 vhcurveto + -14 2 0 -3 14 hhcurveto + 16 0 3 21 hvcurveto + 237 vlineto + 17 0 7 -11 -4 -4 0 -12 -8 vhcurveto + -50 -74 rlineto + 32 -32 -54 54 -99 hhcurveto + -186 -162 -158 -205 -205 160 -159 190 73 80 26 59 34 hvcurveto + -22 13 40 -40 11 hhcurveto + 9 0 8 15 hvcurveto + 174 vlineto + 39 4 5 65 vhcurveto + hintmask 00011000 + -193 676 rmoveto + -22 hlineto + -87 -2 -66 -47 -59 hhcurveto + -63 -63 49 85 -2 hvcurveto + -22 hlineto + -108 2 73 -64 74 hhcurveto + 78 70 67 105 2 hvcurveto + endchar + + + 785 -22 31 233 31 401 31 52 -21 133 -20 hstemhm + 56 103 418 89 hintmask 11100110 + 735 242 rmoveto + 31 vlineto + -122 -3 rlineto + -40 -85 0 3 -36 hvcurveto + -31 32 vlineto + 90 3 -11 -37 hvcurveto + -64 vlineto + -112 -127 -9 -28 -65 -198 35 298 299 197 33 60 107 91 -90 -147 20 vhcurveto + -14 2 0 -3 14 hhcurveto + 16 0 3 21 hvcurveto + 237 vlineto + 17 0 7 -11 -4 -4 0 -12 -8 vhcurveto + -50 -74 rlineto + 32 -32 -54 54 -99 hhcurveto + -186 -162 -158 -205 -205 160 -159 190 73 80 26 59 34 hvcurveto + -22 13 40 -40 11 hhcurveto + 9 0 8 15 hvcurveto + 174 vlineto + 39 4 5 65 vhcurveto + hintmask 00011000 + -192 611 rmoveto + -10 16 -140 -66 -140 66 -12 -16 151 -117 rlineto + endchar + + + 500 -206 23 190 58 45 23 16 23 247 23 -12 23 61 79 145 -20 hstemhm + 28 52 -20 75 -59 75 -44 52 0 75 75 75 35 52 -38 52 hintmask 0000011100000000 + 259 554 rmoveto + 19 -13 20 -27 -11 -11 -4 -8 -8 vhcurveto + -1 60 21 45 33 36 rrcurveto + 4 4 1 1 3 vvcurveto + 5 -4 3 -4 -9 -59 -59 -86 -48 18 -31 30 26 14 20 20 vhcurveto + 226 -150 rmoveto + 17 -12 32 -39 -20 -44 -6 -41 -42 vhcurveto + hintmask 0000100001000000 + 33 -42 -42 3 -22 hhcurveto + -93 -69 -69 -77 hvcurveto + hintmask 0000000000010000 + -44 22 -38 25 -21 vhcurveto + hintmask 0010000000100000 + -13 -15 -18 -33 -35 vvcurveto + -31 13 -38 31 -20 vhcurveto + hintmask 1111000010001110 + -60 -17 -32 -43 -40 vvcurveto + -72 99 -55 122 118 104 51 78 35 -14 51 -51 28 vhcurveto + 28 -53 -58 0 -61 hhcurveto + -25 -43 0 1 -7 hvcurveto + -32 4 -21 31 32 vvcurveto + 4 0 23 17 20 vhcurveto + -28 39 41 -3 19 hhcurveto + 93 69 69 77 37 -16 37 -25 23 hvcurveto + hintmask 0001010001000001 + 34 36 36 5 18 hhcurveto + 0 7 0 3 -1 vhcurveto + -11 -4 -5 -11 -12 vvcurveto + -17 13 -12 16 10 19 7 23 vhcurveto + -176 -108 rmoveto + -27 -1 -32 -15 -25 vhcurveto + -12 -8 -23 -28 -40 hhcurveto + -87 0 100 23 hvcurveto + hintmask 1000100000100100 + 27 1 32 15 25 vhcurveto + 12 8 23 28 40 hhcurveto + 87 0 -100 -23 hvcurveto + 110 -375 rmoveto + -54 -71 -50 -98 vhcurveto + hintmask 0100000010000010 + -101 -69 51 53 46 38 37 44 3 hvcurveto + 59 hlineto + 86 112 0 -86 hvcurveto + endchar + + + 778 54 40 312 40 0 40 hstemhm + 153 40 0 40 311 40 0 40 hintmask 10110010 + 624 15 rmoveto + 14 14 -13 13 -10 10 -74 75 rcurveline + 27 33 16 43 47 vvcurveto + 47 -16 43 -28 34 vhcurveto + 74 74 10 10 14 13 -14 14 rlinecurve + -15 14 -13 -13 -10 -10 -74 -74 rcurveline + 27 -34 -42 17 -47 hhcurveto + -47 -43 -17 -27 -33 hvcurveto + -75 75 -10 10 -13 13 -15 -14 rlinecurve + -14 -14 14 -14 10 -10 74 -74 rcurveline + -27 -34 -17 -43 -47 vvcurveto + -47 17 -43 27 -33 vhcurveto + -74 -75 -10 -10 -14 -13 14 -14 rlinecurve + 15 -14 13 13 10 10 74 74 rcurveline + -27 34 43 -17 47 hhcurveto + 47 43 17 27 33 hvcurveto + 75 -74 10 -10 13 -13 14 14 rlinecurve + -80 235 rmoveto + -86 -69 -70 -86 vhcurveto + hintmask 01001100 + -86 -70 70 86 86 70 70 86 86 69 -70 -86 hvcurveto + endchar + + + + + + + + + + + + + + + + + + diff --git a/tests/ttx/MPLUS1p-Regular_1.ttx b/tests/ttx/MPLUS1p-Regular_1.ttx new file mode 100644 index 00000000..8ed4cb6f --- /dev/null +++ b/tests/ttx/MPLUS1p-Regular_1.ttx @@ -0,0 +1,974 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PUSHB[ ] /* 1 value pushed */ + 0 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 0 + SZP0[ ] /* SetZonePointer0 */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 42 + LT[ ] /* LessThan */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 74 + SROUND[ ] /* SuperRound */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 0 + SWAP[ ] /* SwapTopStack */ + MIAP[1] /* MoveIndirectAbsPt */ + RTG[ ] /* RoundToGrid */ + PUSHB[ ] /* 1 value pushed */ + 6 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + RTDG[ ] /* RoundToDoubleGrid */ + EIF[ ] /* EndIf */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 42 + LT[ ] /* LessThan */ + IF[ ] /* If */ + RDTG[ ] /* RoundDownToGrid */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + MDRP[10100] /* MoveDirectRelPt */ + PUSHB[ ] /* 1 value pushed */ + 1 + SZP0[ ] /* SetZonePointer0 */ + MDAP[0] /* MoveDirectAbsPt */ + RTG[ ] /* RoundToGrid */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 1 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + MDRP[11010] /* MoveDirectRelPt */ + PUSHB[ ] /* 1 value pushed */ + 12 + CALL[ ] /* CallFunction */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 2 + FDEF[ ] /* FunctionDefinition */ + MPPEM[ ] /* MeasurePixelPerEm */ + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + RCVT[ ] /* ReadCVT */ + SWAP[ ] /* SwapTopStack */ + EIF[ ] /* EndIf */ + POP[ ] /* PopTopStack */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 3 + FDEF[ ] /* FunctionDefinition */ + ROUND[01] /* Round */ + RTG[ ] /* RoundToGrid */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 64 + LT[ ] /* LessThan */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + PUSHB[ ] /* 1 value pushed */ + 64 + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 4 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 6 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + ROFF[ ] /* RoundOff */ + IF[ ] /* If */ + MDRP[11101] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MDRP[01101] /* MoveDirectRelPt */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + MPPEM[ ] /* MeasurePixelPerEm */ + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + IF[ ] /* If */ + MIRP[11101] /* MoveIndirectRelPt */ + ELSE[ ] /* Else */ + MIRP[01101] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + PUSHB[ ] /* 1 value pushed */ + 5 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 70 + SROUND[ ] /* SuperRound */ + EIF[ ] /* EndIf */ + IF[ ] /* If */ + MDRP[11101] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MDRP[01101] /* MoveDirectRelPt */ + EIF[ ] /* EndIf */ + EIF[ ] /* EndIf */ + EIF[ ] /* EndIf */ + RTG[ ] /* RoundToGrid */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 5 + FDEF[ ] /* FunctionDefinition */ + GFV[ ] /* GetFVector */ + NOT[ ] /* LogicalNot */ + AND[ ] /* LogicalAnd */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 6 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 2 values pushed */ + 34 1 + GETINFO[ ] /* GetInfo */ + LT[ ] /* LessThan */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 32 + GETINFO[ ] /* GetInfo */ + NOT[ ] /* LogicalNot */ + NOT[ ] /* LogicalNot */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 1 value pushed */ + 0 + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 7 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 2 values pushed */ + 36 1 + GETINFO[ ] /* GetInfo */ + LT[ ] /* LessThan */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 64 + GETINFO[ ] /* GetInfo */ + NOT[ ] /* LogicalNot */ + NOT[ ] /* LogicalNot */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 1 value pushed */ + 0 + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 8 + FDEF[ ] /* FunctionDefinition */ + SRP2[ ] /* SetRefPoint2 */ + SRP1[ ] /* SetRefPoint1 */ + DUP[ ] /* DuplicateTopStack */ + IP[ ] /* InterpolatePts */ + MDAP[1] /* MoveDirectAbsPt */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 9 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + RDTG[ ] /* RoundDownToGrid */ + PUSHB[ ] /* 1 value pushed */ + 6 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + MDRP[00100] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MDRP[01101] /* MoveDirectRelPt */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + CINDEX[ ] /* CopyXToTopStack */ + MD[0] /* MeasureDistance */ + SWAP[ ] /* SwapTopStack */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 4 + MINDEX[ ] /* MoveXToTopStack */ + MD[1] /* MeasureDistance */ + PUSHB[ ] /* 1 value pushed */ + 0 + LT[ ] /* LessThan */ + IF[ ] /* If */ + ROLL[ ] /* RollTopThreeStack */ + NEG[ ] /* Negate */ + ROLL[ ] /* RollTopThreeStack */ + SUB[ ] /* Subtract */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + LT[ ] /* LessThan */ + IF[ ] /* If */ + SHPIX[ ] /* ShiftZoneByPixel */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + ROLL[ ] /* RollTopThreeStack */ + ROLL[ ] /* RollTopThreeStack */ + SUB[ ] /* Subtract */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + SHPIX[ ] /* ShiftZoneByPixel */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + EIF[ ] /* EndIf */ + RTG[ ] /* RoundToGrid */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 10 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 6 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + SRP0[ ] /* SetRefPoint0 */ + ELSE[ ] /* Else */ + SRP0[ ] /* SetRefPoint0 */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 11 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + MDRP[10010] /* MoveDirectRelPt */ + PUSHB[ ] /* 1 value pushed */ + 12 + CALL[ ] /* CallFunction */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 12 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + MDAP[1] /* MoveDirectAbsPt */ + PUSHB[ ] /* 1 value pushed */ + 7 + CALL[ ] /* CallFunction */ + NOT[ ] /* LogicalNot */ + IF[ ] /* If */ + DUP[ ] /* DuplicateTopStack */ + DUP[ ] /* DuplicateTopStack */ + GC[1] /* GetCoordOnPVector */ + SWAP[ ] /* SwapTopStack */ + GC[0] /* GetCoordOnPVector */ + SUB[ ] /* Subtract */ + ROUND[10] /* Round */ + DUP[ ] /* DuplicateTopStack */ + IF[ ] /* If */ + DUP[ ] /* DuplicateTopStack */ + ABS[ ] /* Absolute */ + DIV[ ] /* Divide */ + SHPIX[ ] /* ShiftZoneByPixel */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 13 + FDEF[ ] /* FunctionDefinition */ + SRP2[ ] /* SetRefPoint2 */ + SRP1[ ] /* SetRefPoint1 */ + DUP[ ] /* DuplicateTopStack */ + DUP[ ] /* DuplicateTopStack */ + IP[ ] /* InterpolatePts */ + MDAP[1] /* MoveDirectAbsPt */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + DUP[ ] /* DuplicateTopStack */ + GC[1] /* GetCoordOnPVector */ + ROLL[ ] /* RollTopThreeStack */ + GC[0] /* GetCoordOnPVector */ + SUB[ ] /* Subtract */ + SWAP[ ] /* SwapTopStack */ + ROLL[ ] /* RollTopThreeStack */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + SWAP[ ] /* SwapTopStack */ + MD[1] /* MeasureDistance */ + PUSHB[ ] /* 1 value pushed */ + 0 + LT[ ] /* LessThan */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 64 + SHPIX[ ] /* ShiftZoneByPixel */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + SWAP[ ] /* SwapTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + LT[ ] /* LessThan */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 64 + NEG[ ] /* Negate */ + SHPIX[ ] /* ShiftZoneByPixel */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 14 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 6 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + RTDG[ ] /* RoundToDoubleGrid */ + MDRP[10110] /* MoveDirectRelPt */ + RTG[ ] /* RoundToGrid */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + ELSE[ ] /* Else */ + DUP[ ] /* DuplicateTopStack */ + MDRP[10110] /* MoveDirectRelPt */ + ROLL[ ] /* RollTopThreeStack */ + MPPEM[ ] /* MeasurePixelPerEm */ + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + SWAP[ ] /* SwapTopStack */ + MD[0] /* MeasureDistance */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + NEQ[ ] /* NotEqual */ + IF[ ] /* If */ + SHPIX[ ] /* ShiftZoneByPixel */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 15 + FDEF[ ] /* FunctionDefinition */ + SWAP[ ] /* SwapTopStack */ + DUP[ ] /* DuplicateTopStack */ + MDRP[10110] /* MoveDirectRelPt */ + DUP[ ] /* DuplicateTopStack */ + MDAP[1] /* MoveDirectAbsPt */ + PUSHB[ ] /* 1 value pushed */ + 7 + CALL[ ] /* CallFunction */ + NOT[ ] /* LogicalNot */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + DUP[ ] /* DuplicateTopStack */ + IF[ ] /* If */ + MPPEM[ ] /* MeasurePixelPerEm */ + GTEQ[ ] /* GreaterThanOrEqual */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + PUSHB[ ] /* 1 value pushed */ + 1 + EIF[ ] /* EndIf */ + IF[ ] /* If */ + ROLL[ ] /* RollTopThreeStack */ + PUSHB[ ] /* 1 value pushed */ + 4 + MINDEX[ ] /* MoveXToTopStack */ + MD[0] /* MeasureDistance */ + SWAP[ ] /* SwapTopStack */ + ROLL[ ] /* RollTopThreeStack */ + SWAP[ ] /* SwapTopStack */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + MD[0] /* MeasureDistance */ + ROLL[ ] /* RollTopThreeStack */ + SWAP[ ] /* SwapTopStack */ + SUB[ ] /* Subtract */ + SHPIX[ ] /* ShiftZoneByPixel */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 16 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + MDRP[11010] /* MoveDirectRelPt */ + PUSHB[ ] /* 1 value pushed */ + 18 + CALL[ ] /* CallFunction */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 17 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + MDRP[10010] /* MoveDirectRelPt */ + PUSHB[ ] /* 1 value pushed */ + 18 + CALL[ ] /* CallFunction */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 18 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + MDAP[1] /* MoveDirectAbsPt */ + PUSHB[ ] /* 1 value pushed */ + 7 + CALL[ ] /* CallFunction */ + NOT[ ] /* LogicalNot */ + IF[ ] /* If */ + DUP[ ] /* DuplicateTopStack */ + DUP[ ] /* DuplicateTopStack */ + GC[1] /* GetCoordOnPVector */ + SWAP[ ] /* SwapTopStack */ + GC[0] /* GetCoordOnPVector */ + SUB[ ] /* Subtract */ + ROUND[10] /* Round */ + ROLL[ ] /* RollTopThreeStack */ + DUP[ ] /* DuplicateTopStack */ + GC[1] /* GetCoordOnPVector */ + SWAP[ ] /* SwapTopStack */ + GC[0] /* GetCoordOnPVector */ + SWAP[ ] /* SwapTopStack */ + SUB[ ] /* Subtract */ + ROUND[10] /* Round */ + ADD[ ] /* Add */ + DUP[ ] /* DuplicateTopStack */ + IF[ ] /* If */ + DUP[ ] /* DuplicateTopStack */ + ABS[ ] /* Absolute */ + DIV[ ] /* Divide */ + SHPIX[ ] /* ShiftZoneByPixel */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 19 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + SDPVTL[1] /* SetDualPVectorToLine */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + CINDEX[ ] /* CopyXToTopStack */ + MD[1] /* MeasureDistance */ + ABS[ ] /* Absolute */ + SWAP[ ] /* SwapTopStack */ + ROLL[ ] /* RollTopThreeStack */ + SPVTL[1] /* SetPVectorToLine */ + PUSHB[ ] /* 1 value pushed */ + 32 + LT[ ] /* LessThan */ + IF[ ] /* If */ + ALIGNRP[ ] /* AlignRelativePt */ + ELSE[ ] /* Else */ + MDRP[00000] /* MoveDirectRelPt */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 20 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 4 values pushed */ + 0 64 1 64 + WS[ ] /* WriteStore */ + WS[ ] /* WriteStore */ + SVTCA[1] /* SetFPVectorToAxis */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHW[ ] /* 1 value pushed */ + 4096 + MUL[ ] /* Multiply */ + SVTCA[0] /* SetFPVectorToAxis */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHW[ ] /* 1 value pushed */ + 4096 + MUL[ ] /* Multiply */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + NEQ[ ] /* NotEqual */ + IF[ ] /* If */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + DIV[ ] /* Divide */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + SWAP[ ] /* SwapTopStack */ + WS[ ] /* WriteStore */ + ELSE[ ] /* Else */ + DIV[ ] /* Divide */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 1 + SWAP[ ] /* SwapTopStack */ + WS[ ] /* WriteStore */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 64 + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + PUSHB[ ] /* 3 values pushed */ + 0 32 0 + RS[ ] /* ReadStore */ + MUL[ ] /* Multiply */ + WS[ ] /* WriteStore */ + PUSHB[ ] /* 3 values pushed */ + 1 32 1 + RS[ ] /* ReadStore */ + MUL[ ] /* Multiply */ + WS[ ] /* WriteStore */ + PUSHB[ ] /* 1 value pushed */ + 32 + MUL[ ] /* Multiply */ + PUSHB[ ] /* 1 value pushed */ + 25 + NEG[ ] /* Negate */ + JMPR[ ] /* Jump */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 21 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 1 + RS[ ] /* ReadStore */ + MUL[ ] /* Multiply */ + SWAP[ ] /* SwapTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + RS[ ] /* ReadStore */ + MUL[ ] /* Multiply */ + SWAP[ ] /* SwapTopStack */ + ENDF[ ] /* EndFunctionDefinition */ + + + + + + PUSHW[ ] /* 1 value pushed */ + 511 + SCANCTRL[ ] /* ScanConversionControl */ + PUSHB[ ] /* 1 value pushed */ + 1 + SCANTYPE[ ] /* ScanType */ + SVTCA[0] /* SetFPVectorToAxis */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 8 + LT[ ] /* LessThan */ + IF[ ] /* If */ + PUSHB[ ] /* 2 values pushed */ + 1 1 + INSTCTRL[ ] /* SetInstrExecControl */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 2 values pushed */ + 70 6 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + PUSHB[ ] /* 1 value pushed */ + 16 + EIF[ ] /* EndIf */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 20 + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + PUSHB[ ] /* 1 value pushed */ + 128 + EIF[ ] /* EndIf */ + SCVTCI[ ] /* SetCVTCutIn */ + PUSHB[ ] /* 1 value pushed */ + 6 + CALL[ ] /* CallFunction */ + NOT[ ] /* LogicalNot */ + IF[ ] /* If */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 20 + CALL[ ] /* CallFunction */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright 2016 The M+ Project Authors. + + + M PLUS 1p + + + Regular + + + 1.062;M+ ;MPLUS1p-Regular + + + M PLUS 1p Regular + + + Version 1.062 + + + MPLUS1p-Regular + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/ttx/NewCMMath-Regular_1.ttx b/tests/ttx/NewCMMath-Regular_1.ttx new file mode 100644 index 00000000..136a660c --- /dev/null +++ b/tests/ttx/NewCMMath-Regular_1.ttx @@ -0,0 +1,353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (C) 2019-2021 Antonis Tsolomitis. +This work is released under the GUST Font License -- see http://tug.org/fonts/licenses/GUST-FONT-LICENSE.txt for details. + + + NewComputerModernMath + + + Regular + + + 3.00 + + + NewComputerModernMath-Regular + + + 4.0 + + + NewCMMath-Regular + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -326 endchar + + + 394 11 -21 240 40 hstem + 262 40 vstem + 447 270 rmoveto + -11 -9 -9 -11 -11 9 -9 11 hvcurveto + 176 hlineto + 11 9 9 11 11 -9 9 -11 hvcurveto + -466 hmoveto + 30 20 26 24 22 28 33 42 23 49 10 53 0 1 1 2 0 1 0 11 -9 9 -11 0 -10 0 -8 -7 -2 -9 -9 -47 -21 -44 -28 -36 rrcurveto + -36 -45 -44 -40 -57 -19 -5 -2 -4 -5 0 -6 0 -6 4 -5 5 -2 57 -19 44 -40 36 -45 28 -36 21 -44 9 -47 2 -9 8 -7 10 0 rrcurveto + 11 9 9 11 hvcurveto + 0 1 -1 2 0 1 -10 53 -23 49 -33 42 -22 28 -26 24 -30 20 rrcurveto + 176 hlineto + 11 9 9 11 11 -9 9 -11 hvcurveto + 424 hmoveto + -11 -9 -9 -11 -11 9 -9 11 hvcurveto + 166 hlineto + 11 9 9 11 11 -9 9 -11 hvcurveto + -166 -40 rmoveto + endchar + + + 78 448 40 204 -20 hstem + 322 40 vstem + 322 304 rmoveto + -176 vlineto + -11 9 -9 11 11 9 9 11 vhcurveto + 176 vlineto + 11 -9 9 -11 -11 -9 -9 -11 vhcurveto + 290 vmoveto + -176 vlineto + -11 9 -9 11 11 9 9 11 vhcurveto + 176 vlineto + 20 -30 24 -26 28 -22 42 -33 49 -23 53 -10 1 0 2 -2 1 0 11 0 9 9 0 11 0 10 -7 8 -9 2 -47 9 -44 21 -36 28 rrcurveto + -45 36 -40 45 -19 57 -2 5 -5 3 -6 0 -6 0 -5 -3 -2 -5 -19 -57 -40 -45 -45 -36 -36 -28 -44 -21 -47 -9 -9 -2 -7 -8 0 -10 rrcurveto + -11 9 -9 11 vhcurveto + 1 0 2 2 1 0 53 10 49 23 42 33 28 22 24 26 20 30 rrcurveto + -600 vmoveto + -166 vlineto + -11 9 -9 11 11 9 9 11 vhcurveto + 166 vlineto + 11 -9 9 -11 -11 -9 -9 -11 vhcurveto + 40 hmoveto + endchar + + + 394 11 -21 240 40 hstem + 699 40 vstem + 554 270 rmoveto + -176 hlineto + -11 -9 -9 -11 -11 9 -9 11 hvcurveto + 176 hlineto + 11 9 9 11 11 -9 9 -11 hvcurveto + 290 hmoveto + -176 hlineto + -11 -9 -9 -11 -11 9 -9 11 hvcurveto + 176 hlineto + -30 -20 -26 -24 -22 -28 -33 -42 -23 -49 -10 -53 0 -1 -1 -2 0 -1 0 -11 9 -9 11 0 10 0 8 7 2 9 9 47 21 44 28 36 rrcurveto + 36 45 44 40 57 19 5 2 4 5 0 6 0 6 -4 5 -5 2 -57 19 -44 40 -36 45 -28 36 -21 44 -9 47 -2 9 -8 7 -10 0 rrcurveto + -11 -9 -9 -11 hvcurveto + 0 -1 1 -2 0 -1 10 -53 23 -49 33 -42 22 -28 26 -24 30 -20 rrcurveto + -600 hmoveto + -166 hlineto + -11 -9 -9 -11 -11 9 -9 11 hvcurveto + 166 hlineto + 11 9 9 11 11 -9 9 -11 hvcurveto + -40 vmoveto + endchar + + + 78 12 40 640 -20 hstem + 322 40 vstem + 322 196 rmoveto + -11 9 -9 11 11 9 9 11 vhcurveto + 176 vlineto + 11 -9 9 -11 -11 -9 -9 -11 vhcurveto + -466 vmoveto + -20 30 -24 26 -28 22 -42 33 -49 23 -53 10 -1 0 -2 2 -1 0 -11 0 -9 -9 0 -11 0 -10 7 -8 9 -2 47 -9 44 -21 36 -28 rrcurveto + 45 -36 40 -45 19 -57 2 -5 5 -3 6 0 6 0 5 3 2 5 19 57 40 45 45 36 36 28 44 21 47 9 9 2 7 8 0 10 rrcurveto + 11 -9 9 -11 vhcurveto + -1 0 -2 -2 -1 0 -53 -10 -49 -23 -42 -33 -28 -22 -24 -26 -20 -30 rrcurveto + 176 vlineto + 11 -9 9 -11 -11 -9 -9 -11 vhcurveto + 424 vmoveto + -11 9 -9 11 11 9 9 11 vhcurveto + 166 vlineto + 11 -9 9 -11 -11 -9 -9 -11 vhcurveto + 40 -166 rmoveto + endchar + + + 170 -125 52 91 52 hstem + 57 24 614 23 vstem + 706 365 rmoveto + 18 7 0 27 -18 6 rrcurveto + -623 230 rlineto + -25 9 -14 -37 25 -9 rrcurveto + 575 -213 -575 -212 rlineto + -25 -9 14 -38 25 10 rrcurveto + 635 -85 rmoveto + 7 -5 5 -7 vhcurveto + -5 0 -5 -4 -1 -5 -17 -76 -44 -31 -67 -17 -8 -2 -9 -1 -8 0 -50 0 -46 35 -43 32 -52 39 -58 37 -62 0 -11 0 -12 -1 -12 -3 rrcurveto + -83 -20 -35 -56 -21 -93 rrcurveto + -3 vlineto + -6 6 -6 6 vhcurveto + 6 0 5 4 1 6 16 75 44 32 67 16 9 3 8 0 9 0 50 0 46 -34 42 -32 52 -39 59 -38 61 0 12 0 12 2 12 3 rrcurveto + 82 20 36 56 20 93 rrcurveto + endchar + + + 170 -125 52 91 52 621 -20 hstem + 57 24 134 39 268 39 134 23 vstem + 718 51 rmoveto + 7 -5 5 -7 vhcurveto + -5 0 -5 -4 -1 -5 -17 -76 -44 -31 -67 -17 -8 -2 -9 -1 -8 0 -50 0 -46 35 -43 32 -22 17 -24 16 -24 13 rrcurveto + 73 201 287 -105 rlineto + 25 -10 13 38 -25 9 rrcurveto + -286 106 89 246 197 73 rlineto + 25 9 -13 37 -25 -9 rrcurveto + -166 -61 33 90 rlineto + 0 2 1 2 0 3 0 11 -9 9 -11 0 -9 0 -7 -6 -3 -8 rrcurveto + -44 -121 -408 -151 rlineto + -19 -6 0 -27 19 -7 rrcurveto + 298 -110 -72 -198 rlineto + -21 8 -21 5 -23 0 -11 0 -12 -1 -12 -3 -83 -20 -35 -56 -21 -93 rrcurveto + -3 vlineto + -6 6 -6 6 vhcurveto + 6 0 5 4 1 6 16 75 44 32 67 16 9 3 8 0 9 0 15 0 15 -3 15 -5 rrcurveto + -63 -174 rlineto + -1 -2 0 -2 0 -3 0 -11 9 -9 11 0 8 0 8 6 3 8 rrcurveto + 61 169 rlineto + 20 -12 19 -14 18 -14 52 -39 59 -38 61 0 12 0 12 2 12 3 82 20 36 56 20 93 rrcurveto + -258 458 rmoveto + -78 -214 -251 92 rlineto + endchar + + + 170 -125 52 91 52 621 -20 hstem + 57 24 134 39 268 39 134 23 vstem + 718 51 rmoveto + 7 -5 5 -7 vhcurveto + -5 0 -5 -4 -1 -5 -17 -76 -44 -31 -67 -17 -8 -2 -9 -1 -8 0 -50 0 -46 35 -43 32 -22 17 -24 16 -24 13 rrcurveto + 79 217 294 108 rlineto + 18 7 0 27 -18 6 rrcurveto + -212 78 66 181 rlineto + 0 2 1 2 0 3 0 11 -9 9 -11 0 -9 0 -7 -6 -3 -8 rrcurveto + -65 -180 -374 138 rlineto + -25 9 -14 -37 25 -9 rrcurveto + 374 -138 -63 -172 -311 -115 rlineto + -25 -9 14 -38 25 10 rrcurveto + 279 103 -66 -182 rlineto + -21 8 -21 5 -23 0 -11 0 -12 -1 -12 -3 -83 -20 -35 -56 -21 -93 rrcurveto + -3 vlineto + -6 6 -6 6 vhcurveto + 6 0 5 4 1 6 16 75 44 32 67 16 9 3 8 0 9 0 15 0 15 -3 15 -5 rrcurveto + -63 -174 rlineto + -1 -2 0 -2 0 -3 0 -11 9 -9 11 0 8 0 8 6 3 8 rrcurveto + 61 169 rlineto + 20 -12 19 -14 18 -14 52 -39 59 -38 61 0 12 0 12 2 12 3 82 20 36 56 20 93 rrcurveto + -74 336 rmoveto + -214 -79 50 140 rlineto + endchar + + + 975 -125 29 hstem + 1581 -125 rmoveto + -5 29 -786 -114 -785 114 -5 -29 790 -150 rlineto + endchar + + + 975 570 28 hstem + 1581 598 rmoveto + -791 150 -790 -150 5 -28 785 114 786 -114 rlineto + endchar + + + 975 -287 29 hstem + 1581 -258 rmoveto + -791 150 -790 -150 5 -29 785 114 786 -114 rlineto + endchar + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/ttx/NotoSans-Regular_1.ttx b/tests/ttx/NotoSans-Regular_1.ttx new file mode 100644 index 00000000..1d72e955 --- /dev/null +++ b/tests/ttx/NotoSans-Regular_1.ttx @@ -0,0 +1,640 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PUSHW[ ] /* 1 value pushed */ + 511 + SCANCTRL[ ] /* ScanConversionControl */ + PUSHB[ ] /* 1 value pushed */ + 4 + SCANTYPE[ ] /* ScanType */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright 2015-2021 Google LLC. All Rights Reserved. + + + Noto Sans + + + Regular + + + 2.007;GOOG;NotoSans-Regular + + + Noto Sans Regular + + + Version 2.007 + + + NotoSans-Regular + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/ttx/NotoSansCJKsc-Bold-subset1_1.ttx b/tests/ttx/NotoSansCJKsc-Bold-subset1_1.ttx new file mode 100644 index 00000000..de0e0d0a --- /dev/null +++ b/tests/ttx/NotoSansCJKsc-Bold-subset1_1.ttx @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + © 2014-2021 Adobe (http://www.adobe.com/). + + + Noto Sans CJK SC + + + Bold + + + 2.004;GOOG;NotoSansCJKsc-Bold;ADOBE + + + Noto Sans CJK SC Bold + + + Version 2.004;hotconv 1.0.118;makeotfexe 2.5.65603 + + + NotoSansCJKsc-Bold + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -120 50 859 91 -50 50 hstemhm + 100 50 700 50 hintmask 10111000 + 100 -120 rmoveto + 800 1000 -800 hlineto + 400 -459 rmoveto + -318 409 rlineto + 636 hlineto + -286 -450 rmoveto + hintmask 11011000 + 318 409 rlineto + -818 vlineto + -668 -41 rmoveto + 318 409 318 -409 rlineto + -668 859 rmoveto + 318 -409 -318 -409 rlineto + endchar + + + 21 -21 180 113 324 107 hstem + 276 135 vstem + 276 hmoveto + 135 180 65 113 -65 431 -190 hlineto + -196 -443 rlineto + -101 251 vlineto + 113 vmoveto + -127 hlineto + 80 181 14 40 20 56 14 47 rlinecurve + 4 hlineto + -3 -51 -2 -62 -45 vvcurveto + endchar + + + + + + + + + + + + + + + diff --git a/tests/ttx/NotoSansCJKsc-Regular_1.ttx b/tests/ttx/NotoSansCJKsc-Regular_1.ttx new file mode 100644 index 00000000..181aff43 --- /dev/null +++ b/tests/ttx/NotoSansCJKsc-Regular_1.ttx @@ -0,0 +1,695 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + © 2014-2021 Adobe (http://www.adobe.com/). + + + Noto Sans CJK SC + + + Regular + + + 2.004;GOOG;NotoSansCJKsc-Regular;ADOBE + + + Noto Sans CJK SC + + + Version 2.004;hotconv 1.0.118;makeotfexe 2.5.65603 + + + NotoSansCJKsc-Regular + + + Noto Sans CJK SC + + + Regular + + + Noto Sans CJK SC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -120 50 859 91 -50 50 hstemhm + 100 50 700 50 hintmask 10111000 + 100 -120 rmoveto + 800 1000 -800 hlineto + 400 -459 rmoveto + -318 409 rlineto + 636 hlineto + -286 -450 rmoveto + hintmask 11011000 + 318 409 rlineto + -818 vlineto + -668 -41 rmoveto + 318 409 318 -409 rlineto + -668 859 rmoveto + 318 -409 -318 -409 rlineto + endchar + + + -71 52 48 58 245 60 75 58 82 55 81 57 hstemhm + 213 58 180 62 -62 66 257 28 22 68 -26 68 hintmask 1111110010100000 + 517 607 rmoveto + 307 -82 -307 hlineto + 218 vmoveto + 307 -81 -307 hlineto + hintmask 1111001101010000 + -66 -419 rmoveto + -214 351 58 -289 156 vlineto + -325 -54 rmoveto + 11 -68 12 -88 2 -59 58 14 rcurveline + -4 58 -12 87 -12 68 rrcurveto + -163 -4 rmoveto + -11 -79 -16 -91 -20 -62 16 -4 29 -8 14 -6 16 61 20 95 13 81 rrcurveto + 157 26 rmoveto + 22 -62 26 -81 10 -53 54 19 rcurveline + -11 52 -25 79 -24 62 rrcurveto + -285 14 rmoveto + 20 10 29 8 213 32 15 -51 rcurveline + 42 17 11 -12 12 -18 6 -11 rlinecurve + 44 35 36 40 29 42 rrcurveto + 344 hlineto + -11 -231 -12 -86 -19 -21 rrcurveto + -11 -8 -8 -2 -15 hhcurveto + -15 -36 1 3 -41 hvcurveto + 10 -15 6 -25 1 -16 40 -3 41 0 22 2 26 2 20 6 15 20 28 32 12 94 12 279 rrcurveto + 10 1 21 0 vhcurveto + -376 hlineto + 12 22 10 23 9 22 -52 8 rcurveline + hintmask 0001010010100000 + 354 333 -441 -333 73 hlineto + -23 -59 -43 -72 -65 -56 -15 50 -27 73 -22 57 -55 -16 rcurveline + 12 -30 12 -34 11 -33 -156 -22 rcurveline + 81 94 81 120 66 119 -62 36 rcurveline + -23 -47 -27 -48 -27 -44 -106 -9 rcurveline + 54 76 53 97 41 93 -68 28 rcurveline + -39 -108 -67 -114 -21 -29 -20 -31 -17 -21 -17 -4 8 -18 11 -34 4 -15 14 6 23 5 106 13 -37 -55 -32 -43 -16 -17 -30 -38 -23 -25 -21 -5 rrcurveto + hintmask 1111001101010000 + 8 -19 12 -33 3 -15 rrcurveto + 592 75 rmoveto + -18 -73 -43 -57 -63 -38 12 -10 20 -21 8 -10 36 24 31 31 25 36 41 -31 44 -36 24 -25 39 42 rcurveline + -27 27 -52 39 -44 30 10 20 7 22 6 23 rrcurveto + endchar + + + 111 59 98 56 97 56 96 59 95 65 hstem + 215 58 203 69 290 71 vstem + 545 421 rmoveto + 290 -97 -290 hlineto + -56 vmoveto + 290 -98 -290 hlineto + 403 vmoveto + 290 -96 -290 hlineto + 46 -385 rmoveto + -45 -44 -92 -51 -79 -29 16 -14 21 -24 11 -15 79 31 94 53 58 53 rrcurveto + 61 -4 rmoveto + 68 -37 84 -57 40 -38 60 44 rcurveline + -45 38 -86 55 -65 35 rrcurveto + -582 101 rmoveto + 12 -68 12 -88 2 -59 58 14 rcurveline + -3 58 -12 87 -12 68 rrcurveto + -166 -4 rmoveto + -9 -81 -15 -90 -24 -61 16 -5 29 -10 14 -7 21 63 19 94 11 86 rrcurveto + 159 24 rmoveto + 22 -62 26 -81 10 -53 56 19 rcurveline + -12 52 -25 79 -25 62 rrcurveto + 123 406 rmoveto + -521 430 521 -196 vlineto + 31 95 rlineto + 199 65 -497 -65 215 hlineto + -6 -31 -8 -34 -8 -30 rrcurveto + -572 -392 rmoveto + 19 10 31 8 232 35 5 -18 4 -17 3 -14 58 22 rcurveline + -10 52 -31 83 -32 64 -54 -18 rcurveline + 14 -31 14 -35 12 -34 -177 -24 rcurveline + 81 96 80 119 65 118 -61 36 rcurveline + -22 -48 -27 -48 -28 -44 -107 -9 rcurveline + 56 76 56 98 44 95 -67 27 rcurveline + -40 -109 -70 -115 -22 -29 -21 -31 -17 -20 -17 -4 8 -18 10 -33 4 -14 14 6 22 5 108 13 -37 -56 -33 -43 -16 -18 -30 -37 -23 -26 -21 -5 8 -18 12 -33 3 -14 rrcurveto + endchar + + + -76 67 152 60 -46.5 46.5 88 53.5 61.5 50 156 55 27 59 8 53 -29 29 hstemhm + 285 64 129 73 hintmask 1011110101100000 + 629 82 rmoveto + 81 -41 104 -66 56 -41 65 40 rcurveline + -57 39 -104 62 -82 41 rrcurveto + -411 -6 rmoveto + -57 -51 -91 -50 -83 -33 18 -11 29 -23 13 -14 80 38 97 60 64 58 rrcurveto + -293 583 rmoveto + -55 168 vlineto + -49 -52 -70 -48 -61 -26 15 -12 16 -23 9 -15 69 37 79 63 51 63 rrcurveto + -131 vlineto + -9 -3 -2 -10 -1 vhcurveto + -8 -31 0 1 -32 hvcurveto + 8 -15 10 -20 4 -16 rrcurveto + 48 30 1 8 22 hvcurveto + 21 8 5 13 30 vvcurveto + 146 98 vlineto + -15 -32 -18 -30 -14 -24 51 -18 rcurveline + 18 27 19 36 19 37 13 -9 14 -12 8 -8 20 18 20 21 19 24 21 -37 26 -35 30 -30 -51 -34 -62 -24 -69 -18 13 -14 21 -28 8 -14 70 22 63 29 54 38 rrcurveto + 56 -46 66 -34 74 -22 10 18 19 26 15 15 -71 17 -64 29 -54 38 rrcurveto + hintmask 1011111101100000 + 44 42 34 51 22 63 rrcurveto + 73 59 -276 hlineto + 11 22 10 23 9 24 -65 16 rcurveline + hintmask 1011110011100000 + -24 -67 -41 -63 -48 -50 -36 11 rcurveline + -11 -2 rlineto + -71 hlineto + -11 7 -14 8 -15 7 43 27 47 34 36 35 rrcurveto + hintmask 1011110101100000 + -39 33 -14 -4 rlineto + -344 -53 286 hlineto + -22 -18 -24 -17 -23 -15 -36 14 -39 12 -34 8 -32 -38 rcurveline + 38 -10 42 -14 38 -16 rrcurveto + hintmask 1101111101100000 + 514 27 rmoveto + -17 -45 -26 -38 -34 -32 -36 33 -29 39 -21 43 rrcurveto + -433 -428 rmoveto + 18 7 28 3 179 15 -76 -37 -65 -27 -31 -12 -55 -21 -40 -14 -33 -2 8 -19 10 -34 3 -14 27 10 37 4 260 18 rrcurveto + -138 vlineto + -11 -4 -3 -16 -1 vhcurveto + -16 -1 -53 0 -61 2 11 -19 13 -27 4 -21 rrcurveto + 73 49 1 11 33 hvcurveto + 32 11 8 19 38 vvcurveto + hintmask 1011110101100000 + 144 vlineto + 241 17 32 -29 27 -28 18 -23 rlinecurve + 62 36 -46 55 -95 80 -79 55 rlinecurve + -58 -32 26 -19 29 -22 27 -23 rlinecurve + -389 -24 128 49 130 62 126 77 rlinecurve + -59 45 -39 -25 -41 -25 -42 -23 rlinecurve + -208 -13 52 27 53 32 49 37 rlinecurve + -58 36 -66 -55 -90 -51 -28 -14 rlinecurve + -26 -13 -20 -8 -20 -2 rrcurveto + hintmask 1101111101100000 + 8 -17 10 -31 3 -13 rrcurveto + endchar + + + -32 59 85 53 78 53 81 61 107 63 hstemhm + 212 60 51 70 -59 59 36 67 40 63 77.5 65.5 -55.5 68.5 95 66 hintmask 1111111011011000 + 187 189 rmoveto + 12 -69 11 -91 2 -59 60 12 rcurveline + -3 59 -12 89 -12 69 rrcurveto + -161 -2 rmoveto + -10 -81 -14 -90 -24 -61 16 -5 30 -10 13 -7 21 62 19 94 11 87 rrcurveto + 138 25 rmoveto + 17 -47 19 -61 7 -40 55 19 rcurveline + -8 39 -20 60 -17 46 rrcurveto + -271 13 rmoveto + 18 10 31 7 208 33 rrcurveto + hintmask 0010000100000000 + 5 -21 4 -20 2 -17 rrcurveto + hintmask 0010001000000000 + 59 18 -8 52 -26 90 -25 68 rlinecurve + -56 -14 10 -30 11 -34 9 -33 rlinecurve + -151 -21 79 91 77 113 62 113 rlinecurve + -64 38 -21 -44 -24 -44 -26 -41 rlinecurve + -103 -8 56 76 55 98 44 94 rlinecurve + -71 30 -40 -109 -69 -115 -22 -29 rlinecurve + -20 -31 -18 -21 -17 -4 8 -19 12 -35 4 -15 15 6 22 5 104 13 -37 -55 -33 -43 -15 -17 -30 -36 -22 -25 -21 -5 rrcurveto + hintmask 1111111011011000 + 9 -19 11 -35 4 -15 rrcurveto + 623 486 rmoveto + -5 -118 rlineto + -188 96 hlineto + 63 6 66 7 64 9 rrcurveto + -260 32 rmoveto + -327 vlineto + -152 -9 -192 -78 -139 vhcurveto + 17 -8 26 -18 13 -12 rrcurveto + 86 148 12 203 169 vvcurveto + 115 vlineto + hintmask 1001100001101000 + 185 hlineto + -9 -107 rlineto + -136 -515 63 45 251 -45 66 515 -179 hlineto + 10 107 rlineto + 208 63 hlineto + hintmask 0000100000010000 + -204 hlineto + 8 129 56 10 53 11 45 12 rlinecurve + -51 57 rlineto + hintmask 1111111011011000 + -102 -29 -180 -25 -151 -15 rrcurveto + 170 -515 rmoveto + 251 -78 -251 hlineto + 131 vmoveto + 81 251 -81 vlineto + -251 -269 rmoveto + 85 251 -85 vlineto + endchar + + + -42 64 110 59 109 64 65 59 108 60 65 65 hstemhm + 214 61 53 73 -60 60 41 68 -24 67 95 66 94 70 -21 70 hintmask 0111111000111000 + 553 596 rmoveto + 255 -108 -255 hlineto + -67 168 rmoveto + -227 392 227 vlineto + -454 130 rmoveto + -65 522 65 vlineto + -754 -595 rmoveto + 11 -71 9 -92 2 -61 61 11 rcurveline + -3 61 -10 90 -11 71 rrcurveto + -167 -3 rmoveto + -10 -83 -15 -92 -24 -62 17 -5 31 -11 14 -7 21 63 20 97 11 89 rrcurveto + 142 23 rmoveto + 18 -57 20 -76 7 -49 57 16 rcurveline + -9 49 -20 75 -19 56 rrcurveto + 303 77 rmoveto + -109 vlineto + hintmask 1110000101010100 + -138 109 hlineto + 204 hmoveto + 143 -109 -143 hlineto + -204 -59 rmoveto + 138 -110 -138 hlineto + 347 110 rmoveto + -110 -143 110 vlineto + -272 232 rmoveto + -444 68 38 347 -38 70 444 vlineto + -861 -124 rmoveto + 19 10 30 8 213 31 rrcurveto + hintmask 0010000010000000 + 6 -24 4 -21 3 -18 rrcurveto + hintmask 0100000100000000 + 60 20 -9 54 -28 91 -26 69 rlinecurve + -56 -16 11 -30 10 -34 10 -33 rlinecurve + -156 -20 81 90 81 116 66 114 rlinecurve + -66 38 -21 -44 -26 -45 -26 -41 rlinecurve + -108 -9 57 75 56 97 45 94 rlinecurve + -70 30 -41 -109 -72 -114 -22 -30 rlinecurve + -20 -30 -19 -20 -16 -5 8 -18 11 -35 4 -15 15 6 21 5 108 13 -38 -54 -33 -42 -16 -18 -31 -36 -22 -25 -21 -5 rrcurveto + hintmask 1110000101010100 + 8 -19 12 -36 4 -15 rrcurveto + endchar + + + 468 67 hstem + 91 56 51 54 220 65 120 52 46 67 vstem + 176 188 rmoveto + 11 -68 10 -88 1 -59 54 12 rcurveline + -2 58 -11 87 -12 68 rrcurveto + -136 -1 rmoveto + -10 -81 -14 -90 -24 -61 15 -5 27 -9 12 -6 21 62 19 94 10 86 rrcurveto + 113 19 rmoveto + 18 -55 20 -71 7 -46 51 16 rcurveline + -9 45 -20 70 -20 54 rrcurveto + 350 399 rmoveto + -4 -84 -9 -103 -24 -62 50 -30 rcurveline + 26 69 8 111 5 88 rrcurveto + 190 23 rmoveto + -14 -81 -30 -119 -25 -71 47 -15 rcurveline + 27 69 31 113 25 88 rrcurveto + -205 222 rmoveto + -1 -384 4 -319 -181 -168 17 -10 24 -21 11 -16 83 80 48 110 27 133 29 -132 42 -115 58 -73 12 17 23 23 16 12 -79 90 -50 184 -26 187 rrcurveto + 9 123 1 135 144 vvcurveto + -753 -596 rmoveto + 17 10 28 8 185 35 10 -53 rcurveline + 53 16 -7 48 -21 83 -22 64 rlinecurve + -51 -12 9 -28 9 -32 8 -32 rlinecurve + -135 -22 74 93 73 120 59 117 rlinecurve + -64 31 -19 -46 -25 -48 -24 -44 rlinecurve + -95 -8 50 77 49 101 36 96 rlinecurve + -67 28 -32 -109 -60 -116 -18 -29 rlinecurve + -18 -31 -15 -21 -16 -4 8 -18 11 -34 4 -14 13 6 20 5 96 12 -33 -55 -30 -43 -14 -18 -28 -37 -20 -26 -20 -5 8 -17 11 -34 3 -14 rrcurveto + 291 295 rmoveto + -67 106 vlineto + -25 -120 -48 -135 -51 -71 13 -19 18 -32 7 -21 34 53 32 84 26 89 rrcurveto + -376 65 391 vlineto + 21 -39 21 -45 11 -26 39 60 rcurveline + -13 23 -59 93 -20 31 rrcurveto + 60 84 67 -84 194 vlineto + 39 15 37 17 32 17 -61 52 rcurveline + -52 -34 -92 -38 -82 -26 10 -14 10 -25 5 -15 29 8 30 9 30 10 rrcurveto + -170 vlineto + endchar + + + -78 66 -18 18 163 57 86 56 159 65 93 65 hstemhm + 212 57 176 67 142 63 145 67 hintmask 0111111111000000 + 568 475 rmoveto + 19 -39 20 -52 6 -32 50 19 rcurveline + -8 31 -21 50 -20 38 rrcurveto + -428 -301 rmoveto + 12 -68 11 -88 3 -59 57 14 rcurveline + -4 58 -11 87 -12 68 rrcurveto + -162 -4 rmoveto + -10 -81 -14 -90 -24 -61 15 -5 29 -10 13 -7 21 63 19 94 11 86 rrcurveto + 157 24 rmoveto + 21 -62 26 -81 10 -53 54 19 rcurveline + -11 52 -25 79 -25 62 rrcurveto + 308 615 rmoveto + -2 -32 -2 -38 -4 -39 rrcurveto + -232 -65 225 hlineto + -4 -33 -4 -33 -4 -27 rrcurveto + -183 -653 67 588 350 -505 hlineto + -12 -4 -4 -12 vhcurveto + -13 -1 -39 0 -46 1 rrcurveto + hintmask 1011110011000000 + 9 -18 10 -29 4 -19 rrcurveto + 61 40 1 11 25 hvcurveto + 25 12 7 21 36 vvcurveto + 571 -234 vlineto + 14 93 rlineto + 245 65 -236 hlineto + 13 100 rlineto + 24 -339 rmoveto + -10 -41 -20 -58 -18 -44 rrcurveto + -160 -56 107 -86 -116 -57 116 hlineto + hintmask 0111000010000000 + -181 63 181 118 57 -118 86 106 56 -63 vlineto + 17 38 18 47 16 41 rrcurveto + -748 -236 rmoveto + 18 10 32 8 228 35 5 -18 4 -17 3 -14 56 22 rcurveline + -10 52 -30 83 -32 64 -52 -18 rcurveline + 14 -30 13 -36 12 -34 -175 -24 rcurveline + 80 96 79 119 64 118 -60 36 rcurveline + -22 -48 -27 -48 -26 -44 -106 -9 rcurveline + 55 76 55 98 43 95 -65 27 rcurveline + -40 -109 -69 -115 -21 -29 -21 -31 -17 -20 -17 -4 8 -18 11 -33 3 -14 14 6 22 5 106 13 -36 -56 -32 -43 -16 -18 -30 -37 -22 -26 -21 -5 8 -18 10 -33 4 -14 rrcurveto + endchar + + + -78 66 -18 18 163 57 86 56 159 65 93 65 -65 173 hstemhm + 212 58 175 67 138 67 -63 63 145 67 hintmask 0111110110110000 + 568 475 rmoveto + 19 -39 20 -52 6 -32 50 19 rcurveline + -8 31 -21 50 -20 38 rrcurveto + -428 -301 rmoveto + 12 -68 11 -88 3 -59 58 14 rcurveline + -4 58 -12 87 -12 68 rrcurveto + -162 -4 rmoveto + -10 -81 -14 -90 -24 -61 15 -5 29 -10 14 -7 20 63 19 94 11 86 rrcurveto + 157 24 rmoveto + 22 -62 25 -81 10 -53 55 19 rcurveline + -11 52 -26 79 -24 62 rrcurveto + hintmask 0000001001000000 + 302 614 rmoveto + hintmask 0100110011010000 + -108 -235 -65 235 -93 -205 -653 67 588 350 -505 vlineto + -12 -4 -4 -12 vhcurveto + -13 -1 -39 0 -46 1 rrcurveto + hintmask 1000110000110000 + 9 -18 10 -29 4 -19 rrcurveto + 61 40 1 11 25 hvcurveto + 25 12 7 21 36 vvcurveto + 571 -212 93 237 65 -237 vlineto + hintmask 0111001000100000 + 108 vlineto + 38 -347 rmoveto + -10 -41 -20 -58 -18 -44 rrcurveto + -160 -56 107 -86 -116 -57 116 -181 63 181 119 57 -119 86 106 56 -63 hlineto + 17 38 18 47 16 41 rrcurveto + -748 -236 rmoveto + 18 10 32 8 228 35 5 -18 4 -17 3 -14 58 22 rcurveline + -11 52 -30 83 -32 64 -53 -18 rcurveline + 14 -31 13 -35 12 -34 -175 -24 rcurveline + 80 96 79 119 65 119 -61 35 rcurveline + -22 -48 -27 -48 -27 -44 -104 -9 rcurveline + 55 76 55 98 43 95 -65 27 rcurveline + -40 -109 -70 -115 -21 -29 -21 -31 -17 -20 -17 -4 8 -18 11 -33 3 -14 14 6 22 5 106 13 -37 -56 -32 -43 -15 -18 -30 -37 -22 -26 -21 -5 8 -18 10 -33 4 -14 rrcurveto + endchar + + + -63 -13 73 190 65 169 73 100 110 hstemhm + 52 89 -89 90 -10 111 104 111 -26 80 hintmask 1110100100000000 + 312 -13 rmoveto + 73 58 24 31 47 hvcurveto + -32 61 rlineto + -27 -41 -42 -16 -53 hhcurveto + hintmask 1110010010000000 + -103 -71 74 116 -6 hvcurveto + 366 hlineto + 2 14 2 18 20 vvcurveto + 155 -78 100 -139 vhcurveto + hintmask 1110100100000000 + -124 -119 -109 -177 -179 115 -105 145 hvcurveto + hintmask 1110100010000000 + -171 328 rmoveto + 108 11 68 61 77 hhcurveto + 85 50 -59 -110 hvcurveto + hintmask 1111001100000000 + -245 342 rmoveto + 31 25 24 31 31 -25 24 -31 -32 -23 -24 -31 -31 23 -24 32 hvcurveto + 216 hmoveto + 30 25 24 31 31 -25 24 -30 -33 -23 -24 -31 -31 23 -24 33 hvcurveto + endchar + + + 684 69 hstem + 687 80 vstem + 151 753 rmoveto + -69 536 vlineto + -72 0 -87 -20 -119 vhcurveto + 79 -8 rlineto + 21 120 0 91 75 vvcurveto + 69 vlineto + endchar + + + 676 68 hstem + 357 70 268 71 vstem + 151 744 rmoveto + -68 206 vlineto + -1 -59 -5 -87 -30 -122 69 -15 rcurveline + 38 147 -1 107 60 vvcurveto + 37 vlineto + 57 hmoveto + -68 211 vlineto + -71 -1 -91 -19 -125 vhcurveto + 71 -7 rlineto + 20 131 0 97 74 vvcurveto + 60 vlineto + endchar + + + 417 68 hstem + 150 78 vstem + 150 485 rmoveto + -68 622 68 -544 269 -78 vlineto + endchar + + + 401 68 216 68 hstem + 150 80 vstem + 150 469 rmoveto + -68 617 68 -537 216 514 68 -594 vlineto + endchar + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/ttx/Roboto-Regular_1.ttx b/tests/ttx/Roboto-Regular_1.ttx new file mode 100644 index 00000000..afc589bc --- /dev/null +++ b/tests/ttx/Roboto-Regular_1.ttx @@ -0,0 +1,1512 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PUSHB[ ] /* 1 value pushed */ + 0 + FDEF[ ] /* FunctionDefinition */ + MPPEM[ ] /* MeasurePixelPerEm */ + PUSHB[ ] /* 1 value pushed */ + 9 + LT[ ] /* LessThan */ + IF[ ] /* If */ + PUSHB[ ] /* 2 values pushed */ + 1 1 + INSTCTRL[ ] /* SetInstrExecControl */ + EIF[ ] /* EndIf */ + PUSHW[ ] /* 1 value pushed */ + 511 + SCANCTRL[ ] /* ScanConversionControl */ + PUSHB[ ] /* 1 value pushed */ + 132 + SCVTCI[ ] /* SetCVTCutIn */ + PUSHB[ ] /* 2 values pushed */ + 9 3 + SDS[ ] /* SetDeltaShiftInGState */ + SDB[ ] /* SetDeltaBaseInGState */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 1 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + DUP[ ] /* DuplicateTopStack */ + RCVT[ ] /* ReadCVT */ + ROUND[01] /* Round */ + WCVTP[ ] /* WriteCVTInPixels */ + PUSHB[ ] /* 1 value pushed */ + 1 + ADD[ ] /* Add */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 2 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 1 + LOOPCALL[ ] /* LoopAndCallFunction */ + POP[ ] /* PopTopStack */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 3 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + GC[0] /* GetCoordOnPVector */ + PUSHB[ ] /* 1 value pushed */ + 3 + CINDEX[ ] /* CopyXToTopStack */ + GC[0] /* GetCoordOnPVector */ + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + MD[0] /* MeasureDistance */ + ABS[ ] /* Absolute */ + ROLL[ ] /* RollTopThreeStack */ + DUP[ ] /* DuplicateTopStack */ + GC[0] /* GetCoordOnPVector */ + DUP[ ] /* DuplicateTopStack */ + ROUND[00] /* Round */ + SUB[ ] /* Subtract */ + ABS[ ] /* Absolute */ + PUSHB[ ] /* 1 value pushed */ + 4 + CINDEX[ ] /* CopyXToTopStack */ + GC[0] /* GetCoordOnPVector */ + DUP[ ] /* DuplicateTopStack */ + ROUND[00] /* Round */ + SUB[ ] /* Subtract */ + ABS[ ] /* Absolute */ + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + NEG[ ] /* Negate */ + ROLL[ ] /* RollTopThreeStack */ + EIF[ ] /* EndIf */ + MDAP[1] /* MoveDirectAbsPt */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + ROUND[01] /* Round */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + EQ[ ] /* Equal */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + PUSHB[ ] /* 1 value pushed */ + 64 + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + ROUND[01] /* Round */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 0 + EQ[ ] /* Equal */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + PUSHB[ ] /* 1 value pushed */ + 64 + NEG[ ] /* Negate */ + EIF[ ] /* EndIf */ + EIF[ ] /* EndIf */ + MSIRP[0] /* MoveStackIndirRelPt */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 4 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + GC[0] /* GetCoordOnPVector */ + PUSHB[ ] /* 1 value pushed */ + 4 + CINDEX[ ] /* CopyXToTopStack */ + GC[0] /* GetCoordOnPVector */ + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + ROLL[ ] /* RollTopThreeStack */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + GC[0] /* GetCoordOnPVector */ + DUP[ ] /* DuplicateTopStack */ + ROUND[10] /* Round */ + SUB[ ] /* Subtract */ + ABS[ ] /* Absolute */ + PUSHB[ ] /* 1 value pushed */ + 4 + CINDEX[ ] /* CopyXToTopStack */ + GC[0] /* GetCoordOnPVector */ + DUP[ ] /* DuplicateTopStack */ + ROUND[10] /* Round */ + SUB[ ] /* Subtract */ + ABS[ ] /* Absolute */ + GT[ ] /* GreaterThan */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + ROLL[ ] /* RollTopThreeStack */ + EIF[ ] /* EndIf */ + MDAP[1] /* MoveDirectAbsPt */ + MIRP[11101] /* MoveIndirectRelPt */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 5 + FDEF[ ] /* FunctionDefinition */ + MPPEM[ ] /* MeasurePixelPerEm */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + LT[ ] /* LessThan */ + IF[ ] /* If */ + LTEQ[ ] /* LessThenOrEqual */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 128 + WCVTP[ ] /* WriteCVTInPixels */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 1 value pushed */ + 64 + WCVTP[ ] /* WriteCVTInPixels */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + DUP[ ] /* DuplicateTopStack */ + RCVT[ ] /* ReadCVT */ + PUSHB[ ] /* 1 value pushed */ + 192 + LT[ ] /* LessThan */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 192 + WCVTP[ ] /* WriteCVTInPixels */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 6 + FDEF[ ] /* FunctionDefinition */ + DUP[ ] /* DuplicateTopStack */ + DUP[ ] /* DuplicateTopStack */ + RCVT[ ] /* ReadCVT */ + ROUND[01] /* Round */ + WCVTP[ ] /* WriteCVTInPixels */ + PUSHB[ ] /* 1 value pushed */ + 1 + ADD[ ] /* Add */ + DUP[ ] /* DuplicateTopStack */ + DUP[ ] /* DuplicateTopStack */ + RCVT[ ] /* ReadCVT */ + RDTG[ ] /* RoundDownToGrid */ + ROUND[01] /* Round */ + RTG[ ] /* RoundToGrid */ + WCVTP[ ] /* WriteCVTInPixels */ + PUSHB[ ] /* 1 value pushed */ + 1 + ADD[ ] /* Add */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 7 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 6 + LOOPCALL[ ] /* LoopAndCallFunction */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 8 + FDEF[ ] /* FunctionDefinition */ + MPPEM[ ] /* MeasurePixelPerEm */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 64 + ELSE[ ] /* Else */ + PUSHB[ ] /* 1 value pushed */ + 0 + EIF[ ] /* EndIf */ + ROLL[ ] /* RollTopThreeStack */ + ROLL[ ] /* RollTopThreeStack */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + PUSHB[ ] /* 1 value pushed */ + 128 + ROLL[ ] /* RollTopThreeStack */ + ROLL[ ] /* RollTopThreeStack */ + ELSE[ ] /* Else */ + ROLL[ ] /* RollTopThreeStack */ + SWAP[ ] /* SwapTopStack */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + PUSHB[ ] /* 1 value pushed */ + 192 + ROLL[ ] /* RollTopThreeStack */ + ROLL[ ] /* RollTopThreeStack */ + ELSE[ ] /* Else */ + ROLL[ ] /* RollTopThreeStack */ + SWAP[ ] /* SwapTopStack */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + PUSHW[ ] /* 1 value pushed */ + 256 + ROLL[ ] /* RollTopThreeStack */ + ROLL[ ] /* RollTopThreeStack */ + ELSE[ ] /* Else */ + ROLL[ ] /* RollTopThreeStack */ + SWAP[ ] /* SwapTopStack */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + PUSHW[ ] /* 1 value pushed */ + 320 + ROLL[ ] /* RollTopThreeStack */ + ROLL[ ] /* RollTopThreeStack */ + ELSE[ ] /* Else */ + ROLL[ ] /* RollTopThreeStack */ + SWAP[ ] /* SwapTopStack */ + EIF[ ] /* EndIf */ + DUP[ ] /* DuplicateTopStack */ + PUSHB[ ] /* 1 value pushed */ + 3 + MINDEX[ ] /* MoveXToTopStack */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 3 + CINDEX[ ] /* CopyXToTopStack */ + RCVT[ ] /* ReadCVT */ + PUSHW[ ] /* 1 value pushed */ + 384 + LT[ ] /* LessThan */ + IF[ ] /* If */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + PUSHW[ ] /* 1 value pushed */ + 384 + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 1 value pushed */ + 3 + CINDEX[ ] /* CopyXToTopStack */ + RCVT[ ] /* ReadCVT */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + SWAP[ ] /* SwapTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + WCVTP[ ] /* WriteCVTInPixels */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 9 + FDEF[ ] /* FunctionDefinition */ + MPPEM[ ] /* MeasurePixelPerEm */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + RCVT[ ] /* ReadCVT */ + WCVTP[ ] /* WriteCVTInPixels */ + ELSE[ ] /* Else */ + POP[ ] /* PopTopStack */ + POP[ ] /* PopTopStack */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 10 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 40 + RCVT[ ] /* ReadCVT */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 11 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 41 + RCVT[ ] /* ReadCVT */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 12 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 2 values pushed */ + 39 1 + GETINFO[ ] /* GetInfo */ + DUP[ ] /* DuplicateTopStack */ + ROLL[ ] /* RollTopThreeStack */ + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + PUSHW[ ] /* 2 values pushed */ + 16384 1024 + MUL[ ] /* Multiply */ + PUSHW[ ] /* 1 value pushed */ + 2048 + GETINFO[ ] /* GetInfo */ + EQ[ ] /* Equal */ + IF[ ] /* If */ + PUSHW[ ] /* 2 values pushed */ + 40 1000 + WCVTF[ ] /* WriteCVTInFUnits */ + EIF[ ] /* EndIf */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 1 value pushed */ + 35 + GTEQ[ ] /* GreaterThanOrEqual */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 32 + GETINFO[ ] /* GetInfo */ + PUSHW[ ] /* 1 value pushed */ + 4096 + EQ[ ] /* Equal */ + IF[ ] /* If */ + PUSHW[ ] /* 2 values pushed */ + 40 1000 + WCVTF[ ] /* WriteCVTInFUnits */ + EIF[ ] /* EndIf */ + EIF[ ] /* EndIf */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 13 + FDEF[ ] /* FunctionDefinition */ + PUSHB[ ] /* 1 value pushed */ + 64 + GETINFO[ ] /* GetInfo */ + PUSHW[ ] /* 1 value pushed */ + 8192 + AND[ ] /* LogicalAnd */ + IF[ ] /* If */ + PUSHB[ ] /* 2 values pushed */ + 41 0 + WCVTP[ ] /* WriteCVTInPixels */ + ELSE[ ] /* Else */ + PUSHW[ ] /* 2 values pushed */ + 41 1000 + WCVTP[ ] /* WriteCVTInPixels */ + EIF[ ] /* EndIf */ + ENDF[ ] /* EndFunctionDefinition */ + + + + + + PUSHB[ ] /* 1 value pushed */ + 12 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 1 value pushed */ + 0 + CALL[ ] /* CallFunction */ + SVTCA[0] /* SetFPVectorToAxis */ + PUSHB[ ] /* 3 values pushed */ + 1 16 2 + CALL[ ] /* CallFunction */ + SVTCA[1] /* SetFPVectorToAxis */ + PUSHB[ ] /* 3 values pushed */ + 17 1 2 + CALL[ ] /* CallFunction */ + SVTCA[1] /* SetFPVectorToAxis */ + PUSHB[ ] /* 8 values pushed */ + 17 58 48 37 27 16 0 8 + CALL[ ] /* CallFunction */ + SVTCA[0] /* SetFPVectorToAxis */ + PUSHB[ ] /* 8 values pushed */ + 1 72 59 46 33 20 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 2 88 72 56 40 20 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 3 82 67 52 37 22 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 4 94 77 60 43 25 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 5 54 44 34 25 15 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 6 113 93 70 50 27 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 7 145 119 92 58 35 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 8 126 103 80 57 26 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 9 84 69 54 38 20 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 10 118 96 75 54 29 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 11 131 100 78 58 35 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 12 217 178 138 99 60 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 13 20 16 12 9 6 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 14 60 50 39 28 17 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 15 64 52 41 29 20 0 8 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 8 values pushed */ + 16 80 65 46 33 20 0 8 + CALL[ ] /* CallFunction */ + SVTCA[0] /* SetFPVectorToAxis */ + PUSHB[ ] /* 3 values pushed */ + 18 11 7 + CALL[ ] /* CallFunction */ + PUSHB[ ] /* 1 value pushed */ + 0 + DUP[ ] /* DuplicateTopStack */ + RCVT[ ] /* ReadCVT */ + RDTG[ ] /* RoundDownToGrid */ + ROUND[01] /* Round */ + RTG[ ] /* RoundToGrid */ + WCVTP[ ] /* WriteCVTInPixels */ + PUSHB[ ] /* 3 values pushed */ + 63 26 1 + DELTAC1[ ] /* DeltaExceptionC1 */ + PUSHB[ ] /* 3 values pushed */ + 95 26 1 + DELTAC1[ ] /* DeltaExceptionC1 */ + PUSHB[ ] /* 3 values pushed */ + 127 26 1 + DELTAC1[ ] /* DeltaExceptionC1 */ + PUSHB[ ] /* 3 values pushed */ + 47 26 1 + DELTAC2[ ] /* DeltaExceptionC2 */ + PUSHB[ ] /* 3 values pushed */ + 79 26 1 + DELTAC2[ ] /* DeltaExceptionC2 */ + PUSHB[ ] /* 3 values pushed */ + 111 26 1 + DELTAC2[ ] /* DeltaExceptionC2 */ + PUSHB[ ] /* 3 values pushed */ + 143 26 1 + DELTAC2[ ] /* DeltaExceptionC2 */ + PUSHB[ ] /* 3 values pushed */ + 175 26 1 + DELTAC2[ ] /* DeltaExceptionC2 */ + PUSHB[ ] /* 3 values pushed */ + 255 26 1 + DELTAC2[ ] /* DeltaExceptionC2 */ + PUSHB[ ] /* 3 values pushed */ + 31 26 1 + DELTAC3[ ] /* DeltaExceptionC3 */ + PUSHB[ ] /* 3 values pushed */ + 63 26 1 + DELTAC3[ ] /* DeltaExceptionC3 */ + PUSHB[ ] /* 3 values pushed */ + 95 26 1 + DELTAC3[ ] /* DeltaExceptionC3 */ + PUSHB[ ] /* 3 values pushed */ + 127 26 1 + DELTAC3[ ] /* DeltaExceptionC3 */ + PUSHB[ ] /* 3 values pushed */ + 15 30 1 + DELTAC1[ ] /* DeltaExceptionC1 */ + PUSHB[ ] /* 3 values pushed */ + 127 30 1 + DELTAC1[ ] /* DeltaExceptionC1 */ + PUSHB[ ] /* 3 values pushed */ + 239 30 1 + DELTAC1[ ] /* DeltaExceptionC1 */ + PUSHB[ ] /* 3 values pushed */ + 31 30 1 + DELTAC2[ ] /* DeltaExceptionC2 */ + PUSHB[ ] /* 3 values pushed */ + 95 30 1 + DELTAC2[ ] /* DeltaExceptionC2 */ + PUSHB[ ] /* 3 values pushed */ + 143 30 1 + DELTAC2[ ] /* DeltaExceptionC2 */ + PUSHB[ ] /* 3 values pushed */ + 207 30 1 + DELTAC2[ ] /* DeltaExceptionC2 */ + PUSHB[ ] /* 3 values pushed */ + 255 30 1 + DELTAC2[ ] /* DeltaExceptionC2 */ + PUSHB[ ] /* 3 values pushed */ + 63 30 1 + DELTAC3[ ] /* DeltaExceptionC3 */ + PUSHB[ ] /* 3 values pushed */ + 111 30 1 + DELTAC3[ ] /* DeltaExceptionC3 */ + PUSHB[ ] /* 3 values pushed */ + 47 32 1 + DELTAC1[ ] /* DeltaExceptionC1 */ + PUSHB[ ] /* 3 values pushed */ + 111 32 1 + DELTAC1[ ] /* DeltaExceptionC1 */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PUSHB[ ] /* 3 values pushed */ + 12 16 17 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 1 value pushed */ + 12 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 1 value pushed */ + 0 + MDRP[10000] /* MoveDirectRelPt */ + PUSHB[ ] /* 1 value pushed */ + 12 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 1 value pushed */ + 6 + MDRP[10000] /* MoveDirectRelPt */ + PUSHB[ ] /* 1 value pushed */ + 12 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 1 value pushed */ + 9 + MDRP[10000] /* MoveDirectRelPt */ + PUSHB[ ] /* 1 value pushed */ + 12 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 1 value pushed */ + 13 + MDRP[10000] /* MoveDirectRelPt */ + SVTCA[0] /* SetFPVectorToAxis */ + PUSHB[ ] /* 1 value pushed */ + 0 + RCVT[ ] /* ReadCVT */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 2 + MDAP[1] /* MoveDirectAbsPt */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 2 values pushed */ + 2 30 + MIAP[0] /* MoveIndirectAbsPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 0 + RCVT[ ] /* ReadCVT */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 0 + MDAP[1] /* MoveDirectAbsPt */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 2 values pushed */ + 0 18 + MIAP[0] /* MoveIndirectAbsPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 3 values pushed */ + 4 2 0 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 3 values pushed */ + 5 2 0 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 3 values pushed */ + 7 2 0 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 3 values pushed */ + 8 2 0 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 2 values pushed */ + 10 12 + MIRP[10100] /* MoveIndirectRelPt */ + PUSHB[ ] /* 3 values pushed */ + 12 2 0 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 3 values pushed */ + 13 2 0 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 1 value pushed */ + 2 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 2 values pushed */ + 14 12 + MIRP[10100] /* MoveIndirectRelPt */ + IUP[0] /* InterpolateUntPts */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SVTCA[0] /* SetFPVectorToAxis */ + PUSHB[ ] /* 1 value pushed */ + 0 + RCVT[ ] /* ReadCVT */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 8 + MDAP[1] /* MoveDirectAbsPt */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 2 values pushed */ + 8 28 + MIAP[0] /* MoveIndirectAbsPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 0 + RCVT[ ] /* ReadCVT */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 16 + MDAP[1] /* MoveDirectAbsPt */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 2 values pushed */ + 16 18 + MIAP[0] /* MoveIndirectAbsPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 3 values pushed */ + 2 16 8 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 1 value pushed */ + 2 + MDAP[1] /* MoveDirectAbsPt */ + PUSHB[ ] /* 1 value pushed */ + 16 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 2 values pushed */ + 17 1 + PUSHB[ ] /* 1 value pushed */ + 10 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + MDRP[11000] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MIRP[10100] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 2 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 2 values pushed */ + 20 1 + PUSHB[ ] /* 1 value pushed */ + 10 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + MDRP[11000] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MIRP[10100] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 8 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 2 values pushed */ + 27 1 + PUSHB[ ] /* 1 value pushed */ + 10 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + MDRP[11000] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MIRP[10100] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + IUP[0] /* InterpolateUntPts */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PUSHB[ ] /* 3 values pushed */ + 26 30 31 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + SVTCA[0] /* SetFPVectorToAxis */ + PUSHB[ ] /* 1 value pushed */ + 0 + RCVT[ ] /* ReadCVT */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 1 + MDAP[1] /* MoveDirectAbsPt */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 2 values pushed */ + 1 28 + MIAP[0] /* MoveIndirectAbsPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 0 + RCVT[ ] /* ReadCVT */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 13 + MDAP[1] /* MoveDirectAbsPt */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 2 values pushed */ + 13 18 + MIAP[0] /* MoveIndirectAbsPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 1 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 2 values pushed */ + 3 1 + PUSHB[ ] /* 1 value pushed */ + 10 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + MDRP[11000] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MIRP[10100] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 3 values pushed */ + 7 1 13 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 1 value pushed */ + 7 + MDAP[1] /* MoveDirectAbsPt */ + PUSHB[ ] /* 2 values pushed */ + 26 1 + PUSHB[ ] /* 1 value pushed */ + 10 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + MDRP[11000] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MIRP[10100] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 3 values pushed */ + 5 7 26 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 1 value pushed */ + 13 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 1 value pushed */ + 17 + MDRP[10000] /* MoveDirectRelPt */ + PUSHB[ ] /* 1 value pushed */ + 13 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 2 values pushed */ + 20 1 + PUSHB[ ] /* 1 value pushed */ + 10 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + MDRP[11000] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MIRP[10100] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 7 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 1 value pushed */ + 29 + MDRP[10000] /* MoveDirectRelPt */ + IUP[0] /* InterpolateUntPts */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + PUSHB[ ] /* 3 values pushed */ + 14 15 16 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 1 value pushed */ + 14 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 1 value pushed */ + 9 + MDRP[10000] /* MoveDirectRelPt */ + SVTCA[0] /* SetFPVectorToAxis */ + PUSHB[ ] /* 1 value pushed */ + 0 + RCVT[ ] /* ReadCVT */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 9 + MDAP[1] /* MoveDirectAbsPt */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 2 values pushed */ + 9 28 + MIAP[0] /* MoveIndirectAbsPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 0 + RCVT[ ] /* ReadCVT */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 4 + MDAP[1] /* MoveDirectAbsPt */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 2 values pushed */ + 4 18 + MIAP[0] /* MoveIndirectAbsPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 3 values pushed */ + 1 9 4 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 1 value pushed */ + 1 + MDAP[1] /* MoveDirectAbsPt */ + PUSHB[ ] /* 2 values pushed */ + 2 1 + PUSHB[ ] /* 1 value pushed */ + 10 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + MDRP[11000] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MIRP[10100] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 6 + MDRP[10000] /* MoveDirectRelPt */ + PUSHB[ ] /* 1 value pushed */ + 1 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 1 value pushed */ + 11 + MDRP[10000] /* MoveDirectRelPt */ + PUSHB[ ] /* 3 values pushed */ + 13 9 4 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IUP[0] /* InterpolateUntPts */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PUSHB[ ] /* 3 values pushed */ + 32 39 40 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + SVTCA[0] /* SetFPVectorToAxis */ + PUSHB[ ] /* 1 value pushed */ + 0 + RCVT[ ] /* ReadCVT */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 14 + MDAP[1] /* MoveDirectAbsPt */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 2 values pushed */ + 14 28 + MIAP[0] /* MoveIndirectAbsPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 0 + RCVT[ ] /* ReadCVT */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 25 + MDAP[1] /* MoveDirectAbsPt */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 2 values pushed */ + 25 18 + MIAP[0] /* MoveIndirectAbsPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 3 values pushed */ + 1 14 25 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 1 value pushed */ + 1 + MDAP[1] /* MoveDirectAbsPt */ + PUSHB[ ] /* 3 values pushed */ + 191 1 1 + DELTAP1[ ] /* DeltaExceptionP1 */ + PUSHB[ ] /* 5 values pushed */ + 175 1 191 1 2 + DELTAP2[ ] /* DeltaExceptionP2 */ + PUSHB[ ] /* 5 values pushed */ + 223 1 239 1 2 + DELTAP1[ ] /* DeltaExceptionP1 */ + PUSHB[ ] /* 5 values pushed */ + 31 1 47 1 2 + DELTAP1[ ] /* DeltaExceptionP1 */ + PUSHB[ ] /* 5 values pushed */ + 111 1 127 1 2 + DELTAP3[ ] /* DeltaExceptionP3 */ + PUSHB[ ] /* 1 value pushed */ + 14 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 2 values pushed */ + 7 1 + PUSHB[ ] /* 1 value pushed */ + 10 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + MDRP[11000] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MIRP[10100] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 14 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 1 value pushed */ + 10 + MDRP[10000] /* MoveDirectRelPt */ + PUSHB[ ] /* 1 value pushed */ + 1 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 2 values pushed */ + 37 1 + PUSHB[ ] /* 1 value pushed */ + 10 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + MDRP[11000] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MIRP[10100] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 3 values pushed */ + 20 37 1 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 1 value pushed */ + 25 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 1 value pushed */ + 29 + MDRP[10000] /* MoveDirectRelPt */ + PUSHB[ ] /* 1 value pushed */ + 25 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 2 values pushed */ + 32 1 + PUSHB[ ] /* 1 value pushed */ + 10 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + MDRP[11000] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MIRP[10100] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + IUP[0] /* InterpolateUntPts */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PUSHB[ ] /* 3 values pushed */ + 9 25 26 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + SVTCA[0] /* SetFPVectorToAxis */ + PUSHB[ ] /* 1 value pushed */ + 0 + RCVT[ ] /* ReadCVT */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 16 + MDAP[1] /* MoveDirectAbsPt */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 2 values pushed */ + 16 28 + MIAP[0] /* MoveIndirectAbsPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 0 + RCVT[ ] /* ReadCVT */ + IF[ ] /* If */ + PUSHB[ ] /* 1 value pushed */ + 0 + MDAP[1] /* MoveDirectAbsPt */ + ELSE[ ] /* Else */ + PUSHB[ ] /* 2 values pushed */ + 0 18 + MIAP[0] /* MoveIndirectAbsPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 2 values pushed */ + 23 1 + PUSHB[ ] /* 1 value pushed */ + 10 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + MDRP[11000] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MIRP[10100] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 2 + MDRP[10000] /* MoveDirectRelPt */ + PUSHB[ ] /* 3 values pushed */ + 3 16 0 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + PUSHB[ ] /* 1 value pushed */ + 16 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 2 values pushed */ + 9 1 + PUSHB[ ] /* 1 value pushed */ + 10 + CALL[ ] /* CallFunction */ + IF[ ] /* If */ + POP[ ] /* PopTopStack */ + MDRP[11000] /* MoveDirectRelPt */ + ELSE[ ] /* Else */ + MIRP[10100] /* MoveIndirectRelPt */ + EIF[ ] /* EndIf */ + PUSHB[ ] /* 1 value pushed */ + 16 + SRP0[ ] /* SetRefPoint0 */ + PUSHB[ ] /* 1 value pushed */ + 12 + MDRP[10000] /* MoveDirectRelPt */ + PUSHB[ ] /* 3 values pushed */ + 22 0 16 + SRP1[ ] /* SetRefPoint1 */ + SRP2[ ] /* SetRefPoint2 */ + IP[ ] /* InterpolatePts */ + IUP[0] /* InterpolateUntPts */ + IUP[1] /* InterpolateUntPts */ + + + + + + + + + Copyright 2011 Google Inc. All Rights Reserved. + + + Roboto + + + Regular + + + Roboto + + + Roboto + + + Version 2.137; 2017 + + + Roboto-Regular + + + + + + + + + + + + + + + + diff --git a/tests/ttx/clear.sh b/tests/ttx/clear.sh new file mode 100755 index 00000000..d7ba5d08 --- /dev/null +++ b/tests/ttx/clear.sh @@ -0,0 +1,2 @@ +rm ./*.otf +rm ./*_ref.ttx \ No newline at end of file