ttf-parser-0.3.0/.gitignore010064400017500001750000000000541354314235500140160ustar0000000000000000/target **/*.rs.bk Cargo.lock /.idea /*.iml ttf-parser-0.3.0/.travis.yml010064400017500001750000000005371354314235500141450ustar0000000000000000language: rust rust: - 1.35.0 - stable - nightly script: - cargo build --no-default-features --verbose --all - cargo build --verbose --all - cargo test --verbose --all - if [ $TRAVIS_RUST_VERSION == "nightly" ]; then env RUSTFLAGS="-Z sanitizer=address" cargo +nightly test --test tests --target x86_64-unknown-linux-gnu; fi ttf-parser-0.3.0/CHANGELOG.md010064400017500001750000000027441354314235500136470ustar0000000000000000# Change Log All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ## [0.3.0] - 2019-09-26 ### Added - `no_std` compatibility. ### Changed - The library has one `unsafe` block now. - 35% faster `family_name()` method. - 25% faster `from_data()` method for TrueType fonts. - The `Name` struct has a new API. Public fields became public functions and data is parsed on demand and not beforehand. ## [0.2.2] - 2019-08-12 ### Fixed - Allow format 12 subtables with *Unicode full repertoire* in `cmap`. ## [0.2.1] - 2019-08-12 ### Fixed - Check that `cmap` subtable encoding is Unicode. ## [0.2.0] - 2019-07-10 ### Added - CFF support. - Basic kerning support. - All `cmap` subtable formats except Mixed Coverage (8) are supported. - Vertical metrics querying from the `vmtx` table. - OpenType fonts are allowed now. ### Changed - A major rewrite. TrueType tables are no longer public. - Use `GlyphId` instead of `u16`. ### Removed - `GDEF` table parsing. [Unreleased]: https://github.com/RazrFalcon/ttf-parser/compare/v0.3.0...HEAD [0.3.0]: https://github.com/RazrFalcon/ttf-parser/compare/v0.2.2...v0.3.0 [0.2.2]: https://github.com/RazrFalcon/ttf-parser/compare/v0.2.1...v0.2.2 [0.2.1]: https://github.com/RazrFalcon/ttf-parser/compare/v0.2.0...v0.2.1 [0.2.0]: https://github.com/RazrFalcon/ttf-parser/compare/v0.1.0...v0.2.0 ttf-parser-0.3.0/Cargo.toml.orig010064400017500001750000000013761354314235500147250ustar0000000000000000[package] name = "ttf-parser" version = "0.3.0" authors = ["Evgeniy Reizner "] keywords = ["ttf", "truetype", "opentype"] categories = ["parser-implementations"] license = "MIT/Apache-2.0" description = "A high-level, safe, zero-allocation TrueType font parser." repository = "https://github.com/RazrFalcon/ttf-parser" documentation = "https://docs.rs/ttf-parser/" readme = "README.md" edition = "2018" exclude = ["fonts/**"] [features] default = ["std"] std = [] [dev-dependencies] bencher = "0.1" svgtypes = "0.5" time = "0.1" xmlwriter = "0.1" [[bench]] name = "outline" harness = false [[bench]] name = "methods_perf" harness = false [[bench]] name = "methods_perf_x1000" harness = false [package.metadata.docs.rs] features = ["std"] ttf-parser-0.3.0/Cargo.toml0000644000000025450000000000000111660ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "ttf-parser" version = "0.3.0" authors = ["Evgeniy Reizner "] exclude = ["fonts/**"] description = "A high-level, safe, zero-allocation TrueType font parser." documentation = "https://docs.rs/ttf-parser/" readme = "README.md" keywords = ["ttf", "truetype", "opentype"] categories = ["parser-implementations"] license = "MIT/Apache-2.0" repository = "https://github.com/RazrFalcon/ttf-parser" [package.metadata.docs.rs] features = ["std"] [[bench]] name = "outline" harness = false [[bench]] name = "methods_perf" harness = false [[bench]] name = "methods_perf_x1000" harness = false [dev-dependencies.bencher] version = "0.1" [dev-dependencies.svgtypes] version = "0.5" [dev-dependencies.time] version = "0.1" [dev-dependencies.xmlwriter] version = "0.1" [features] default = ["std"] std = [] ttf-parser-0.3.0/LICENSE-APACHE010064400017500001750000000251371354314235500137630ustar0000000000000000 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. ttf-parser-0.3.0/LICENSE-MIT010064400017500001750000000020441354314235500134630ustar0000000000000000Copyright (c) 2018 Evgeniy 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. ttf-parser-0.3.0/README.md010064400017500001750000000210621354314235500133070ustar0000000000000000## ttf-parser [![Build Status](https://travis-ci.org/RazrFalcon/ttf-parser.svg?branch=master)](https://travis-ci.org/RazrFalcon/ttf-parser) [![Crates.io](https://img.shields.io/crates/v/ttf-parser.svg)](https://crates.io/crates/ttf-parser) [![Documentation](https://docs.rs/ttf-parser/badge.svg)](https://docs.rs/ttf-parser) [![Rust 1.35+](https://img.shields.io/badge/rust-1.35+-orange.svg)](https://www.rust-lang.org) A high-level, safe, zero-allocation TrueType font parser. ### Features - A high-level API. - Zero allocations. - Zero dependencies. - `no_std` compatible. - Fast. - Stateless. - Simple and maintainable code (no magic numbers). ### Supported TrueType features - (`cmap`) Character to glyph index mapping using [glyph_index()] method.
All subtable formats except Mixed Coverage (8) are supported. - (`cmap`) Character variation to glyph index mapping using [glyph_variation_index()] method. - (`glyf`) Glyph outlining using [outline_glyph()] method. - (`hmtx`) Retrieving a glyph's horizontal metrics using [glyph_hor_metrics()] method. - (`vmtx`) Retrieving a glyph's vertical metrics using [glyph_ver_metrics()] method. - (`kern`) Retrieving a glyphs pair kerning using [glyphs_kerning()] method. - (`maxp`) Retrieving a total number of glyphs using [number_of_glyphs()] method. - (`name`) Listing all name records using [names()] method. - (`name`) Retrieving a font's family name using [family_name()] method. - (`name`) Retrieving a font's PostScript name using [post_script_name()] method. - (`post`) Retrieving a font's underline metrics name using [underline_metrics()] method. - (`head`) Retrieving a font's units per EM value using [units_per_em()] method. - (`hhea`) Retrieving a generic font info using: [ascender()], [descender()], [height()] and [line_gap()] methods. [glyph_index()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyph_index [glyph_variation_index()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyph_variation_index [outline_glyph()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.outline_glyph [glyph_hor_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyph_hor_metrics [glyph_ver_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyph_ver_metrics [glyphs_kerning()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyphs_kerning [number_of_glyphs()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.number_of_glyphs [names()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.names [family_name()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.family_name [post_script_name()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.post_script_name [underline_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.underline_metrics [units_per_em()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.units_per_em [ascender()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.ascender [descender()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.descender [height()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.height [line_gap()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.line_gap ### Supported OpenType features - (`CFF `) Glyph outlining using [outline_glyph()] method. - (`OS/2`) Retrieving a font kind using [is_regular()], [is_italic()], [is_bold()] and [is_oblique()] methods. - (`OS/2`) Retrieving a font's weight using [weight()] method. - (`OS/2`) Retrieving a font's width using [width()] method. - (`OS/2`) Retrieving a font's X height using [x_height()] method. - (`OS/2`) Retrieving a font's strikeout metrics using [strikeout_metrics()] method. - (`OS/2`) Retrieving a font's subscript metrics using [subscript_metrics()] method. - (`OS/2`) Retrieving a font's superscript metrics using [superscript_metrics()] method. [is_regular()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.is_regular [is_italic()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.is_italic [is_bold()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.is_bold [is_oblique()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.is_oblique [weight()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.weight [width()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.width [x_height()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.x_height [strikeout_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.strikeout_metrics [subscript_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.subscript_metrics [superscript_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.superscript_metrics ### Methods' computational complexity TrueType fonts designed for fast querying, so most of the methods are very fast. The main exception is glyph outlining. Glyphs can be stored using two different methods: using [Glyph Data](https://docs.microsoft.com/en-us/typography/opentype/spec/glyf) format and [Compact Font Format](http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5176.CFF.pdf) (pdf). The first one is fairly simple which makes it faster to process. The second one is basically a tiny language with a stack-based VM, which makes it way harder to process. Currently, it takes 60% more time to outline all glyphs in *SourceSansPro-Regular.otf* (which uses CFF) rather than in *SourceSansPro-Regular.ttf*. ``` test outline_cff ... bench: 1,617,138 ns/iter (+/- 1,261) test outline_glyf ... bench: 995,771 ns/iter (+/- 2,801) ``` Here is some methods benchmarks: ``` test outline_glyph_276_from_cff ... bench: 1,203 ns/iter (+/- 4) test outline_glyph_276_from_glyf ... bench: 796 ns/iter (+/- 3) test outline_glyph_8_from_cff ... bench: 497 ns/iter (+/- 3) test from_data_otf ... bench: 372 ns/iter (+/- 5) test outline_glyph_8_from_glyf ... bench: 347 ns/iter (+/- 1) test family_name ... bench: 269 ns/iter (+/- 3) test from_data_ttf ... bench: 72 ns/iter (+/- 3) test glyph_index_u41 ... bench: 24 ns/iter (+/- 0) test glyph_2_hor_metrics ... bench: 8 ns/iter (+/- 0) ``` `family_name` is expensive, because it allocates a `String` and the original data is stored as UTF-16 BE. Some methods are too fast, so we execute them **1000 times** to get better measurements. ``` test x_height ... bench: 847 ns/iter (+/- 0) test units_per_em ... bench: 564 ns/iter (+/- 2) test strikeout_metrics ... bench: 564 ns/iter (+/- 0) test width ... bench: 287 ns/iter (+/- 0) test ascender ... bench: 279 ns/iter (+/- 1) test subscript_metrics ... bench: 279 ns/iter (+/- 0) ``` ### Safety - The library must not panic. Any panic considered as a critical bug and should be reported. - The library has a single `unsafe` block for array casting. ### Alternatives - [font-rs](https://crates.io/crates/font-rs) - Mainly a glyph outline extractor. No documentation. Has less features. Doesn't support CFF. Has a lot of magic numbers. - [stb_truetype](https://crates.io/crates/stb_truetype) - Mainly a glyph outline extractor. Isn't allocation free. Has less features. Doesn't support CFF. Has a lot of magic numbers. Uses `panic` a lot. - [truetype](https://crates.io/crates/truetype) - Simply maps TrueType data to the Rust structures. Doesn't actually parses the data. Isn't allocation free. Has some **unsafe**. Unmaintained. - [font](https://github.com/pdf-rs/font) - Very similar to `ttf-parser`, but supports less features. Still an alpha. Isn't allocation free. - [fontdue](https://github.com/mooman219/fontdue) - Parser and rasterizer. In alpha state. Allocates all the required data. Doesn't support CFF. (revised on 2019-09-24) ### License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. ttf-parser-0.3.0/README.tpl010064400017500001750000000034461354314235500135140ustar0000000000000000## {{crate}} [![Build Status](https://travis-ci.org/RazrFalcon/{{crate}}.svg?branch=master)](https://travis-ci.org/RazrFalcon/{{crate}}) [![Crates.io](https://img.shields.io/crates/v/{{crate}}.svg)](https://crates.io/crates/{{crate}}) [![Documentation](https://docs.rs/{{crate}}/badge.svg)](https://docs.rs/{{crate}}) [![Rust 1.35+](https://img.shields.io/badge/rust-1.35+-orange.svg)](https://www.rust-lang.org) {{readme}} ### Alternatives - [font-rs](https://crates.io/crates/font-rs) - Mainly a glyph outline extractor. No documentation. Has less features. Doesn't support CFF. Has a lot of magic numbers. - [stb_truetype](https://crates.io/crates/stb_truetype) - Mainly a glyph outline extractor. Isn't allocation free. Has less features. Doesn't support CFF. Has a lot of magic numbers. Uses `panic` a lot. - [truetype](https://crates.io/crates/truetype) - Simply maps TrueType data to the Rust structures. Doesn't actually parses the data. Isn't allocation free. Has some **unsafe**. Unmaintained. - [font](https://github.com/pdf-rs/font) - Very similar to `ttf-parser`, but supports less features. Still an alpha. Isn't allocation free. - [fontdue](https://github.com/mooman219/fontdue) - Parser and rasterizer. In alpha state. Allocates all the required data. Doesn't support CFF. (revised on 2019-09-24) ### License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. ttf-parser-0.3.0/benches/methods_perf.rs010064400017500001750000000065001354314235500164640ustar0000000000000000use ttf_parser as ttf; fn from_data_ttf(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); bencher.iter(|| { bencher::black_box(ttf::Font::from_data(&font_data, 0).unwrap()); }) } fn from_data_otf(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.otf").unwrap(); bencher.iter(|| { bencher::black_box(ttf::Font::from_data(&font_data, 0).unwrap()); }) } fn outline_glyph_8_from_glyf(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); let font = ttf::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { font.outline_glyph(ttf::GlyphId(8), &mut Builder(0)) }) } fn outline_glyph_276_from_glyf(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); let font = ttf::Font::from_data(&font_data, 0).unwrap(); let mut b = Builder(0); bencher.iter(|| { font.outline_glyph(ttf::GlyphId(276), &mut b) }) } fn outline_glyph_8_from_cff(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.otf").unwrap(); let font = ttf::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { font.outline_glyph(ttf::GlyphId(8), &mut Builder(0)) }) } fn outline_glyph_276_from_cff(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.otf").unwrap(); let font = ttf::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { font.outline_glyph(ttf::GlyphId(276), &mut Builder(0)) }) } fn family_name(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); let font = ttf::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { bencher::black_box(font.family_name()); }) } fn glyph_index_u41(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); let font = ttf::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { bencher::black_box(font.glyph_index('A').unwrap()); }) } fn glyph_2_hor_metrics(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); let font = ttf::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { bencher::black_box(font.glyph_hor_metrics(ttf::GlyphId(2)).unwrap()); }) } struct Builder(usize); impl ttf_parser::OutlineBuilder for Builder { #[inline] fn move_to(&mut self, _: f32, _: f32) { self.0 += 1; } #[inline] fn line_to(&mut self, _: f32, _: f32) { self.0 += 1; } #[inline] fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) { self.0 += 2; } #[inline] fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) { self.0 += 3; } #[inline] fn close(&mut self) { self.0 += 1; } } bencher::benchmark_group!(perf, from_data_ttf, from_data_otf, outline_glyph_8_from_glyf, outline_glyph_276_from_glyf, outline_glyph_8_from_cff, outline_glyph_276_from_cff, family_name, glyph_index_u41, glyph_2_hor_metrics ); bencher::benchmark_main!(perf); ttf-parser-0.3.0/benches/methods_perf_x1000.rs010064400017500001750000000040421354314235500173130ustar0000000000000000use ttf_parser as ttf; fn units_per_em(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); let font = ttf::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { for _ in 0..1000 { bencher::black_box(font.units_per_em()); } }) } fn width(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); let font = ttf::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { for _ in 0..1000 { bencher::black_box(font.width()); } }) } fn ascender(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); let font = ttf::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { for _ in 0..1000 { bencher::black_box(font.ascender()); } }) } fn strikeout_metrics(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); let font = ttf::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { for _ in 0..1000 { bencher::black_box(font.strikeout_metrics().unwrap()); } }) } fn subscript_metrics(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); let font = ttf::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { for _ in 0..1000 { bencher::black_box(font.subscript_metrics().unwrap()); } }) } fn x_height(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); let font = ttf::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { for _ in 0..1000 { bencher::black_box(font.x_height().unwrap()); } }) } bencher::benchmark_group!(perf, units_per_em, width, ascender, strikeout_metrics, subscript_metrics, x_height ); bencher::benchmark_main!(perf); ttf-parser-0.3.0/benches/outline.rs010064400017500001750000000025421354314235500154660ustar0000000000000000fn outline_glyf(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); let font = ttf_parser::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { for id in 0..font.number_of_glyphs() { let _ = font.outline_glyph(ttf_parser::GlyphId(id), &mut Builder(0)); } }) } fn outline_cff(bencher: &mut bencher::Bencher) { let font_data = std::fs::read("fonts/SourceSansPro-Regular.otf").unwrap(); let font = ttf_parser::Font::from_data(&font_data, 0).unwrap(); bencher.iter(|| { for id in 0..font.number_of_glyphs() { let _ = font.outline_glyph(ttf_parser::GlyphId(id), &mut Builder(0)); } }) } struct Builder(usize); impl ttf_parser::OutlineBuilder for Builder { #[inline] fn move_to(&mut self, _: f32, _: f32) { self.0 += 1; } #[inline] fn line_to(&mut self, _: f32, _: f32) { self.0 += 1; } #[inline] fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) { self.0 += 2; } #[inline] fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) { self.0 += 3; } #[inline] fn close(&mut self) { self.0 += 1; } } bencher::benchmark_group!(outline_group, outline_glyf, outline_cff); bencher::benchmark_main!(outline_group); ttf-parser-0.3.0/examples/font-info.rs010064400017500001750000000030051354314235500161100ustar0000000000000000fn main() { if let Err(e) = process() { eprintln!("Error: {}.", e); std::process::exit(1); } } fn process() -> Result<(), Box> { let args: Vec<_> = std::env::args().collect(); if args.len() != 2 { println!("Usage:\n\tfont-info font.ttf"); std::process::exit(1); } let font_data = std::fs::read(&args[1])?; let start = time::precise_time_ns(); let font = ttf_parser::Font::from_data(&font_data, 0)?; println!("Family name: {:?}", font.family_name()); println!("PostScript name: {:?}", font.post_script_name()); println!("Units per EM: {:?}", font.units_per_em()); println!("Ascender: {}", font.ascender()); println!("Descender: {}", font.descender()); println!("Line gap: {}", font.line_gap()); println!("Number of glyphs: {}", font.number_of_glyphs()); println!("Underline: {:?}", font.underline_metrics()); println!("X height: {:?}", font.x_height()); println!("Weight: {:?}", font.weight()); println!("Width: {:?}", font.width()); println!("Regular: {}", font.is_regular()); println!("Italic: {}", font.is_italic()); println!("Bold: {}", font.is_bold()); println!("Oblique: {}", font.is_oblique()); println!("Strikeout: {:?}", font.strikeout_metrics()); println!("Subscript: {:?}", font.subscript_metrics()); println!("Superscript: {:?}", font.superscript_metrics()); let end = time::precise_time_ns(); println!("Elapsed: {}us", (end - start) / 1000); Ok(()) } ttf-parser-0.3.0/examples/font2svg.rs010064400017500001750000000130051354314235500157620ustar0000000000000000use ttf_parser as ttf; use svgtypes::WriteBuffer; const FONT_SIZE: f64 = 128.0; const COLUMNS: u32 = 50; fn main() { if let Err(e) = process() { eprintln!("Error: {}.", e); std::process::exit(1); } } fn process() -> Result<(), Box> { let args: Vec<_> = std::env::args().collect(); if args.len() != 3 { println!("Usage:\n\tfont2svg font.ttf out.svg"); std::process::exit(1); } let font_data = std::fs::read(&args[1])?; // Exclude IO operations. let start = time::precise_time_ns(); let font = ttf::Font::from_data(&font_data, 0)?; let units_per_em = font.units_per_em().ok_or("invalid units per em")?; let scale = FONT_SIZE / units_per_em as f64; let cell_size = font.height() as f64 * FONT_SIZE / units_per_em as f64; let rows = (font.number_of_glyphs() as f64 / COLUMNS as f64).ceil() as u32; let mut svg = xmlwriter::XmlWriter::with_capacity( font.number_of_glyphs() as usize * 512, xmlwriter::Options::default(), ); svg.start_element("svg"); svg.write_attribute("xmlns", "http://www.w3.org/2000/svg"); svg.write_attribute_fmt( "viewBox", format_args!("{} {} {} {}", 0, 0, cell_size * COLUMNS as f64, cell_size * rows as f64), ); draw_grid(font.number_of_glyphs(), cell_size, &mut svg); let mut path_buf = svgtypes::Path::with_capacity(64); let mut row = 0; let mut column = 0; for id in 0..font.number_of_glyphs() { glyph_to_path( column as f64 * cell_size, row as f64 * cell_size, &font, ttf::GlyphId(id), cell_size, scale, &mut svg, &mut path_buf, ); column += 1; if column == COLUMNS { column = 0; row += 1; } } let end = time::precise_time_ns(); println!("Elapsed: {:.2}ms", (end - start) as f64 / 1_000_000.0); std::fs::write(&args[2], &svg.end_document())?; Ok(()) } fn draw_grid( n_glyphs: u16, cell_size: f64, svg: &mut xmlwriter::XmlWriter, ) { let columns = COLUMNS; let rows = (n_glyphs as f64 / columns as f64).ceil() as u32; let width = columns as f64 * cell_size; let height = rows as f64 * cell_size; svg.start_element("path"); svg.write_attribute("fill", "none"); svg.write_attribute("stroke", "black"); svg.write_attribute("stroke-width", "5"); let mut path = svgtypes::Path::with_capacity(256); let mut x = 0.0; for _ in 0..=columns { path.push_move_to(x, 0.0); path.push_line_to(x, height); x += cell_size; } let mut y = 0.0; for _ in 0..=rows { path.push_move_to(0.0, y); path.push_line_to(width, y); y += cell_size; } svg.write_attribute_raw("d", |buf| path.write_buf(buf)); svg.end_element(); } fn glyph_to_path( x: f64, y: f64, font: &ttf::Font, glyph_id: ttf::GlyphId, cell_size: f64, scale: f64, svg: &mut xmlwriter::XmlWriter, path_buf: &mut svgtypes::Path, ) { svg.start_element("text"); svg.write_attribute("x", &(x + 2.0)); svg.write_attribute("y", &(y + cell_size - 4.0)); svg.write_attribute("font-size", "36"); svg.write_attribute("fill", "gray"); svg.write_text_fmt(format_args!("{}", glyph_id.0)); svg.end_element(); path_buf.clear(); let mut builder = Builder(path_buf); let bbox = match font.outline_glyph(glyph_id, &mut builder) { Ok(v) => v, Err(ttf::Error::NoOutline) => return, Err(ttf::Error::NoGlyph) => return, Err(e) => { eprintln!("Warning (glyph {}): {}.", glyph_id.0, e); return; } }; let metrics = match font.glyph_hor_metrics(glyph_id) { Ok(v) => v, Err(_) => return, }; let dx = (cell_size - metrics.advance as f64 * scale) / 2.0; let y = y + cell_size + font.descender() as f64 * scale; let mut ts = svgtypes::Transform::default(); ts.translate(x + dx, y); ts.scale(1.0, -1.0); ts.scale(scale, scale); svg.start_element("path"); svg.write_attribute_raw("d", |buf| path_buf.write_buf(buf)); svg.write_attribute_raw("transform", |buf| ts.write_buf(buf)); svg.end_element(); { let bbox_w = (bbox.x_max as f64 - bbox.x_min as f64) * scale; let bbox_h = (bbox.y_max as f64 - bbox.y_min as f64) * scale; let bbox_x = x + dx + bbox.x_min as f64 * scale; let bbox_y = y - bbox.y_min as f64 * scale - bbox_h; svg.start_element("rect"); svg.write_attribute("x", &bbox_x); svg.write_attribute("y", &bbox_y); svg.write_attribute("width", &bbox_w); svg.write_attribute("height", &bbox_h); svg.write_attribute("fill", "none"); svg.write_attribute("stroke", "green"); svg.end_element(); } } struct Builder<'a>(&'a mut svgtypes::Path); impl ttf::OutlineBuilder for Builder<'_> { fn move_to(&mut self, x: f32, y: f32) { self.0.push_move_to(x as f64, y as f64); } fn line_to(&mut self, x: f32, y: f32) { self.0.push_line_to(x as f64, y as f64); } fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { self.0.push_quad_to(x1 as f64, y1 as f64, x as f64, y as f64); } fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { self.0.push_curve_to(x1 as f64, y1 as f64, x2 as f64, y2 as f64, x as f64, y as f64); } fn close(&mut self) { self.0.push_close_path(); } } ttf-parser-0.3.0/scripts/gen-tables.py010075500017500001750000000411741354314235500161230ustar0000000000000000#!/usr/bin/env python3 import re from typing import List def to_snake_case(name: str) -> str: s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() class TTFType: def to_rust(self) -> str: raise NotImplementedError() def size(self) -> int: return 0 def print(self, offset: int) -> None: raise NotImplementedError() class TTF_UInt8(TTFType): def to_rust(self) -> str: return 'u8' def size(self) -> int: return 1 def print(self, offset: int) -> None: print(f'self.data[{offset}]') class TTF_UInt16(TTFType): def to_rust(self) -> str: return 'u16' def size(self) -> int: return 2 def print(self, offset: int) -> None: print(f'u16::from_be_bytes([self.data[{offset}], self.data[{offset + 1}]])') class TTF_Int16(TTFType): def to_rust(self) -> str: return 'i16' def size(self) -> int: return 2 def print(self, offset: int) -> None: print(f'i16::from_be_bytes([self.data[{offset}], self.data[{offset + 1}]])') class TTF_UInt24(TTFType): def to_rust(self) -> str: return 'u32' def size(self) -> int: return 3 def print(self, offset: int) -> None: print(f'(self.data[{offset}] as u32) << 16 | (self.data[{offset + 1}] as u32) << 8 ' f'| self.data[{offset + 2}] as u32') class TTF_UInt32(TTFType): def to_rust(self) -> str: return 'u32' def size(self) -> int: return 4 def print(self, offset: int) -> None: print(f'u32::from_be_bytes([' f' self.data[{offset}], self.data[{offset + 1}], self.data[{offset + 2}], self.data[{offset + 3}]' f'])') class TTF_FWORD(TTF_Int16): pass class TTF_UFWORD(TTF_UInt16): pass class TTF_Offset32(TTF_UInt32): pass class TTF_Optional_Offset32(TTF_UInt32): def to_rust(self) -> str: return 'Option' def size(self) -> int: return 4 def print(self, offset: int) -> None: print(f'let n = u32::from_be_bytes([' f' self.data[{offset}], self.data[{offset + 1}], self.data[{offset + 2}], self.data[{offset + 3}]' f']);') print('if n != 0 { Some(n) } else { None }') class TTF_GlyphId(TTFType): def to_rust(self) -> str: return 'GlyphId' def size(self) -> int: return 2 def print(self, offset: int) -> None: print(f'GlyphId(u16::from_be_bytes([self.data[{offset}], self.data[{offset + 1}]]))') class TTF_Tag(TTFType): def to_rust(self) -> str: return '[u8; 4]' def size(self) -> int: return 4 def print(self, offset: int) -> None: print('// Unwrap is safe, because an array and a slice have the same size.') print(f'self.data[{offset}..{offset + self.size()}].try_into().unwrap()') # unsupported class TTF_Fixed(TTFType): def size(self) -> int: return 4 # unsupported class TTF_LONGDATETIME(TTFType): def size(self) -> int: return 8 # unsupported class TTF_Panose(TTFType): def size(self) -> int: return 10 class TableRow: enable: bool ttf_type: TTFType name: str def __init__(self, enable: bool, ttf_type: TTFType, name: str): self.enable = enable self.ttf_type = ttf_type self.name = name # https://docs.microsoft.com/en-us/typography/opentype/spec/otff#ttc-header TTC_HEADER = [ TableRow(True, TTF_Tag(), 'ttcTag'), TableRow(False, TTF_UInt16(), 'majorVersion'), TableRow(False, TTF_UInt16(), 'minorVersion'), TableRow(True, TTF_UInt32(), 'numFonts'), # + offsetTable[numFonts] ] # https://docs.microsoft.com/en-us/typography/opentype/spec/otff#ttc-header TABLE_RECORD = [ TableRow(True, TTF_Tag(), 'tableTag'), TableRow(False, TTF_UInt32(), 'checkSum'), TableRow(True, TTF_Offset32(), 'offset'), TableRow(True, TTF_UInt32(), 'length'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/head HEAD_TABLE = [ TableRow(False, TTF_UInt16(), 'majorVersion'), TableRow(False, TTF_UInt16(), 'minorVersion'), TableRow(False, TTF_Fixed(), 'fontRevision'), TableRow(False, TTF_UInt32(), 'checkSumAdjustment'), TableRow(False, TTF_UInt32(), 'magicNumber'), TableRow(False, TTF_UInt16(), 'flags'), TableRow(True, TTF_UInt16(), 'unitsPerEm'), TableRow(False, TTF_LONGDATETIME(), 'created'), TableRow(False, TTF_LONGDATETIME(), 'modified'), TableRow(False, TTF_Int16(), 'xMin'), TableRow(False, TTF_Int16(), 'yMin'), TableRow(False, TTF_Int16(), 'xMax'), TableRow(False, TTF_Int16(), 'yMax'), TableRow(False, TTF_UInt16(), 'macStyle'), TableRow(False, TTF_UInt16(), 'lowestRecPPEM'), TableRow(False, TTF_Int16(), 'fontDirectionHint'), TableRow(True, TTF_Int16(), 'indexToLocFormat'), TableRow(False, TTF_Int16(), 'glyphDataFormat'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/hhea HHEA_TABLE = [ TableRow(False, TTF_UInt16(), 'majorVersion'), TableRow(False, TTF_UInt16(), 'minorVersion'), TableRow(True, TTF_FWORD(), 'ascender'), TableRow(True, TTF_FWORD(), 'descender'), TableRow(True, TTF_FWORD(), 'lineGap'), TableRow(False, TTF_UFWORD(), 'advanceWidthMax'), TableRow(False, TTF_FWORD(), 'minLeftSideBearing'), TableRow(False, TTF_FWORD(), 'minRightSideBearing'), TableRow(False, TTF_FWORD(), 'xMaxExtent'), TableRow(False, TTF_Int16(), 'caretSlopeRise'), TableRow(False, TTF_Int16(), 'caretSlopeRun'), TableRow(False, TTF_Int16(), 'caretOffset'), TableRow(False, TTF_Int16(), 'reserved'), TableRow(False, TTF_Int16(), 'reserved'), TableRow(False, TTF_Int16(), 'reserved'), TableRow(False, TTF_Int16(), 'reserved'), TableRow(False, TTF_Int16(), 'metricDataFormat'), TableRow(True, TTF_UInt16(), 'numberOfHMetrics'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx HMTX_METRICS = [ TableRow(True, TTF_UInt16(), 'advanceWidth'), TableRow(True, TTF_Int16(), 'lsb'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/vhea#table-format VHEA_TABLE = [ TableRow(False, TTF_Fixed(), 'version'), TableRow(False, TTF_Int16(), 'ascender'), TableRow(False, TTF_Int16(), 'descender'), TableRow(False, TTF_Int16(), 'lineGap'), TableRow(False, TTF_Int16(), 'advanceHeightMax'), TableRow(False, TTF_Int16(), 'minTopSideBearing'), TableRow(False, TTF_Int16(), 'minBottomSideBearing'), TableRow(False, TTF_Int16(), 'yMaxExtent'), TableRow(False, TTF_Int16(), 'caretSlopeRise'), TableRow(False, TTF_Int16(), 'caretSlopeRun'), TableRow(False, TTF_Int16(), 'caretOffset'), TableRow(False, TTF_Int16(), 'reserved'), TableRow(False, TTF_Int16(), 'reserved'), TableRow(False, TTF_Int16(), 'reserved'), TableRow(False, TTF_Int16(), 'reserved'), TableRow(False, TTF_Int16(), 'metricDataFormat'), TableRow(True, TTF_UInt16(), 'numOfLongVerMetrics'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/vmtx#vertical-metrics-table-format VMTX_METRICS = [ TableRow(True, TTF_UInt16(), 'advanceHeight'), TableRow(True, TTF_Int16(), 'topSideBearing'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/post#header POST_TABLE = [ TableRow(False, TTF_Fixed(), 'version'), TableRow(False, TTF_Fixed(), 'italicAngle'), TableRow(True, TTF_FWORD(), 'underlinePosition'), TableRow(True, TTF_FWORD(), 'underlineThickness'), TableRow(False, TTF_UInt32(), 'isFixedPitch'), TableRow(False, TTF_UInt32(), 'minMemType42'), TableRow(False, TTF_UInt32(), 'maxMemType42'), TableRow(False, TTF_UInt32(), 'minMemType1'), TableRow(False, TTF_UInt32(), 'maxMemType1'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-records NAME_RECORD_TABLE = [ TableRow(True, TTF_UInt16(), 'platformID'), TableRow(True, TTF_UInt16(), 'encodingID'), TableRow(True, TTF_UInt16(), 'languageID'), TableRow(True, TTF_UInt16(), 'nameID'), TableRow(True, TTF_UInt16(), 'length'), TableRow(True, TTF_UInt16(), 'offset'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#encoding-records-and-encodings CMAP_ENCODING_RECORD = [ TableRow(True, TTF_UInt16(), 'platformID'), TableRow(True, TTF_UInt16(), 'encodingID'), TableRow(True, TTF_Offset32(), 'offset'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-2-high-byte-mapping-through-table CMAP_SUB_HEADER_RECORD = [ TableRow(True, TTF_UInt16(), 'firstCode'), TableRow(True, TTF_UInt16(), 'entryCount'), TableRow(True, TTF_Int16(), 'idDelta'), TableRow(True, TTF_UInt16(), 'idRangeOffset'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-12-segmented-coverage CMAP_SEQUENTIAL_MAP_GROUP_RECORD = [ TableRow(True, TTF_UInt32(), 'startCharCode'), TableRow(True, TTF_UInt32(), 'endCharCode'), TableRow(True, TTF_UInt32(), 'startGlyphID'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#default-uvs-table CMAP_UNICODE_RANGE_RECORD = [ TableRow(True, TTF_UInt24(), 'startUnicodeValue'), TableRow(True, TTF_UInt8(), 'additionalCount'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#non-default-uvs-table CMAP_UVS_MAPPING_RECORD = [ TableRow(True, TTF_UInt24(), 'unicodeValue'), TableRow(True, TTF_GlyphId(), 'glyphID'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-14-unicode-variation-sequences CMAP_VARIATION_SELECTOR_RECORD = [ TableRow(True, TTF_UInt24(), 'varSelector'), TableRow(True, TTF_Optional_Offset32(), 'defaultUVSOffset'), TableRow(True, TTF_Optional_Offset32(), 'nonDefaultUVSOffset'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/maxp MAXP_TABLE = [ TableRow(False, TTF_Fixed(), 'version'), TableRow(True, TTF_UInt16(), 'numGlyphs'), ] # https://docs.microsoft.com/en-us/typography/opentype/spec/os2#os2-table-formats OS_2_TABLE_V0 = [ TableRow(True, TTF_UInt16(), 'version'), TableRow(False, TTF_Int16(), 'xAvgCharWidth'), TableRow(True, TTF_UInt16(), 'usWeightClass'), TableRow(True, TTF_UInt16(), 'usWidthClass'), TableRow(False, TTF_UInt16(), 'fsType'), TableRow(True, TTF_Int16(), 'ySubscriptXSize'), TableRow(True, TTF_Int16(), 'ySubscriptYSize'), TableRow(True, TTF_Int16(), 'ySubscriptXOffset'), TableRow(True, TTF_Int16(), 'ySubscriptYOffset'), TableRow(True, TTF_Int16(), 'ySuperscriptXSize'), TableRow(True, TTF_Int16(), 'ySuperscriptYSize'), TableRow(True, TTF_Int16(), 'ySuperscriptXOffset'), TableRow(True, TTF_Int16(), 'ySuperscriptYOffset'), TableRow(True, TTF_Int16(), 'yStrikeoutSize'), TableRow(True, TTF_Int16(), 'yStrikeoutPosition'), TableRow(False, TTF_Int16(), 'sFamilyClass'), TableRow(False, TTF_Panose(), 'panose'), TableRow(False, TTF_UInt32(), 'ulUnicodeRange1'), TableRow(False, TTF_UInt32(), 'ulUnicodeRange2'), TableRow(False, TTF_UInt32(), 'ulUnicodeRange3'), TableRow(False, TTF_UInt32(), 'ulUnicodeRange4'), TableRow(False, TTF_Tag(), 'achVendID'), TableRow(True, TTF_UInt16(), 'fsSelection'), TableRow(False, TTF_UInt16(), 'usFirstCharIndex'), TableRow(False, TTF_UInt16(), 'usLastCharIndex'), TableRow(False, TTF_Int16(), 'sTypoAscender'), TableRow(False, TTF_Int16(), 'sTypoDescender'), TableRow(False, TTF_Int16(), 'sTypoLineGap'), TableRow(False, TTF_UInt16(), 'usWinAscent'), TableRow(False, TTF_UInt16(), 'usWinDescent'), ] def print_struct(name: str, size: int, owned: bool) -> None: print('#[derive(Clone, Copy)]') if owned: print(f'pub struct {name} {{ data: [u8; {size}] }}') else: print(f'pub struct {name}<\'a> {{ data: &\'a [u8; {size}] }}') def print_struct_size(size: int) -> None: print(f'pub const SIZE: usize = {size};') def print_constructor(name: str, size: int, owned: bool) -> None: print('#[inline(always)]') if owned: print('pub fn new(input: &[u8]) -> Self {') print(' let mut data = [0u8; Self::SIZE];') # Do not use `copy_from_slice`, because it's slower. print(' data.clone_from_slice(input);') print(f' {name} {{ data }}') print('}') else: print('pub fn new(input: &\'a [u8]) -> Self {') print(f' {name} {{ data: array_ref![input, {size}] }}') print('}') def print_method(spec_name: str, ttf_type: TTFType, offset: int) -> None: fn_name = to_snake_case(spec_name) rust_type = ttf_type.to_rust() print(' #[inline(always)]') print(f' pub fn {fn_name}(&self) -> {rust_type} {{') ttf_type.print(offset) print(' }') def print_impl_from_data(name: str) -> None: print(f'impl FromData for {name} {{') print(f' const SIZE: usize = {name}::SIZE;') print() print(' #[inline]') print(' fn parse(data: &[u8]) -> Self {') print(' Self::new(data)') print(' }') print('}') # Structs smaller than 16 bytes is more efficient to store as owned. def generate_table(table: List[TableRow], struct_name: str, owned: bool = False, impl_from_data: bool = False) -> None: struct_size = 0 for row in table: struct_size += row.ttf_type.size() print_struct(struct_name, struct_size, owned) print() if owned: print(f'impl {struct_name} {{') else: print(f'impl<\'a> {struct_name}<\'a> {{') print_struct_size(struct_size) print() print_constructor(struct_name, struct_size, owned) print() offset = 0 for row in table: if not row.enable: offset += row.ttf_type.size() continue print_method(row.name, row.ttf_type, offset) print() offset += row.ttf_type.size() print('}') if impl_from_data: print() print_impl_from_data(struct_name) print('// This file is autogenerated by scripts/get-tables.py') print('// Do not edit it!') print() print('// By using static arrays we can have compile-time guaranties that') print('// we are not reading out-ouf-bounds.') print('// Also, it removes bounds-checking overhead.') print() print('// Based on https://github.com/droundy/arrayref') print('macro_rules! array_ref {') print(' ($arr:expr, $len:expr) => {{') print(' // Always check that the slice length is the same as `$len`.') print(' assert_eq!($arr.len(), $len);') print(' unsafe { &*($arr.as_ptr() as *const [_; $len]) }') print(' }}') print('}') print() print('use core::convert::TryInto;') print('use crate::parser::FromData;') print() generate_table(TTC_HEADER, 'TTCHeader') print() generate_table(TABLE_RECORD, 'TableRecord', owned=True, impl_from_data=True) print() print('pub mod head {') generate_table(HEAD_TABLE, 'Table') print('}') print() print('pub mod maxp {') generate_table(MAXP_TABLE, 'Table') print('}') print() print('pub mod hhea {') generate_table(HHEA_TABLE, 'Table') print('}') print() print('pub mod hmtx {') print('use crate::parser::FromData;') print() generate_table(HMTX_METRICS, 'HorizontalMetrics', owned=True, impl_from_data=True) print('}') print() print('pub mod vhea {') generate_table(VHEA_TABLE, 'Table') print('}') print() print('pub mod vmtx {') print('use crate::parser::FromData;') print() generate_table(VMTX_METRICS, 'VerticalMetrics', owned=True, impl_from_data=True) print('}') print() print('pub mod post {') generate_table(POST_TABLE, 'Table') print('}') print() print('pub mod cmap {') print('use crate::GlyphId;') print('use crate::parser::FromData;') print() generate_table(CMAP_ENCODING_RECORD, 'EncodingRecord', owned=True, impl_from_data=True) print() generate_table(CMAP_SUB_HEADER_RECORD, 'SubHeaderRecord', owned=True, impl_from_data=True) print() generate_table(CMAP_SEQUENTIAL_MAP_GROUP_RECORD, 'SequentialMapGroup', owned=True, impl_from_data=True) print() generate_table(CMAP_UNICODE_RANGE_RECORD, 'UnicodeRangeRecord', owned=True, impl_from_data=True) print() generate_table(CMAP_UVS_MAPPING_RECORD, 'UVSMappingRecord', owned=True, impl_from_data=True) print() generate_table(CMAP_VARIATION_SELECTOR_RECORD, 'VariationSelectorRecord', owned=True, impl_from_data=True) print('}') print() print('pub mod os_2 {') generate_table(OS_2_TABLE_V0, 'TableV0') print('}') print() print('pub mod name {') generate_table(NAME_RECORD_TABLE, 'NameRecord', owned=True) print('}') print() ttf-parser-0.3.0/scripts/gen-tables.sh010075500017500001750000000001651354314235500161000ustar0000000000000000#!/usr/bin/env bash set -e mypy --strict gen-tables.py python3 gen-tables.py > ../src/raw.rs rustfmt ../src/raw.rs ttf-parser-0.3.0/src/cff.rs010064400017500001750000001262051354314235500137300ustar0000000000000000// Useful links: // http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5176.CFF.pdf // http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5177.Type2.pdf // https://github.com/opentypejs/opentype.js/blob/master/src/tables/cff.js use core::ops::Range; use crate::parser::{Stream, TryFromData, SafeStream, TrySlice}; use crate::{Font, GlyphId, TableName, OutlineBuilder, Rect, Result, Error}; // Limits according to the Adobe Technical Note #5176, chapter 4 DICT Data. const MAX_OPERANDS_LEN: usize = 48; // Limits according to the Adobe Technical Note #5177 Appendix B. const STACK_LIMIT: u8 = 10; const MAX_ARGUMENTS_STACK_LEN: usize = 48; const TWO_BYTE_OPERATOR_MARK: u8 = 12; /// A list of errors that can occur during a CFF table parsing. #[derive(Clone, Copy, Debug)] pub enum CFFError { /// The CFF table doesn't have any char strings. NoCharStrings, /// An invalid operand occurred. InvalidOperand, /// An invalid operator occurred. InvalidOperator, /// An unsupported operator occurred. UnsupportedOperator, /// Failed to parse a float number. InvalidFloat, /// The `OffSize` value must be in 1..4 range. /// /// Adobe Technical Note #5176, Table 2 CFF Data Types InvalidOffsetSize, /// Subroutines nesting is limited by 10. /// /// Adobe Technical Note #5177 Appendix B. NestingLimitReached, /// An arguments stack size is limited by 48 values. /// /// Adobe Technical Note #5177 Appendix B. ArgumentsStackLimitReached, /// Each operand expects a specific amount of arguments on the stack. /// /// Usually indicates an implementation error and should not occur on valid fonts. InvalidArgumentsStackLength, } impl core::fmt::Display for CFFError { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { match *self { CFFError::NoCharStrings => { write!(f, "table doesn't have any char strings") } CFFError::InvalidOperand => { write!(f, "an invalid operand occurred") } CFFError::InvalidOperator => { write!(f, "an invalid operator occurred") } CFFError::UnsupportedOperator => { write!(f, "an unsupported operator occurred") } CFFError::InvalidFloat => { write!(f, "failed to parse a float number") } CFFError::InvalidOffsetSize => { write!(f, "OffSize with an invalid value occurred") } CFFError::NestingLimitReached => { write!(f, "subroutines nesting limit reached") } CFFError::ArgumentsStackLimitReached => { write!(f, "arguments stack limit reached") } CFFError::InvalidArgumentsStackLength => { write!(f, "an invalid amount of items are in an arguments stack") } } } } #[cfg(feature = "std")] impl std::error::Error for CFFError {} #[derive(Clone, Default)] pub struct Metadata { char_strings_offset: u32, private_dict_range: Option>, subroutines_offset: Option, global_subrs_offset: u32, } pub(crate) fn parse_metadata(data: &[u8]) -> Result { let mut s = Stream::new(data); // Parse Header. let major: u8 = s.read()?; s.skip::(); // minor let header_size: u8 = s.read()?; s.skip::(); // Absolute offset if major != 1 { return Err(Error::UnsupportedTableVersion(TableName::CompactFontFormat, major as u16)); } // Jump to Name INDEX. It's not necessarily right after the header. if header_size > s.offset() as u8 { s.skip_len(header_size as u32 - s.offset() as u32); } let mut metadata = Metadata::default(); // Skip Name INDEX. skip_index(&mut s)?; parse_top_dict(&mut s, &mut metadata)?; if let Some(range) = metadata.private_dict_range.clone() { let range = range.start as usize .. range.end as usize; let dict_data = data.try_slice(range)?; parse_private_dict(dict_data, &mut metadata)?; } // Skip String INDEX. skip_index(&mut s)?; // Global Subroutines INDEX offset. metadata.global_subrs_offset = s.offset() as u32; Ok(metadata) } impl<'a> Font<'a> { pub(crate) fn cff_glyph_outline( &self, glyph_id: GlyphId, builder: &mut impl OutlineBuilder, ) -> Result { let data = self.cff_.ok_or_else(|| Error::TableMissing(TableName::CompactFontFormat))?; let mut s = Stream::new_at(data, self.cff_metadata.global_subrs_offset as usize); // Parse Global Subroutines INDEX. let global_subrs = parse_index(&mut s)?; let mut local_subrs = DataIndex::create_empty(); match (self.cff_metadata.private_dict_range.clone(), self.cff_metadata.subroutines_offset.clone()) { (Some(private_dict_range), Some(subroutines_offset)) => { // 'The local subroutines offset is relative to the beginning // of the Private DICT data.' if let Some(start) = private_dict_range.start.checked_add(subroutines_offset) { let data = data.try_slice(start as usize..data.len())?; let mut s = Stream::new(data); local_subrs = parse_index(&mut s)?; } } _ => {} } let start = self.cff_metadata.char_strings_offset as usize; let mut s = Stream::new(data.try_slice(start..data.len())?); parse_char_string(global_subrs, local_subrs, glyph_id, &mut s, builder) } } fn parse_top_dict(s: &mut Stream, metadata: &mut Metadata) -> Result<()> { let index = parse_index(s)?; // The Top DICT INDEX should have only one dictionary. let data = match index.get(0) { Some(v) => v, None => return Err(CFFError::NoCharStrings.into()), }; let mut dict_parser = DictionaryParser::new(data); while let Some(operator) = dict_parser.parse_next() { // Adobe Technical Note #5176, Table 9 Top DICT Operator Entries match operator.value() { 17 => { dict_parser.parse_operands()?; let operands = dict_parser.operands(); if operands.len() == 1 { metadata.char_strings_offset = operands[0].as_i32() as u32; } } 18 => { dict_parser.parse_operands()?; let operands = dict_parser.operands(); if operands.len() == 2 { let start = operands[1].as_i32() as u32; let len = operands[0].as_i32() as u32; if let Some(end) = start.checked_add(len) { metadata.private_dict_range = Some(start..end); } } } _ => {} } if metadata.char_strings_offset != 0 && metadata.private_dict_range.is_some() { break; } } // `char_strings_offset` must be set, otherwise there are nothing to parse. if metadata.char_strings_offset == 0 { return Err(CFFError::NoCharStrings.into()); } Ok(()) } fn parse_private_dict(data: &[u8], metadata: &mut Metadata) -> Result<()> { let mut dict_parser = DictionaryParser::new(data); while let Some(operator) = dict_parser.parse_next() { // Adobe Technical Note #5176, Table 23 Private DICT Operators if operator.value() == 19 { dict_parser.parse_operands()?; let operands = dict_parser.operands(); if operands.len() == 1 { metadata.subroutines_offset = Some(operands[0].as_i32() as u32); } break; } } Ok(()) } struct CharStringParserContext<'a> { global_subrs: DataIndex<'a>, local_subrs: DataIndex<'a>, is_first_move_to: bool, width_parsed: bool, stems_len: u32, } fn parse_char_string( global_subrs: DataIndex, local_subrs: DataIndex, glyph_id: GlyphId, s: &mut Stream, builder: &mut impl OutlineBuilder, ) -> Result { let char_strings = parse_index(s)?; let data = char_strings.get(glyph_id.0).ok_or(Error::NoGlyph)?; let mut ctx = CharStringParserContext { global_subrs, local_subrs, is_first_move_to: true, width_parsed: false, stems_len: 0, }; let mut inner_builder = Builder { builder, bbox: RectF { x_min: core::f32::MAX, y_min: core::f32::MAX, x_max: core::f32::MIN, y_max: core::f32::MIN, } }; let mut stack = ArgumentsStack::new(); let _ = _parse_char_string(&mut ctx, data, 0.0, 0.0, &mut stack, 0, &mut inner_builder)?; let bbox = inner_builder.bbox; Ok(Rect { x_min: bbox.x_min as i16, y_min: bbox.y_min as i16, x_max: bbox.x_max as i16, y_max: bbox.y_max as i16, }) } struct RectF { pub x_min: f32, pub y_min: f32, pub x_max: f32, pub y_max: f32, } trait OutlineBuilderInner { fn update_bbox(&mut self, x: f32, y: f32); fn move_to(&mut self, x: f32, y: f32); fn line_to(&mut self, x: f32, y: f32); fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32); fn close(&mut self); } struct Builder<'a, T: OutlineBuilder> { builder: &'a mut T, bbox: RectF, } impl<'a, T: OutlineBuilder> OutlineBuilderInner for Builder<'a, T> { #[inline] fn update_bbox(&mut self, x: f32, y: f32) { self.bbox.x_min = self.bbox.x_min.min(x); self.bbox.y_min = self.bbox.y_min.min(y); self.bbox.x_max = self.bbox.x_max.max(x); self.bbox.y_max = self.bbox.y_max.max(y); } #[inline] fn move_to(&mut self, x: f32, y: f32) { self.update_bbox(x, y); self.builder.move_to(x, y); } #[inline] fn line_to(&mut self, x: f32, y: f32) { self.update_bbox(x, y); self.builder.line_to(x, y); } #[inline] fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { self.update_bbox(x1, y1); self.update_bbox(x2, y2); self.update_bbox(x, y); self.builder.curve_to(x1, y1, x2, y2, x, y); } #[inline] fn close(&mut self) { self.builder.close(); } } fn _parse_char_string( ctx: &mut CharStringParserContext, char_string: &[u8], mut x: f32, mut y: f32, stack: &mut ArgumentsStack, depth: u8, builder: &mut Builder, ) -> Result<(f32, f32)> { let mut s = Stream::new(char_string); while !s.at_end() { let op: u8 = s.read()?; match op { 0 | 2 | 9 | 13 | 15 | 16 | 17 => { // Reserved. return Err(CFFError::InvalidOperator.into()); } 1 | 3 | 18 | 23 => { // |- y dy {dya dyb}* hstem (1) |- // |- x dx {dxa dxb}* vstem (3) |- // |- y dy {dya dyb}* hstemhm (18) |- // |- x dx {dxa dxb}* vstemhm (23) |- // If the stack length is uneven, than the first value is a `width`. let len = if stack.len().is_odd() && !ctx.width_parsed { ctx.width_parsed = true; stack.len() - 1 } else { stack.len() }; ctx.stems_len += len as u32 >> 1; // We are ignoring the hint operators. stack.clear(); } 4 => { // |- dy1 vmoveto (4) |- let mut i = 0; if stack.len() == 2 && !ctx.width_parsed { i += 1; ctx.width_parsed = true; } else if stack.len() != 1 { return Err(CFFError::InvalidArgumentsStackLength.into()); } if ctx.is_first_move_to { ctx.is_first_move_to = false; } else { builder.close(); } y += stack.at(i); builder.move_to(x, y); stack.clear(); } 5 => { // |- {dxa dya}+ rlineto (5) |- if stack.len().is_odd() { return Err(CFFError::InvalidArgumentsStackLength.into()); } let mut i = 0; while i < stack.len() { x += stack.at(i + 0); y += stack.at(i + 1); builder.line_to(x, y); i += 2; } stack.clear(); } 6 => { // |- dx1 {dya dxb}* hlineto (6) |- // |- {dxa dyb}+ hlineto (6) |- let mut i = 0; while i < stack.len() { x += stack.at(i); i += 1; builder.line_to(x, y); if i == stack.len() { break; } y += stack.at(i); i += 1; builder.line_to(x, y); } stack.clear(); } 7 => { // |- dy1 {dxa dyb}* vlineto (7) |- // |- {dya dxb}+ vlineto (7) |- let mut i = 0; while i < stack.len() { y += stack.at(i); i += 1; builder.line_to(x, y); if i == stack.len() { break; } x += stack.at(i); i += 1; builder.line_to(x, y); } stack.clear(); } 8 => { // |- {dxa dya dxb dyb dxc dyc}+ rrcurveto (8) |- if stack.len() % 6 != 0 { return Err(CFFError::InvalidArgumentsStackLength.into()); } let mut i = 0; while i < stack.len() { let x1 = x + stack.at(i + 0); let y1 = y + stack.at(i + 1); let x2 = x1 + stack.at(i + 2); let y2 = y1 + stack.at(i + 3); x = x2 + stack.at(i + 4); y = y2 + stack.at(i + 5); builder.curve_to(x1, y1, x2, y2, x, y); i += 6; } stack.clear(); } 10 => { // subr# callsubr (10) – if stack.is_empty() { return Err(CFFError::InvalidArgumentsStackLength.into()); } if depth == STACK_LIMIT { return Err(CFFError::NestingLimitReached.into()); } let subroutine_bias = calc_subroutine_bias(ctx.local_subrs.len() as u16); let index = stack.pop() as i32 + subroutine_bias as i32; let char_string = ctx.local_subrs.get(index as u16).ok_or(Error::NoGlyph)?; let pos = _parse_char_string(ctx, char_string, x, y, stack, depth + 1, builder)?; x = pos.0; y = pos.1; } 11 => { // – return (11) – break; } TWO_BYTE_OPERATOR_MARK => { // flex let op2: u8 = s.read()?; match op2 { 34 => { // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- if stack.len() != 7 { return Err(CFFError::InvalidArgumentsStackLength.into()); } let dx1 = x + stack.at(0); let dy1 = y; let dx2 = dx1 + stack.at(1); let dy2 = dy1 + stack.at(2); let dx3 = dx2 + stack.at(3); let dy3 = dy2; let dx4 = dx3 + stack.at(4); let dy4 = dy2; let dx5 = dx4 + stack.at(5); let dy5 = y; x = dx5 + stack.at(6); builder.curve_to(dx1, dy1, dx2, dy2, dx3, dy3); builder.curve_to(dx4, dy4, dx5, dy5, x, y); stack.clear(); } 35 => { // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- if stack.len() != 13 { return Err(CFFError::InvalidArgumentsStackLength.into()); } let dx1 = x + stack.at(0); let dy1 = y + stack.at(1); let dx2 = dx1 + stack.at(2); let dy2 = dy1 + stack.at(3); let dx3 = dx2 + stack.at(4); let dy3 = dy2 + stack.at(5); let dx4 = dx3 + stack.at(6); let dy4 = dy3 + stack.at(7); let dx5 = dx4 + stack.at(8); let dy5 = dy4 + stack.at(9); x = dx5 + stack.at(10); y = dy5 + stack.at(11); builder.curve_to(dx1, dy1, dx2, dy2, dx3, dy3); builder.curve_to(dx4, dy4, dx5, dy5, x, y); stack.clear(); } 36 => { // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- if stack.len() != 9 { return Err(CFFError::InvalidArgumentsStackLength.into()); } let dx1 = x + stack.at(0); let dy1 = y + stack.at(1); let dx2 = dx1 + stack.at(2); let dy2 = dy1 + stack.at(3); let dx3 = dx2 + stack.at(4); let dy3 = dy2; let dx4 = dx3 + stack.at(5); let dy4 = dy2; let dx5 = dx4 + stack.at(6); let dy5 = dy4 + stack.at(7); x = dx5 + stack.at(8); builder.curve_to(dx1, dy1, dx2, dy2, dx3, dy3); builder.curve_to(dx4, dy4, dx5, dy5, x, y); stack.clear(); } 37 => { // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- if stack.len() != 11 { return Err(CFFError::InvalidArgumentsStackLength.into()); } let dx1 = x + stack.at(0); let dy1 = y + stack.at(1); let dx2 = dx1 + stack.at(2); let dy2 = dy1 + stack.at(3); let dx3 = dx2 + stack.at(4); let dy3 = dy2 + stack.at(5); let dx4 = dx3 + stack.at(6); let dy4 = dy3 + stack.at(7); let dx5 = dx4 + stack.at(8); let dy5 = dy4 + stack.at(9); if f32_abs(dx5 - x) > f32_abs(dy5 - y) { x = dx5 + stack.at(10); } else { y = dy5 + stack.at(10); } builder.curve_to(dx1, dy1, dx2, dy2, dx3, dy3); builder.curve_to(dx4, dy4, dx5, dy5, x, y); stack.clear(); } _ => { return Err(CFFError::UnsupportedOperator.into()); } } } 14 => { // – endchar (14) |– if !stack.is_empty() && !ctx.width_parsed { stack.clear(); ctx.width_parsed = true; } if !ctx.is_first_move_to { ctx.is_first_move_to = true; builder.close(); } } 19 | 20 => { // |- hintmask (19 + mask) |- // |- cntrmask (20 + mask) |- let mut len = stack.len(); // We are ignoring the hint operators. stack.clear(); // If the stack length is uneven, than the first value is a `width`. if len.is_odd() && !ctx.width_parsed { len -= 1; ctx.width_parsed = true; } ctx.stems_len += len as u32 >> 1; s.skip_len((ctx.stems_len + 7) >> 3); } 21 => { // |- dx1 dy1 rmoveto (21) |- let mut i = 0; if stack.len() == 3 && !ctx.width_parsed { i += 1; ctx.width_parsed = true; } else if stack.len() != 2 { return Err(CFFError::InvalidArgumentsStackLength.into()); } if ctx.is_first_move_to { ctx.is_first_move_to = false; } else { builder.close(); } x += stack.at(i + 0); y += stack.at(i + 1); builder.move_to(x, y); stack.clear(); } 22 => { // |- dx1 hmoveto (22) |- let mut i = 0; if stack.len() == 2 && !ctx.width_parsed { i += 1; ctx.width_parsed = true; } else if stack.len() != 1 { return Err(CFFError::InvalidArgumentsStackLength.into()); } if ctx.is_first_move_to { ctx.is_first_move_to = false; } else { builder.close(); } x += stack.at(i); builder.move_to(x, y); stack.clear(); } 24 => { // |- {dxa dya dxb dyb dxc dyc}+ dxd dyd rcurveline (24) |- if stack.len() < 8 { return Err(CFFError::InvalidArgumentsStackLength.into()); } if (stack.len() - 2) % 6 != 0 { return Err(CFFError::InvalidArgumentsStackLength.into()); } let mut i = 0; while i < stack.len() - 2 { let x1 = x + stack.at(i + 0); let y1 = y + stack.at(i + 1); let x2 = x1 + stack.at(i + 2); let y2 = y1 + stack.at(i + 3); x = x2 + stack.at(i + 4); y = y2 + stack.at(i + 5); builder.curve_to(x1, y1, x2, y2, x, y); i += 6; } x += stack.at(i + 0); y += stack.at(i + 1); builder.line_to(x, y); stack.clear(); } 25 => { // |- {dxa dya}+ dxb dyb dxc dyc dxd dyd rlinecurve (25) |- if stack.len() < 8 { return Err(CFFError::InvalidArgumentsStackLength.into()); } if (stack.len() - 6).is_odd() { return Err(CFFError::InvalidArgumentsStackLength.into()); } let mut i = 0; while i < stack.len() - 6 { x += stack.at(i + 0); y += stack.at(i + 1); builder.line_to(x, y); i += 2; } let x1 = x + stack.at(i + 0); let y1 = y + stack.at(i + 1); let x2 = x1 + stack.at(i + 2); let y2 = y1 + stack.at(i + 3); x = x2 + stack.at(i + 4); y = y2 + stack.at(i + 5); builder.curve_to(x1, y1, x2, y2, x, y); stack.clear(); } 26 => { // |- dx1? {dya dxb dyb dyc}+ vvcurveto (26) |- let mut i = 0; // The odd argument count indicates an X position. if stack.len().is_odd() { x += stack.at(0); i += 1; } if (stack.len() - i) % 4 != 0 { return Err(CFFError::InvalidArgumentsStackLength.into()); } while i < stack.len() { let x1 = x; let y1 = y + stack.at(i + 0); let x2 = x1 + stack.at(i + 1); let y2 = y1 + stack.at(i + 2); x = x2; y = y2 + stack.at(i + 3); builder.curve_to(x1, y1, x2, y2, x, y); i += 4; } stack.clear(); } 27 => { // |- dy1? {dxa dxb dyb dxc}+ hhcurveto (27) |- let mut i = 0; // The odd argument count indicates an Y position. if stack.len().is_odd() { y += stack.at(0); i += 1; } if (stack.len() - i) % 4 != 0 { return Err(CFFError::InvalidArgumentsStackLength.into()); } while i < stack.len() { let x1 = x + stack.at(i + 0); let y1 = y; let x2 = x1 + stack.at(i + 1); let y2 = y1 + stack.at(i + 2); x = x2 + stack.at(i + 3); y = y2; builder.curve_to(x1, y1, x2, y2, x, y); i += 4; } stack.clear(); } 28 => { let b1 = s.read::()? as i32; let b2 = s.read::()? as i32; let n = ((b1 << 24) | (b2 << 16)) >> 16; debug_assert!((-32768..=32767).contains(&n)); stack.push(n as f32)?; } 29 => { // globalsubr# callgsubr (29) – if stack.is_empty() { return Err(CFFError::InvalidArgumentsStackLength.into()); } if depth == STACK_LIMIT { return Err(CFFError::NestingLimitReached.into()); } let subroutine_bias = calc_subroutine_bias(ctx.global_subrs.len() as u16); let index = stack.pop() as i32 + subroutine_bias as i32; let char_string = ctx.global_subrs.get(index as u16).ok_or(Error::NoGlyph)?; let pos = _parse_char_string(ctx, char_string, x, y, stack, depth + 1, builder)?; x = pos.0; y = pos.1; } 30 => { // |- dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? vhcurveto (30) |- // |- {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? vhcurveto (30) |- if stack.len() < 4 { return Err(CFFError::InvalidArgumentsStackLength.into()); } stack.reverse(); while !stack.is_empty() { if stack.len() < 4 { return Err(CFFError::InvalidArgumentsStackLength.into()); } let x1 = x; let y1 = y + stack.pop(); let x2 = x1 + stack.pop(); let y2 = y1 + stack.pop(); x = x2 + stack.pop(); y = y2 + if stack.len() == 1 { stack.pop() } else { 0.0 }; builder.curve_to(x1, y1, x2, y2, x, y); if stack.is_empty() { break; } if stack.len() < 4 { return Err(CFFError::InvalidArgumentsStackLength.into()); } let x1 = x + stack.pop(); let y1 = y; let x2 = x1 + stack.pop(); let y2 = y1 + stack.pop(); y = y2 + stack.pop(); x = x2 + if stack.len() == 1 { stack.pop() } else { 0.0 }; builder.curve_to(x1, y1, x2, y2, x, y); } debug_assert!(stack.is_empty()); } 31 => { // |- dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf? hvcurveto (31) |- // |- {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf? hvcurveto (31) |- if stack.len() < 4 { return Err(CFFError::InvalidArgumentsStackLength.into()); } stack.reverse(); while !stack.is_empty() { if stack.len() < 4 { return Err(CFFError::InvalidArgumentsStackLength.into()); } let x1 = x + stack.pop(); let y1 = y; let x2 = x1 + stack.pop(); let y2 = y1 + stack.pop(); y = y2 + stack.pop(); x = x2 + if stack.len() == 1 { stack.pop() } else { 0.0 }; builder.curve_to(x1, y1, x2, y2, x, y); if stack.is_empty() { break; } if stack.len() < 4 { return Err(CFFError::InvalidArgumentsStackLength.into()); } let x1 = x; let y1 = y + stack.pop(); let x2 = x1 + stack.pop(); let y2 = y1 + stack.pop(); x = x2 + stack.pop(); y = y2 + if stack.len() == 1 { stack.pop() } else { 0.0 }; builder.curve_to(x1, y1, x2, y2, x, y); } debug_assert!(stack.is_empty()); } 32..=246 => { let n = op as i32 - 139; debug_assert!((-107..=107).contains(&n)); stack.push(n as f32)?; } 247..=250 => { let b1 = s.read::()? as i32; let n = (op as i32 - 247) * 256 + b1 + 108; debug_assert!((108..=1131).contains(&n)); stack.push(n as f32)?; } 251..=254 => { let b1 = s.read::()? as i32; let n = -(op as i32 - 251) * 256 - b1 - 108; debug_assert!((-1131..=-108).contains(&n)); stack.push(n as f32)?; } 255 => { let n = s.read::()? as i32 as f32 / 65536.0; stack.push(n)?; } } } Ok((x, y)) } // Adobe Technical Note #5176, Chapter 16 "Local / Global Subrs INDEXes" #[inline] fn calc_subroutine_bias(len: u16) -> u16 { if len < 1240 { 107 } else if len < 33900 { 1131 } else { 32768 } } fn parse_index<'a>(s: &mut Stream<'a>) -> Result> { let count: u16 = s.read()?; if count != 0 && count != core::u16::MAX { let offset_size: OffsetSize = s.try_read()?; let offsets_len = (count + 1) as u32 * offset_size as u32; let offsets = VarOffsets { data: &s.read_bytes(offsets_len)?, offset_size, }; match offsets.last() { Some(last_offset) => { let data = s.read_bytes(last_offset)?; Ok(DataIndex { data, offsets }) } None => { Ok(DataIndex::create_empty()) } } } else { Ok(DataIndex::create_empty()) } } fn skip_index(s: &mut Stream) -> Result<()> { let count: u16 = s.read()?; if count != 0 && count != core::u16::MAX { let offset_size: OffsetSize = s.try_read()?; let offsets_len = (count + 1) as u32 * offset_size as u32; let offsets = VarOffsets { data: &s.read_bytes(offsets_len)?, offset_size, }; if let Some(last_offset) = offsets.last() { s.skip_len(last_offset); } } Ok(()) } #[derive(Clone, Copy)] struct VarOffsets<'a> { data: &'a [u8], offset_size: OffsetSize, } impl<'a> VarOffsets<'a> { fn get(&self, index: u16) -> Option { if index >= self.len() { return None; } let start = index as usize * self.offset_size as usize; let end = start + self.offset_size as usize; let data = self.data.try_slice(start..end).ok()?; let mut s = SafeStream::new(data); let n: u32 = match self.offset_size { OffsetSize::Size1 => s.read::() as u32, OffsetSize::Size2 => s.read::() as u32, OffsetSize::Size3 => s.read_u24(), OffsetSize::Size4 => s.read(), }; // Offset must be positive. if n == 0 { return None; } // Offsets are offset by one byte in the font, // so we have to shift them back. Some(n - 1) } #[inline] fn last(&self) -> Option { if !self.is_empty() { self.get(self.len() - 1) } else { None } } #[inline] fn len(&self) -> u16 { self.data.len() as u16 / self.offset_size as u16 } #[inline] fn is_empty(&self) -> bool { self.len() == 0 } } #[derive(Clone, Copy)] struct DataIndex<'a> { data: &'a [u8], offsets: VarOffsets<'a>, } impl<'a> DataIndex<'a> { #[inline] fn create_empty() -> Self { DataIndex { data: b"", offsets: VarOffsets { data: b"", offset_size: OffsetSize::Size1 }, } } #[inline] fn len(&self) -> u16 { if !self.offsets.is_empty() { // Last offset points to the byte after the `Object data`. // We should skip it. self.offsets.len() - 1 } else { 0 } } fn get(&self, index: u16) -> Option<&'a [u8]> { // Check for overflow first. if index == core::u16::MAX { None } else if index + 1 < self.offsets.len() { let start = self.offsets.get(index)? as usize; let end = self.offsets.get(index + 1)? as usize; let data = self.data.try_slice(start..end).ok()?; Some(data) } else { None } } } #[derive(Clone, Copy, Debug)] #[repr(u8)] enum OffsetSize { Size1 = 1, Size2 = 2, Size3 = 3, Size4 = 4, } impl TryFromData for OffsetSize { #[inline] fn try_parse(data: &[u8]) -> Result { let mut s = SafeStream::new(data); let n: u8 = s.read(); match n { 1 => Ok(OffsetSize::Size1), 2 => Ok(OffsetSize::Size2), 3 => Ok(OffsetSize::Size3), 4 => Ok(OffsetSize::Size4), _ => Err(CFFError::InvalidOffsetSize.into()), } } } #[derive(Clone, Copy, Debug)] struct Operator(u16); impl Operator { #[inline] fn value(&self) -> u16 { self.0 } } struct DictionaryParser<'a> { data: &'a [u8], // The current offset. offset: usize, // Offset to the last operands start. operands_offset: usize, // Actual operands. operands: [Number; MAX_OPERANDS_LEN], // 192B // An amount of operands in the `operands` array. operands_len: u8, } impl<'a> DictionaryParser<'a> { #[inline] fn new(data: &'a [u8]) -> Self { DictionaryParser { data, offset: 0, operands_offset: 0, operands: [Number::Integer(0); MAX_OPERANDS_LEN], operands_len: 0, } } #[inline(never)] fn parse_next(&mut self) -> Option { let mut s = Stream::new_at(self.data, self.offset); self.operands_offset = self.offset; while !s.at_end() { let b: u8 = s.read().ok()?; // 0..=21 bytes are operators. if b <= 21 { let mut operator = b as u16; // Check that operator is two byte long. if b == TWO_BYTE_OPERATOR_MARK { // Use a 1200 'prefix' to make two byte operators more readable. // 12 3 => 1203 operator = 1200 + s.read::().ok()? as u16; } self.offset = s.offset(); return Some(Operator(operator)); } else { skip_number(b, &mut s)?; } } None } /// Parses operands of the current operator. /// /// In the DICT structure, operands are defined before an operator. /// So we are trying to find an operator first and the we can actually parse the operands. /// /// Since this methods is pretty expensive and we do not care about most of the operators, /// we can speed up parsing by parsing operands only for required operators. /// /// We still have to "skip" operands during operators search (see `skip_number()`), /// but it's still faster that a naive method. fn parse_operands(&mut self) -> Result<()> { let mut s = Stream::new_at(self.data, self.operands_offset); self.operands_len = 0; while !s.at_end() { let b: u8 = s.read()?; // 0..=21 bytes are operators. if b <= 21 { break; } else { let op = parse_number(b, &mut s)?; self.operands[self.operands_len as usize] = op; self.operands_len += 1; if self.operands_len >= MAX_OPERANDS_LEN as u8 { break; } } } Ok(()) } #[inline] fn operands(&self) -> &[Number] { &self.operands[..self.operands_len as usize] } } // Adobe Technical Note #5177, Table 3 Operand Encoding fn parse_number(b0: u8, s: &mut Stream) -> Result { match b0 { 28 => { let n = s.read::()? as i32; Ok(Number::Integer(n)) } 29 => { let n = s.read::()? as i32; Ok(Number::Integer(n)) } 30 => { parse_float(s) } 32..=246 => { let n = b0 as i32 - 139; Ok(Number::Integer(n)) } 247..=250 => { let b1 = s.read::()? as i32; let n = (b0 as i32 - 247) * 256 + b1 + 108; Ok(Number::Integer(n)) } 251..=254 => { let b1 = s.read::()? as i32; let n = -(b0 as i32 - 251) * 256 - b1 - 108; Ok(Number::Integer(n)) } _ => Err(CFFError::InvalidOperand.into()), } } const FLOAT_STACK_LEN: usize = 64; const END_OF_FLOAT_FLAG: u8 = 0xf; fn parse_float(s: &mut Stream) -> Result { let mut data = [0u8; FLOAT_STACK_LEN]; let mut idx = 0; loop { let b1: u8 = s.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]).map_err(|_| CFFError::InvalidFloat)?; let n = s.parse().map_err(|_| CFFError::InvalidFloat)?; Ok(Number::Float(n)) } // Adobe Technical Note #5176, Table 5 Nibble Definitions fn parse_float_nibble(nibble: u8, mut idx: usize, data: &mut [u8]) -> Result { if idx == FLOAT_STACK_LEN { return Err(CFFError::InvalidFloat.into()); } 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 Err(CFFError::InvalidFloat.into()); } data[idx] = b'E'; idx += 1; data[idx] = b'-'; } 13 => { return Err(CFFError::InvalidFloat.into()); } 14 => { data[idx] = b'-'; } _ => { return Err(CFFError::InvalidFloat.into()); } } idx += 1; Ok(idx) } // Just like `parse_number`, but doesn't actually parses the data. fn skip_number(b0: u8, s: &mut Stream) -> Option<()> { match b0 { 28 => s.skip::(), 29 => s.skip::(), 30 => { while !s.at_end() { let b1: u8 = s.read().ok()?; let nibble1 = b1 >> 4; let nibble2 = b1 & 15; if nibble1 == END_OF_FLOAT_FLAG || nibble2 == END_OF_FLOAT_FLAG { break; } } } 32..=246 => {} 247..=250 => s.skip::(), 251..=254 => s.skip::(), _ => return None, } Some(()) } #[derive(Clone, Copy, Debug)] enum Number { Integer(i32), Float(f32), } impl Number { #[inline] fn as_i32(&self) -> i32 { match *self { Number::Integer(n) => n, Number::Float(n) => n as i32, } } } struct ArgumentsStack { data: [f32; MAX_ARGUMENTS_STACK_LEN], // 192B len: usize, } impl ArgumentsStack { #[inline] fn new() -> Self { ArgumentsStack { data: [0.0; MAX_ARGUMENTS_STACK_LEN], len: 0, } } #[inline] fn len(&self) -> usize { self.len } #[inline] fn is_empty(&self) -> bool { self.len == 0 } #[inline] fn push(&mut self, n: f32) -> Result<()> { if self.len == MAX_ARGUMENTS_STACK_LEN { Err(CFFError::ArgumentsStackLimitReached.into()) } else { self.data[self.len] = n; self.len += 1; Ok(()) } } #[inline] fn at(&self, index: usize) -> f32 { self.data[index] } #[inline] fn pop(&mut self) -> f32 { debug_assert!(!self.is_empty()); self.len -= 1; self.data[self.len] } #[inline] fn reverse(&mut self) { if self.is_empty() { return; } // Reverse only the actual data and not the whole stack. let (first, _) = self.data.split_at_mut(self.len); first.reverse(); } #[inline] fn clear(&mut self) { self.len = 0; } } impl<'a> core::fmt::Debug for ArgumentsStack { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_list().entries(&self.data[..self.len]).finish() } } trait IsEven { fn is_even(&self) -> bool; fn is_odd(&self) -> bool; } impl IsEven for usize { #[inline] fn is_even(&self) -> bool { (*self) & 1 == 0 } #[inline] fn is_odd(&self) -> bool { !self.is_even() } } #[cfg(feature = "std")] fn f32_abs(n: f32) -> f32 { n.abs() } #[cfg(not(feature = "std"))] fn f32_abs(n: f32) -> f32 { if n.is_sign_negative() { -n } else { n } } ttf-parser-0.3.0/src/cmap.rs010064400017500001750000000365461354314235500141220ustar0000000000000000// https://docs.microsoft.com/en-us/typography/opentype/spec/cmap use crate::parser::{Stream, LazyArray, TrySlice}; use crate::{Font, GlyphId, TableName, Result, Error, PlatformId}; use crate::raw::cmap as raw; impl<'a> Font<'a> { /// Resolves Glyph ID for code point. /// /// Returns `Error::NoGlyph` instead of `0` when glyph is not found. /// /// All subtable formats except Mixed Coverage (8) are supported. pub fn glyph_index(&self, c: char) -> Result { let data = self.cmap.ok_or_else(|| Error::TableMissing(TableName::CharacterToGlyphIndexMapping))?; let mut s = Stream::new(data); s.skip::(); // version let records: LazyArray = s.read_array16()?; for record in records { let subtable_data = data.try_slice(record.offset() as usize..data.len())?; let mut s = Stream::new(subtable_data); let format = match parse_format(s.read()?) { Some(format) => format, None => continue, }; let platform_id = match PlatformId::from_u16(record.platform_id()) { Some(v) => v, None => continue, }; if !is_unicode_encoding(format, platform_id, record.encoding_id()) { continue; } let c = c as u32; let glyph = match format { Format::ByteEncodingTable => { parse_byte_encoding_table(&mut s, c) } Format::HighByteMappingThroughTable => { parse_high_byte_mapping_through_table(subtable_data, c) } Format::SegmentMappingToDeltaValues => { parse_segment_mapping_to_delta_values(subtable_data, c) } Format::TrimmedTableMapping => { parse_trimmed_table_mapping(&mut s, c) } Format::MixedCoverage => { // Unsupported. continue; } Format::TrimmedArray => { parse_trimmed_array(&mut s, c) } Format::SegmentedCoverage | Format::ManyToOneRangeMappings => { parse_segmented_coverage(&mut s, c, format) } Format::UnicodeVariationSequences => { // This subtable is used only by glyph_variation_index(). continue; } }; if let Ok(id) = glyph { return Ok(GlyphId(id)); } } Err(Error::NoGlyph) } /// Resolves a variation of a Glyph ID from two code points. /// /// Implemented according to /// [Unicode Variation Sequences]( /// https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-14-unicode-variation-sequences). /// /// Returns `Error::NoGlyph` instead of `0` when glyph is not found. pub fn glyph_variation_index(&self, c: char, variation: char) -> Result { let data = self.cmap.ok_or_else(|| Error::TableMissing(TableName::CharacterToGlyphIndexMapping))?; let mut s = Stream::new(data); s.skip::(); // version let records: LazyArray = s.read_array16()?; for record in records { let subtable_data = data.try_slice(record.offset() as usize..data.len())?; let mut s = Stream::new(subtable_data); let format = match parse_format(s.read()?) { Some(format) => format, None => continue, }; if format != Format::UnicodeVariationSequences { continue; } return self.parse_unicode_variation_sequences(subtable_data, c, variation as u32); } Err(Error::NoGlyph) } fn parse_unicode_variation_sequences( &self, data: &[u8], c: char, variation: u32, ) -> Result { let cp = c as u32; let mut s = Stream::new(data); s.skip::(); // format s.skip::(); // length let records: LazyArray = s.read_array32()?; let record = records.binary_search_by(|v| v.var_selector().cmp(&variation)).ok_or(Error::NoGlyph)?; if let Some(offset) = record.default_uvs_offset() { let data = data.try_slice(offset as usize..data.len())?; let mut s = Stream::new(data); let ranges: LazyArray = s.read_array32()?; for range in ranges { if range.contains(c) { // This is a default glyph. return self.glyph_index(c); } } } if let Some(offset) = record.non_default_uvs_offset() { let data = data.try_slice(offset as usize..data.len())?; let mut s = Stream::new(data); let uvs_mappings: LazyArray = s.read_array32()?; if let Some(mapping) = uvs_mappings.binary_search_by(|v| v.unicode_value().cmp(&cp)) { return Ok(mapping.glyph_id()); } } Err(Error::NoGlyph) } } // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-0-byte-encoding-table fn parse_byte_encoding_table(s: &mut Stream, code_point: u32) -> Result { let length: u16 = s.read()?; s.skip::(); // language if code_point < (length as u32) { s.skip_len(code_point); Ok(s.read::()? as u16) } else { Err(Error::NoGlyph) } } // This table has a pretty complex parsing algorithm. // A detailed explanation can be found here: // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-2-high-byte-mapping-through-table // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html // https://github.com/fonttools/fonttools/blob/a360252709a3d65f899915db0a5bd753007fdbb7/Lib/fontTools/ttLib/tables/_c_m_a_p.py#L360 fn parse_high_byte_mapping_through_table(data: &[u8], code_point: u32) -> Result { // This subtable supports code points only in a u16 range. if code_point > 0xffff { return Err(Error::NoGlyph); } let code_point = code_point as u16; let high_byte = (code_point >> 8) as u16; let low_byte = (code_point & 0x00FF) as u16; let mut s = Stream::new(data); s.skip::(); // format s.skip::(); // length s.skip::(); // language let sub_header_keys: LazyArray = s.read_array(256_u32)?; // The maximum index in a sub_header_keys is a sub_headers count. let sub_headers_count = sub_header_keys.into_iter().map(|n| n / 8).max() .ok_or_else(|| Error::NoGlyph)? + 1; // Remember sub_headers offset before reading. Will be used later. let sub_headers_offset = s.offset(); let sub_headers: LazyArray = s.read_array(sub_headers_count)?; let i = if code_point < 0xff { // 'SubHeader 0 is special: it is used for single-byte character codes.' 0 } else { // 'Array that maps high bytes to subHeaders: value is subHeader index × 8.' sub_header_keys.get(high_byte).ok_or_else(|| Error::NoGlyph)? / 8 }; let sub_header = sub_headers.get(i).ok_or_else(|| Error::NoGlyph)?; let first_code = sub_header.first_code(); let range_end = first_code + sub_header.entry_count(); if low_byte < first_code || low_byte > range_end { return Err(Error::NoGlyph); } // SubHeaderRecord::id_range_offset points to SubHeaderRecord::first_code // in the glyphIndexArray. So we have to advance to our code point. let index_offset = (low_byte - first_code) as usize * core::mem::size_of::(); // 'The value of the idRangeOffset is the number of bytes // past the actual location of the idRangeOffset'. let offset = sub_headers_offset // Advance to required subheader. + raw::SubHeaderRecord::SIZE * (i + 1) as usize // Move back to idRangeOffset start. - core::mem::size_of::() // Use defined offset. + sub_header.id_range_offset() as usize // Advance to required index in the glyphIndexArray. + index_offset; let glyph: u16 = Stream::read_at(data, offset)?; if glyph == 0 { return Err(Error::NoGlyph); } let glyph = ((glyph as i32 + sub_header.id_delta() as i32) % 65536) as u16; Ok(glyph) } // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-4-segment-mapping-to-delta-values fn parse_segment_mapping_to_delta_values(data: &[u8], code_point: u32) -> Result { // This subtable supports code points only in a u16 range. if code_point > 0xffff { return Err(Error::NoGlyph); } let code_point = code_point as u16; let mut s = Stream::new(data); s.skip_len(6 as u32); // format + length + language let seg_count_x2: u16 = s.read()?; if seg_count_x2 < 2 { return Err(Error::NoGlyph); } let seg_count = seg_count_x2 / 2; s.skip_len(6 as u32); // searchRange + entrySelector + rangeShift let end_codes: LazyArray = s.read_array(seg_count)?; s.skip::(); // reservedPad let start_codes: LazyArray = s.read_array(seg_count)?; let id_deltas: LazyArray = s.read_array(seg_count)?; let id_range_offset_pos = s.offset(); let id_range_offsets: LazyArray = s.read_array(seg_count)?; // A custom binary search. let mut start = 0; let mut end = seg_count; while end > start { let index = (start + end) / 2; let end_value = end_codes.get(index).ok_or_else(|| Error::NoGlyph)?; if end_value >= code_point { let start_value = start_codes.get(index).ok_or_else(|| Error::NoGlyph)?; if start_value > code_point { end = index; } else { let id_range_offset = id_range_offsets.get(index).ok_or_else(|| Error::NoGlyph)?; let id_delta = id_deltas.get(index).ok_or_else(|| Error::NoGlyph)?; if id_range_offset == 0 { return Ok(code_point.wrapping_add(id_delta as u16)); } let delta = (code_point as u32 - start_value as u32) * 2; // Check for overflow. if delta > core::u16::MAX as u32 { return Err(Error::NoGlyph); } // `delta` must be u16. let delta = delta as u16; let id_range_offset_pos = (id_range_offset_pos + index as usize * 2) as u16; let pos = id_range_offset_pos.wrapping_add(delta); let pos = pos.wrapping_add(id_range_offset); let glyph_array_value: u16 = Stream::read_at(data, pos as usize)?; if glyph_array_value == 0 { return Err(Error::NoGlyph); } let glyph_id = (glyph_array_value as i16).wrapping_add(id_delta); return Ok(glyph_id as u16); } } else { start = index + 1; } } return Err(Error::NoGlyph); } // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-6-trimmed-table-mapping fn parse_trimmed_table_mapping(s: &mut Stream, code_point: u32) -> Result { // This subtable supports code points only in a u16 range. if code_point > 0xffff { return Err(Error::NoGlyph); } s.skip::(); // length s.skip::(); // language let first_code_point: u16 = s.read()?; let glyphs: LazyArray = s.read_array16()?; let code_point = code_point as u16; // Check for overflow. if code_point < first_code_point { return Err(Error::NoGlyph); } let idx = code_point - first_code_point; glyphs.get(idx).ok_or_else(|| Error::NoGlyph) } // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-10-trimmed-array fn parse_trimmed_array(s: &mut Stream, code_point: u32) -> Result { s.skip::(); // reserved s.skip::(); // length s.skip::(); // language let first_code_point: u32 = s.read()?; let glyphs: LazyArray = s.read_array32()?; // Check for overflow. if code_point < first_code_point { return Err(Error::NoGlyph); } let idx = code_point - first_code_point; glyphs.get(idx).ok_or_else(|| Error::NoGlyph) } // + ManyToOneRangeMappings // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-12-segmented-coverage // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-13-many-to-one-range-mappings fn parse_segmented_coverage(s: &mut Stream, code_point: u32, format: Format) -> Result { s.skip::(); // reserved s.skip::(); // length s.skip::(); // language let groups: LazyArray = s.read_array32()?; for group in groups { let start_char_code = group.start_char_code(); if code_point >= start_char_code && code_point <= group.end_char_code() { if format == Format::SegmentedCoverage { let id = group.start_glyph_id() + code_point - start_char_code; return Ok(id as u16); } else { // TODO: what if start_glyph_id is > u16::MAX return Ok(group.start_glyph_id() as u16); } } } Err(Error::NoGlyph) } #[derive(Clone, Copy, PartialEq, Debug)] enum Format { ByteEncodingTable = 0, HighByteMappingThroughTable = 2, SegmentMappingToDeltaValues = 4, TrimmedTableMapping = 6, MixedCoverage = 8, TrimmedArray = 10, SegmentedCoverage = 12, ManyToOneRangeMappings = 13, UnicodeVariationSequences = 14, } fn parse_format(v: u16) -> Option { match v { 0 => Some(Format::ByteEncodingTable), 2 => Some(Format::HighByteMappingThroughTable), 4 => Some(Format::SegmentMappingToDeltaValues), 6 => Some(Format::TrimmedTableMapping), 8 => Some(Format::MixedCoverage), 10 => Some(Format::TrimmedArray), 12 => Some(Format::SegmentedCoverage), 13 => Some(Format::ManyToOneRangeMappings), 14 => Some(Format::UnicodeVariationSequences), _ => None, } } impl raw::UnicodeRangeRecord { fn contains(&self, c: char) -> bool { let start_unicode_value = self.start_unicode_value(); let end = start_unicode_value + self.additional_count() as u32; start_unicode_value >= (c as u32) && (c as u32) < end } } #[inline] fn is_unicode_encoding(format: Format, platform_id: PlatformId, encoding_id: u16) -> bool { // https://docs.microsoft.com/en-us/typography/opentype/spec/name#windows-encoding-ids const WINDOWS_UNICODE_BMP_ENCODING_ID: u16 = 1; const WINDOWS_UNICODE_FULL_REPERTOIRE_ENCODING_ID: u16 = 10; match platform_id { PlatformId::Unicode => true, PlatformId::Windows if encoding_id == WINDOWS_UNICODE_BMP_ENCODING_ID => true, PlatformId::Windows => { // "Fonts that support Unicode supplementary-plane characters (U+10000 to U+10FFFF) // on the Windows platform must have a format 12 subtable for platform ID 3, // encoding ID 10." encoding_id == WINDOWS_UNICODE_FULL_REPERTOIRE_ENCODING_ID && format == Format::SegmentedCoverage } _ => false, } } ttf-parser-0.3.0/src/glyf.rs010064400017500001750000000420721354314235500141320ustar0000000000000000// https://docs.microsoft.com/en-us/typography/opentype/spec/glyf // This module is a heavily modified version of https://github.com/raphlinus/font-rs use crate::parser::{Stream, LazyArray, TrySlice}; use crate::{Font, GlyphId, OutlineBuilder, TableName, Rect, Result, Error}; /// A wrapper that transforms segments before passing them to `OutlineBuilder`. trait OutlineBuilderInner { fn move_to(&mut self, x: f32, y: f32); fn line_to(&mut self, x: f32, y: f32); fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32); fn close(&mut self); } struct Builder<'a, T: OutlineBuilder> { builder: &'a mut T, transform: Transform, is_default_ts: bool, // `bool` is faster than `Option` or `is_default`. } impl<'a, T: OutlineBuilder> OutlineBuilderInner for Builder<'a, T> { #[inline] fn move_to(&mut self, mut x: f32, mut y: f32) { if !self.is_default_ts { self.transform.apply_to(&mut x, &mut y); } self.builder.move_to(x, y); } #[inline] fn line_to(&mut self, mut x: f32, mut y: f32) { if !self.is_default_ts { self.transform.apply_to(&mut x, &mut y); } self.builder.line_to(x, y); } #[inline] fn quad_to(&mut self, mut x1: f32, mut y1: f32, mut x: f32, mut y: f32) { if !self.is_default_ts { self.transform.apply_to(&mut x1, &mut y1); self.transform.apply_to(&mut x, &mut y); } self.builder.quad_to(x1, y1, x, y); } #[inline] fn close(&mut self) { self.builder.close(); } } // https://docs.microsoft.com/en-us/typography/opentype/spec/glyf#simple-glyph-description #[derive(Clone, Copy)] struct SimpleGlyphFlags(u8); impl SimpleGlyphFlags { const ON_CURVE_POINT: Self = Self(1 << 0); const X_SHORT_VECTOR: Self = Self(1 << 1); const Y_SHORT_VECTOR: Self = Self(1 << 2); const REPEAT_FLAG: Self = Self(1 << 3); const X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR: Self = Self(1 << 4); const Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR: Self = Self(1 << 5); #[inline] fn empty() -> Self { Self(0) } #[inline] fn all() -> Self { Self(63) } #[inline] fn from_bits_truncate(bits: u8) -> Self { Self(bits & Self::all().0) } #[inline] fn contains(&self, other: Self) -> bool { (self.0 & other.0) == other.0 } } // https://docs.microsoft.com/en-us/typography/opentype/spec/glyf#composite-glyph-description #[derive(Clone, Copy)] struct CompositeGlyphFlags(u16); impl CompositeGlyphFlags { const ARG_1_AND_2_ARE_WORDS: Self = Self(1 << 0); const ARGS_ARE_XY_VALUES: Self = Self(1 << 1); const WE_HAVE_A_SCALE: Self = Self(1 << 3); const MORE_COMPONENTS: Self = Self(1 << 5); const WE_HAVE_AN_X_AND_Y_SCALE: Self = Self(1 << 6); const WE_HAVE_A_TWO_BY_TWO: Self = Self(1 << 7); #[inline] fn all() -> Self { Self(235) } #[inline] fn from_bits_truncate(bits: u16) -> Self { Self(bits & Self::all().0) } #[inline] fn contains(&self, other: Self) -> bool { (self.0 & other.0) == other.0 } } #[inline] fn f32_bound(min: f32, val: f32, max: f32) -> f32 { debug_assert!(min.is_finite()); debug_assert!(val.is_finite()); debug_assert!(max.is_finite()); if val > max { return max; } else if val < min { return min; } val } // It's not defined in the spec, so we are using our own value. const MAX_COMPONENTS: u8 = 32; impl<'a> Font<'a> { pub(crate) fn glyf_glyph_outline( &self, glyph_id: GlyphId, builder: &mut impl OutlineBuilder, ) -> Result { let mut b = Builder { builder, transform: Transform::default(), is_default_ts: true, }; let glyph_data = self.glyph_data(glyph_id)?; self.outline_impl(glyph_data, 0, &mut b) } fn glyph_data(&self, glyph_id: GlyphId) -> Result<&[u8]> { let range = self.glyph_range(glyph_id)?; let data = self.glyf.ok_or_else(|| Error::TableMissing(TableName::GlyphData))?; data.try_slice(range) } fn outline_impl( &self, data: &[u8], depth: u8, builder: &mut Builder, ) -> Result { if depth >= MAX_COMPONENTS { return Ok(Rect::zero()); } let mut s = Stream::new(data); let number_of_contours: i16 = s.read()?; // It's faster to parse the rect directly, instead of using `FromData`. let rect = Rect { x_min: s.read()?, y_min: s.read()?, x_max: s.read()?, y_max: s.read()?, }; if number_of_contours > 0 { Self::parse_simple_outline(s.tail()?, number_of_contours as u16, builder)?; } else if number_of_contours < 0 { self.parse_composite_outline(s.tail()?, depth + 1, builder)?; } else { // An empty glyph. return Ok(Rect::zero()); } Ok(rect) } #[inline(never)] fn parse_simple_outline( glyph_data: &[u8], number_of_contours: u16, builder: &mut Builder, ) -> Result<()> { let mut s = Stream::new(glyph_data); let endpoints: LazyArray = s.read_array(number_of_contours)?; let points_total = { // Unwrap is safe, because it's guarantee that array has at least one value. let last_point = endpoints.last().unwrap(); // Prevent overflow. if last_point == core::u16::MAX { return Ok(()); } last_point + 1 }; let instructions_len: u16 = s.read()?; s.skip_len(instructions_len); let flags_offset = s.offset(); let x_coords_len = Self::resolve_x_coords_len(&mut s, points_total) .ok_or_else(|| Error::NoOutline)?; let x_coords_offset = s.offset(); let y_coords_offset = x_coords_offset + x_coords_len as usize; let mut points = GlyphPoints { flags: Stream::new(glyph_data.try_slice(flags_offset..x_coords_offset)?), x_coords: Stream::new(glyph_data.try_slice(x_coords_offset..y_coords_offset)?), y_coords: Stream::new(glyph_data.try_slice(y_coords_offset..glyph_data.len())?), points_left: points_total, flag_repeats: 0, last_flags: SimpleGlyphFlags::empty(), x: 0, y: 0, }; let mut total = 0u16; let mut last = 0u16; for n in endpoints { if n < last { // Endpoints must be in increasing order. break; } last = n; // Check for overflow. if n == core::u16::MAX { break; } let n = n + 1 - total; // Contour must have at least 2 points. if n >= 2 { Self::parse_contour(points.by_ref().take(n as usize), builder); } total += n; } Ok(()) } /// Resolves the X coordinates length. /// /// The length depends on *Simple Glyph Flags*, so we have to process them all to find it. fn resolve_x_coords_len( s: &mut Stream, points_total: u16, ) -> Option { type Flags = SimpleGlyphFlags; let mut flags_left = points_total; let mut x_coords_len = 0u16; while flags_left > 0 { let flags: u8 = s.read().ok()?; let flags = Flags::from_bits_truncate(flags); // The number of times a glyph point repeats. let repeats = if flags.contains(Flags::REPEAT_FLAG) { let repeats: u8 = s.read().ok()?; repeats as u16 + 1 } else { 1 }; if flags.contains(Flags::X_SHORT_VECTOR) { // Coordinate is 1 byte long. x_coords_len = x_coords_len.checked_add(repeats)?; } else { if !flags.contains(Flags::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) { // Coordinate is 2 bytes long. x_coords_len = x_coords_len.checked_add(repeats * 2)?; } } // Check for overflow. // Do not use checked_sub, because it's very slow for some reasons. if repeats <= flags_left { flags_left -= repeats; } else { return None; } } Some(x_coords_len) } fn parse_contour( points: core::iter::Take<&mut GlyphPoints>, builder: &mut Builder, ) { let mut first_oncurve: Option = None; let mut first_offcurve: Option = None; let mut last_offcurve: Option = None; for point in points { let p = Point { x: point.x as f32, y: point.y as f32 }; if first_oncurve.is_none() { if point.on_curve_point { first_oncurve = Some(p); builder.move_to(p.x, p.y); } else { match first_offcurve { Some(offcurve) => { let mid = offcurve.lerp(p, 0.5); first_oncurve = Some(mid); last_offcurve = Some(p); builder.move_to(mid.x, mid.y); } None => { first_offcurve = Some(p); } } } } else { match (last_offcurve, point.on_curve_point) { (Some(offcurve), true) => { last_offcurve = None; builder.quad_to(offcurve.x, offcurve.y, p.x, p.y); } (Some(offcurve), false) => { last_offcurve = Some(p); let mid = offcurve.lerp(p, 0.5); builder.quad_to(offcurve.x, offcurve.y, mid.x, mid.y); } (None, true) => { builder.line_to(p.x, p.y); } (None, false) => { last_offcurve = Some(p); } } } } loop { match (first_offcurve, last_offcurve) { (Some(offcurve1), Some(offcurve2)) => { last_offcurve = None; let mid = offcurve2.lerp(offcurve1, 0.5); builder.quad_to(offcurve2.x, offcurve2.y, mid.x, mid.y); } (Some(offcurve1), None) => { if let Some(p) = first_oncurve { builder.quad_to(offcurve1.x, offcurve1.y, p.x, p.y); } break; } (None, Some(offcurve2)) => { if let Some(p) = first_oncurve { builder.quad_to(offcurve2.x, offcurve2.y, p.x, p.y); } break; } (None, None) => { if let Some(p) = first_oncurve { builder.line_to(p.x, p.y); } break; } } } builder.close(); } #[inline(never)] fn parse_composite_outline( &self, glyph_data: &[u8], depth: u8, builder: &mut Builder, ) -> Result<()> { type Flags = CompositeGlyphFlags; if depth >= MAX_COMPONENTS { return Ok(()); } let mut s = Stream::new(glyph_data); let flags = Flags::from_bits_truncate(s.read()?); let glyph_id: GlyphId = s.read()?; let mut ts = Transform::default(); if flags.contains(Flags::ARGS_ARE_XY_VALUES) { if flags.contains(Flags::ARG_1_AND_2_ARE_WORDS) { ts.e = s.read::()? as f32; ts.f = s.read::()? as f32; } else { ts.e = s.read::()? as f32; ts.f = s.read::()? as f32; } } if flags.contains(Flags::WE_HAVE_A_TWO_BY_TWO) { ts.a = s.read_f2_14()?; ts.b = s.read_f2_14()?; ts.c = s.read_f2_14()?; ts.d = s.read_f2_14()?; } else if flags.contains(Flags::WE_HAVE_AN_X_AND_Y_SCALE) { ts.a = s.read_f2_14()?; ts.d = s.read_f2_14()?; } else if flags.contains(Flags::WE_HAVE_A_SCALE) { ts.a = f32_bound(-2.0, s.read_f2_14()?, 2.0); ts.d = ts.a; } if let Ok(glyph_data) = self.glyph_data(glyph_id) { let transform = Transform::combine(builder.transform, ts); let mut b = Builder { builder: builder.builder, transform, is_default_ts: transform.is_default(), }; self.outline_impl(glyph_data, depth + 1, &mut b)?; } if flags.contains(Flags::MORE_COMPONENTS) { if depth < MAX_COMPONENTS { self.parse_composite_outline(s.tail()?, depth + 1, builder)?; } } Ok(()) } } #[derive(Clone, Copy)] struct Transform { a: f32, b: f32, c: f32, d: f32, e: f32, f: f32, } impl Transform { #[inline] fn combine(ts1: Self, ts2: Self) -> Self { Transform { a: ts1.a * ts2.a + ts1.c * ts2.b, b: ts1.b * ts2.a + ts1.d * ts2.b, c: ts1.a * ts2.c + ts1.c * ts2.d, d: ts1.b * ts2.c + ts1.d * ts2.d, e: ts1.a * ts2.e + ts1.c * ts2.f + ts1.e, f: ts1.b * ts2.e + ts1.d * ts2.f + ts1.f, } } #[inline] fn apply_to(&self, x: &mut f32, y: &mut f32) { let tx = *x; let ty = *y; *x = self.a * tx + self.c * ty + self.e; *y = self.b * tx + self.d * ty + self.f; } #[inline] fn is_default(&self) -> bool { // A direct float comparison is fine in our case. self.a == 1.0 && self.b == 0.0 && self.c == 0.0 && self.d == 1.0 && self.e == 0.0 && self.f == 0.0 } } impl Default for Transform { #[inline] fn default() -> Self { Transform { a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: 0.0, f: 0.0 } } } impl core::fmt::Debug for Transform { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "Transform({} {} {} {} {} {})", self.a, self.b, self.c, self.d, self.e, self.f) } } #[derive(Clone, Copy, Debug)] struct GlyphPoint { x: i16, y: i16, on_curve_point: bool, } struct GlyphPoints<'a> { flags: Stream<'a>, x_coords: Stream<'a>, y_coords: Stream<'a>, points_left: u16, flag_repeats: u8, last_flags: SimpleGlyphFlags, x: i16, y: i16, } impl<'a> Iterator for GlyphPoints<'a> { type Item = GlyphPoint; fn next(&mut self) -> Option { type Flags = SimpleGlyphFlags; if self.points_left == 0 { return None; } if self.flag_repeats == 0 { self.last_flags = Flags::from_bits_truncate(self.flags.read().ok()?); if self.last_flags.contains(Flags::REPEAT_FLAG) { self.flag_repeats = self.flags.read().ok()?; } } else { self.flag_repeats -= 1; } let x = get_glyph_coord( self.last_flags, Flags::X_SHORT_VECTOR, Flags::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR, &mut self.x_coords, ).ok()?; self.x = self.x.wrapping_add(x); let y = get_glyph_coord( self.last_flags, Flags::Y_SHORT_VECTOR, Flags::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR, &mut self.y_coords, ).ok()?; self.y = self.y.wrapping_add(y); self.points_left -= 1; Some(GlyphPoint { x: self.x, y: self.y, on_curve_point: self.last_flags.contains(Flags::ON_CURVE_POINT), }) } } fn get_glyph_coord( flags: SimpleGlyphFlags, short_vector: SimpleGlyphFlags, is_same_or_positive_short_vector: SimpleGlyphFlags, coords: &mut Stream, ) -> Result { let flags = ( flags.contains(short_vector), flags.contains(is_same_or_positive_short_vector), ); Ok(match flags { (true, true) => { coords.read::()? as i16 } (true, false) => { -(coords.read::()? as i16) } (false, true) => { // Keep previous coordinate. 0 } (false, false) => { coords.read()? } }) } #[derive(Clone, Copy, Debug)] struct Point { x: f32, y: f32, } impl Point { #[inline] fn lerp(&self, other: Point, t: f32) -> Point { Point { x: self.x + t * (other.x - self.x), y: self.y + t * (other.y - self.y), } } } ttf-parser-0.3.0/src/head.rs010064400017500001750000000014731354314235500140720ustar0000000000000000// https://docs.microsoft.com/en-us/typography/opentype/spec/head use crate::Font; #[derive(Clone, Copy, PartialEq, Debug)] pub(crate) enum IndexToLocationFormat { Short, Long, } impl<'a> Font<'a> { #[inline] pub(crate) fn index_to_location_format(&self) -> Option { match self.head.index_to_loc_format() { 0 => Some(IndexToLocationFormat::Short), 1 => Some(IndexToLocationFormat::Long), _ => None, } } /// Returns font's units per EM. /// /// Returns `None` if value is not in a 16..16384 range. #[inline] pub fn units_per_em(&self) -> Option { let num = self.head.units_per_em(); if num >= 16 && num <= 16384 { Some(num) } else { None } } } ttf-parser-0.3.0/src/hhea.rs010064400017500001750000000013211354314235500140660ustar0000000000000000// https://docs.microsoft.com/en-us/typography/opentype/spec/hhea use crate::Font; impl<'a> Font<'a> { /// Returns font's ascender value. #[inline] pub fn ascender(&self) -> i16 { self.hhea.ascender() } /// Returns font's descender value. #[inline] pub fn descender(&self) -> i16 { self.hhea.descender() } /// Returns font's height. #[inline] pub fn height(&self) -> i16 { self.ascender() - self.descender() } /// Returns font's line gap. #[inline] pub fn line_gap(&self) -> i16 { self.hhea.line_gap() } #[inline] pub(crate) fn number_of_hmetrics(&self) -> u16 { self.hhea.number_of_h_metrics() } } ttf-parser-0.3.0/src/hmtx.rs010064400017500001750000000041661354314235500141530ustar0000000000000000// https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx use crate::parser::{Stream, LazyArray}; use crate::{Font, TableName, GlyphId, HorizontalMetrics, Result, Error}; use crate::raw::hmtx as raw; impl<'a> Font<'a> { /// Returns glyph's horizontal metrics. pub fn glyph_hor_metrics(&self, glyph_id: GlyphId) -> Result { self.check_glyph_id(glyph_id)?; let data = self.hmtx.ok_or_else(|| Error::TableMissing(TableName::HorizontalMetrics))?; let mut s = Stream::new(data); let number_of_hmetrics = self.number_of_hmetrics(); if number_of_hmetrics == 0 { return Err(Error::NoHorizontalMetrics); } let glyph_id = glyph_id.0; let array: LazyArray = s.read_array(number_of_hmetrics)?; if let Some(metrics) = array.get(glyph_id) { Ok(HorizontalMetrics { advance: metrics.advance_width(), left_side_bearing: metrics.lsb(), }) } else { // 'If the number_of_hmetrics is less than the total number of glyphs, // then that array is followed by an array for the left side bearing values // of the remaining glyphs.' // Check for overflow. if self.number_of_glyphs() < number_of_hmetrics { return Err(Error::NoHorizontalMetrics); } let count = self.number_of_glyphs() - number_of_hmetrics; let left_side_bearings: LazyArray = s.read_array(count)?; let left_side_bearing = left_side_bearings.get(glyph_id) .ok_or_else(|| Error::NoHorizontalMetrics)?; // 'As an optimization, the number of records can be less than the number of glyphs, // in which case the advance width value of the last record applies // to all remaining glyph IDs.' let last_metric = array.last().ok_or_else(|| Error::NoHorizontalMetrics)?; Ok(HorizontalMetrics { advance: last_metric.advance_width(), left_side_bearing, }) } } } ttf-parser-0.3.0/src/kern.rs010064400017500001750000000055771354314235500141410ustar0000000000000000// https://docs.microsoft.com/en-us/typography/opentype/spec/kern use crate::parser::{Stream, FromData, SafeStream, LazyArray}; use crate::{Font, GlyphId, TableName, Result, Error}; impl<'a> Font<'a> { /// Returns a glyphs pair kerning. /// /// Only horizontal kerning is supported. pub fn glyphs_kerning(&self, glyph_id1: GlyphId, glyph_id2: GlyphId) -> Result { self.check_glyph_id(glyph_id1)?; self.check_glyph_id(glyph_id2)?; let data = self.kern.ok_or_else(|| Error::TableMissing(TableName::Kerning))?; let mut s = Stream::new(data); let version: u16 = s.read()?; if version != 0 { return Err(Error::UnsupportedTableVersion(TableName::Kerning, version)); } let number_of_subtables: u16 = s.read()?; // TODO: Technically, we have to iterate over all tables, // but I'm not sure how exactly this should be implemented. // Also, I have to find a font, that actually has more that one table. if number_of_subtables == 0 { return Err(Error::NoKerning); } s.skip::(); // subtable_version s.skip::(); // length let coverage: Coverage = s.read()?; if !coverage.is_horizontal() { return Err(Error::NoKerning); } if coverage.format != 0 { return Err(Error::NoKerning); } parse_format1(&mut s, glyph_id1, glyph_id2) } } fn parse_format1(s: &mut Stream, glyph_id1: GlyphId, glyph_id2: GlyphId) -> Result { let number_of_pairs: u16 = s.read()?; s.skip_len(6u32); // search_range (u16) + entry_selector (u16) + range_shift (u16) let pairs: LazyArray = s.read_array(number_of_pairs)?; let needle = (glyph_id1.0 as u32) << 16 | glyph_id2.0 as u32; match pairs.binary_search_by(|v| v.pair.cmp(&needle)) { Some(v) => Ok(v.value), None => Err(Error::NoKerning), } } struct KerningRecord { pair: u32, value: i16, } impl FromData for KerningRecord { const SIZE: usize = 6; // Override, since `size_of` will be 8 because of padding. #[inline] fn parse(data: &[u8]) -> Self { let mut s = SafeStream::new(data); KerningRecord { pair: s.read(), value: s.read(), } } } // https://docs.microsoft.com/en-us/typography/opentype/spec/kern struct Coverage { coverage: u8, format: u8, } impl Coverage { const HORIZONTAL_BIT: u8 = 0; #[inline] fn is_horizontal(&self) -> bool { (self.coverage >> Coverage::HORIZONTAL_BIT) & 1 == 1 } } impl FromData for Coverage { #[inline] fn parse(data: &[u8]) -> Self { let mut s = SafeStream::new(data); Coverage { // Reverse order, since we're reading a big-endian u16. format: s.read(), coverage: s.read(), } } } ttf-parser-0.3.0/src/lib.rs010064400017500001750000000561041354314235500137400ustar0000000000000000/*! A high-level, safe, zero-allocation TrueType font parser. ## Features - A high-level API. - Zero allocations. - Zero dependencies. - `no_std` compatible. - Fast. - Stateless. - Simple and maintainable code (no magic numbers). ## Supported TrueType features - (`cmap`) Character to glyph index mapping using [glyph_index()] method.
All subtable formats except Mixed Coverage (8) are supported. - (`cmap`) Character variation to glyph index mapping using [glyph_variation_index()] method. - (`glyf`) Glyph outlining using [outline_glyph()] method. - (`hmtx`) Retrieving a glyph's horizontal metrics using [glyph_hor_metrics()] method. - (`vmtx`) Retrieving a glyph's vertical metrics using [glyph_ver_metrics()] method. - (`kern`) Retrieving a glyphs pair kerning using [glyphs_kerning()] method. - (`maxp`) Retrieving a total number of glyphs using [number_of_glyphs()] method. - (`name`) Listing all name records using [names()] method. - (`name`) Retrieving a font's family name using [family_name()] method. - (`name`) Retrieving a font's PostScript name using [post_script_name()] method. - (`post`) Retrieving a font's underline metrics name using [underline_metrics()] method. - (`head`) Retrieving a font's units per EM value using [units_per_em()] method. - (`hhea`) Retrieving a generic font info using: [ascender()], [descender()], [height()] and [line_gap()] methods. [glyph_index()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyph_index [glyph_variation_index()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyph_variation_index [outline_glyph()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.outline_glyph [glyph_hor_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyph_hor_metrics [glyph_ver_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyph_ver_metrics [glyphs_kerning()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyphs_kerning [number_of_glyphs()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.number_of_glyphs [names()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.names [family_name()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.family_name [post_script_name()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.post_script_name [underline_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.underline_metrics [units_per_em()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.units_per_em [ascender()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.ascender [descender()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.descender [height()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.height [line_gap()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.line_gap ## Supported OpenType features - (`CFF `) Glyph outlining using [outline_glyph()] method. - (`OS/2`) Retrieving a font kind using [is_regular()], [is_italic()], [is_bold()] and [is_oblique()] methods. - (`OS/2`) Retrieving a font's weight using [weight()] method. - (`OS/2`) Retrieving a font's width using [width()] method. - (`OS/2`) Retrieving a font's X height using [x_height()] method. - (`OS/2`) Retrieving a font's strikeout metrics using [strikeout_metrics()] method. - (`OS/2`) Retrieving a font's subscript metrics using [subscript_metrics()] method. - (`OS/2`) Retrieving a font's superscript metrics using [superscript_metrics()] method. [is_regular()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.is_regular [is_italic()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.is_italic [is_bold()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.is_bold [is_oblique()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.is_oblique [weight()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.weight [width()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.width [x_height()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.x_height [strikeout_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.strikeout_metrics [subscript_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.subscript_metrics [superscript_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.superscript_metrics ## Methods' computational complexity TrueType fonts designed for fast querying, so most of the methods are very fast. The main exception is glyph outlining. Glyphs can be stored using two different methods: using [Glyph Data](https://docs.microsoft.com/en-us/typography/opentype/spec/glyf) format and [Compact Font Format](http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5176.CFF.pdf) (pdf). The first one is fairly simple which makes it faster to process. The second one is basically a tiny language with a stack-based VM, which makes it way harder to process. Currently, it takes 60% more time to outline all glyphs in *SourceSansPro-Regular.otf* (which uses CFF) rather than in *SourceSansPro-Regular.ttf*. ```text test outline_cff ... bench: 1,617,138 ns/iter (+/- 1,261) test outline_glyf ... bench: 995,771 ns/iter (+/- 2,801) ``` Here is some methods benchmarks: ```text test outline_glyph_276_from_cff ... bench: 1,203 ns/iter (+/- 4) test outline_glyph_276_from_glyf ... bench: 796 ns/iter (+/- 3) test outline_glyph_8_from_cff ... bench: 497 ns/iter (+/- 3) test from_data_otf ... bench: 372 ns/iter (+/- 5) test outline_glyph_8_from_glyf ... bench: 347 ns/iter (+/- 1) test family_name ... bench: 269 ns/iter (+/- 3) test from_data_ttf ... bench: 72 ns/iter (+/- 3) test glyph_index_u41 ... bench: 24 ns/iter (+/- 0) test glyph_2_hor_metrics ... bench: 8 ns/iter (+/- 0) ``` `family_name` is expensive, because it allocates a `String` and the original data is stored as UTF-16 BE. Some methods are too fast, so we execute them **1000 times** to get better measurements. ```text test x_height ... bench: 847 ns/iter (+/- 0) test units_per_em ... bench: 564 ns/iter (+/- 2) test strikeout_metrics ... bench: 564 ns/iter (+/- 0) test width ... bench: 287 ns/iter (+/- 0) test ascender ... bench: 279 ns/iter (+/- 1) test subscript_metrics ... bench: 279 ns/iter (+/- 0) ``` ## Safety - The library must not panic. Any panic considered as a critical bug and should be reported. - The library has a single `unsafe` block for array casting. */ #![doc(html_root_url = "https://docs.rs/ttf-parser/0.3.0")] #![no_std] #![warn(missing_docs)] #![warn(missing_copy_implementations)] #![warn(missing_debug_implementations)] #[cfg(feature = "std")] extern crate std; use core::fmt; mod cff; mod cmap; mod glyf; mod head; mod hhea; mod hmtx; mod kern; mod loca; mod name; mod os2; mod parser; mod post; mod raw; mod vhea; mod vmtx; use parser::{Stream, FromData, SafeStream, TrySlice, LazyArray}; pub use cff::CFFError; pub use name::*; pub use os2::*; /// A type-safe wrapper for glyph ID. #[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Debug)] pub struct GlyphId(pub u16); impl FromData for GlyphId { #[inline] fn parse(data: &[u8]) -> Self { let mut s = SafeStream::new(data); GlyphId(s.read()) } } /// A font parsing error. #[derive(Clone, Copy, Debug)] pub enum Error { /// Not a TrueType data. NotATrueType, /// The font index is out of bounds. FontIndexOutOfBounds, /// One of the required tables is missing. TableMissing(TableName), /// Table has an invalid size. InvalidTableSize(TableName), /// Font doesn't have such glyph ID. NoGlyph, /// Glyph doesn't have an outline. NoOutline, /// An invalid glyph class. InvalidGlyphClass(u16), /// No horizontal metrics for this glyph. NoHorizontalMetrics, /// No vertical metrics for this glyph. NoVerticalMetrics, /// No kerning for this glyph. NoKerning, /// An unsupported table version. UnsupportedTableVersion(TableName, u16), /// A CFF table parsing error. CFFError(CFFError), /// An attempt to slice a raw data out of bounds. /// /// This may be caused by a bug in the library or by a malformed font. #[allow(missing_docs)] SliceOutOfBounds { // u32 is enough, since fonts are usually times smaller. start: u32, end: u32, data_len: u32, }, } impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { match *self { Error::NotATrueType => { write!(f, "not a TrueType font") } Error::FontIndexOutOfBounds => { write!(f, "font index is out of bounds") } Error::TableMissing(name) => { write!(f, "font doesn't have a {:?} table", name) } Error::InvalidTableSize(name) => { write!(f, "table {:?} has an invalid size", name) } Error::SliceOutOfBounds { start, end, data_len } => { write!(f, "an attempt to slice {}..{} on 0..{}", start, end, data_len) } Error::NoGlyph => { write!(f, "font doesn't have such glyph ID") } Error::NoOutline => { write!(f, "glyph has no outline") } Error::InvalidGlyphClass(n) => { write!(f, "{} is not a valid glyph class", n) } Error::NoHorizontalMetrics => { write!(f, "glyph has no horizontal metrics") } Error::NoVerticalMetrics => { write!(f, "glyph has no vertical metrics") } Error::NoKerning => { write!(f, "glyph has no kerning") } Error::UnsupportedTableVersion(name, version) => { write!(f, "table {:?} with version {} is not supported", name, version) } Error::CFFError(e) => { write!(f, "{:?} table parsing failed cause {}", TableName::CompactFontFormat, e) } } } } impl From for Error { #[inline] fn from(e: CFFError) -> Self { Error::CFFError(e) } } #[cfg(feature = "std")] impl std::error::Error for Error {} pub(crate) type Result = core::result::Result; /// A line metrics. /// /// Used for underline and strikeout. #[derive(Clone, Copy, PartialEq, Debug)] pub struct LineMetrics { /// Line position. pub position: i16, /// Line thickness. pub thickness: i16, } /// A horizontal metrics of a glyph. #[derive(Clone, Copy, PartialEq, Debug)] pub struct HorizontalMetrics { /// A horizontal advance. pub advance: u16, /// Left side bearing. pub left_side_bearing: i16, } /// A vertical metrics of a glyph. #[derive(Clone, Copy, PartialEq, Debug)] pub struct VerticalMetrics { /// A vertical advance. pub advance: u16, /// Top side bearing. pub top_side_bearing: i16, } /// Rectangle. #[derive(Clone, Copy, PartialEq, Debug)] #[allow(missing_docs)] pub struct Rect { pub x_min: i16, pub y_min: i16, pub x_max: i16, pub y_max: i16, } impl Rect { #[inline] pub(crate) fn zero() -> Self { Rect { x_min: 0, y_min: 0, x_max: 0, y_max: 0, } } } /// A trait for glyph outline construction. pub trait OutlineBuilder { /// Appends a MoveTo segment. /// /// Start of a contour. fn move_to(&mut self, x: f32, y: f32); /// Appends a LineTo segment. fn line_to(&mut self, x: f32, y: f32); /// Appends a QuadTo segment. fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32); /// Appends a CurveTo segment. fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32); /// Appends a ClosePath segment. /// /// End of a contour. fn close(&mut self); } /// A table name. #[derive(Clone, Copy, PartialEq, Debug)] #[allow(missing_docs)] pub enum TableName { CharacterToGlyphIndexMapping, CompactFontFormat, GlyphData, Header, HorizontalHeader, HorizontalMetrics, IndexToLocation, Kerning, MaximumProfile, Naming, PostScript, VerticalHeader, VerticalMetrics, WindowsMetrics, } /// A font data handle. #[derive(Clone)] pub struct Font<'a> { head: raw::head::Table<'a>, hhea: raw::hhea::Table<'a>, cff_: Option<&'a [u8]>, cmap: Option<&'a [u8]>, glyf: Option<&'a [u8]>, hmtx: Option<&'a [u8]>, kern: Option<&'a [u8]>, loca: Option<&'a [u8]>, name: Option<&'a [u8]>, os_2: Option<&'a [u8]>, os_2_v0: Option>, post: Option>, vhea: Option>, vmtx: Option<&'a [u8]>, number_of_glyphs: GlyphId, cff_metadata: cff::Metadata, } impl<'a> Font<'a> { /// Creates a `Font` object from a raw data. /// /// You can set `index` in case of font collections. /// For simple `ttf` fonts set `index` to 0. /// /// This function only parses font tables, so it's relatively light. /// /// Required tables: `head` and `hhea`. pub fn from_data(data: &'a [u8], index: u32) -> Result { let table_data = if let Some(n) = fonts_in_collection(data) { if index < n { // https://docs.microsoft.com/en-us/typography/opentype/spec/otff#ttc-header const OFFSET_32_SIZE: usize = 4; let offset = raw::TTCHeader::SIZE + OFFSET_32_SIZE * index as usize; let font_offset: u32 = Stream::read_at(data, offset)?; data.try_slice(font_offset as usize .. data.len())? } else { return Err(Error::FontIndexOutOfBounds); } } else { data }; // https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font const OFFSET_TABLE_SIZE: usize = 12; if data.len() < OFFSET_TABLE_SIZE { return Err(Error::NotATrueType); } // https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font const SFNT_VERSION_TRUE_TYPE: u32 = 0x00010000; const SFNT_VERSION_OPEN_TYPE: u32 = 0x4F54544F; let mut s = Stream::new(table_data); let sfnt_version: u32 = s.read()?; if sfnt_version != SFNT_VERSION_TRUE_TYPE && sfnt_version != SFNT_VERSION_OPEN_TYPE { return Err(Error::NotATrueType); } let num_tables: u16 = s.read()?; s.skip_len(6u32); // searchRange (u16) + entrySelector (u16) + rangeShift (u16) let tables: LazyArray = s.read_array(num_tables)?; let mut font = Font { head: raw::head::Table::new(&[0; raw::head::Table::SIZE]), hhea: raw::hhea::Table::new(&[0; raw::hhea::Table::SIZE]), cff_: None, cmap: None, glyf: None, hmtx: None, kern: None, loca: None, name: None, os_2: None, os_2_v0: None, post: None, vhea: None, vmtx: None, number_of_glyphs: GlyphId(0), cff_metadata: cff::Metadata::default(), }; let mut has_head = false; let mut has_hhea = false; for table in tables { let offset = table.offset() as usize; let length = table.length() as usize; let range = offset..(offset + length); // It's way faster to compare `[u8; 4]` with `&[u8]` // rather than `&[u8]` with `&[u8]`. match &table.table_tag() { b"head" => { if length != raw::head::Table::SIZE { return Err(Error::InvalidTableSize(TableName::Header)); } font.head = raw::head::Table::new(data.try_slice(range)?); has_head = true; } b"hhea" => { if length != raw::hhea::Table::SIZE { return Err(Error::InvalidTableSize(TableName::HorizontalHeader)); } font.hhea = raw::hhea::Table::new(data.try_slice(range)?); has_hhea = true; } b"maxp" => { if length < raw::maxp::Table::SIZE { return Err(Error::InvalidTableSize(TableName::MaximumProfile)); } let data = &data[offset..(offset + raw::maxp::Table::SIZE)]; let table = raw::maxp::Table::new(data); font.number_of_glyphs = GlyphId(table.num_glyphs()); } b"OS/2" => { if length < raw::os_2::TableV0::SIZE { return Err(Error::InvalidTableSize(TableName::WindowsMetrics)); } if let Some(data) = data.get(range) { font.os_2 = Some(data); let data = &data[0..raw::os_2::TableV0::SIZE]; font.os_2_v0 = Some(raw::os_2::TableV0::new(data)); } } b"post" => { if length < raw::post::Table::SIZE { return Err(Error::InvalidTableSize(TableName::PostScript)); } let data = data.try_slice(offset..(offset + raw::post::Table::SIZE))?; font.post = Some(raw::post::Table::new(data)); } b"vhea" => { if length != raw::vhea::Table::SIZE { return Err(Error::InvalidTableSize(TableName::VerticalHeader)); } font.vhea = data.get(range).map(raw::vhea::Table::new); } b"CFF " => { if let Some(data) = data.get(range) { if let Ok(metadata) = cff::parse_metadata(data) { font.cff_ = Some(data); font.cff_metadata = metadata; } } } b"cmap" => font.cmap = data.get(range), b"glyf" => font.glyf = data.get(range), b"hmtx" => font.hmtx = data.get(range), b"kern" => font.kern = data.get(range), b"loca" => font.loca = data.get(range), b"name" => font.name = data.get(range), b"vmtx" => font.vmtx = data.get(range), _ => {} } } // Check for mandatory tables. if !has_head { return Err(Error::TableMissing(TableName::Header)); } if !has_hhea { return Err(Error::TableMissing(TableName::HorizontalHeader)); } Ok(font) } /// Checks that font has a specified table. #[inline] pub fn has_table(&self, name: TableName) -> bool { match name { TableName::Header => true, TableName::HorizontalHeader => true, TableName::MaximumProfile => true, TableName::CharacterToGlyphIndexMapping => self.cmap.is_some(), TableName::CompactFontFormat => self.cff_.is_some(), TableName::GlyphData => self.glyf.is_some(), TableName::HorizontalMetrics => self.hmtx.is_some(), TableName::IndexToLocation => self.loca.is_some(), TableName::Kerning => self.kern.is_some(), TableName::Naming => self.name.is_some(), TableName::PostScript => self.post.is_some(), TableName::VerticalHeader => self.vhea.is_some(), TableName::VerticalMetrics => self.vmtx.is_some(), TableName::WindowsMetrics => self.os_2.is_some(), } } /// Returns a total number of glyphs in the font. /// /// The value was already parsed, so this function doesn't involve any parsing. #[inline] pub fn number_of_glyphs(&self) -> u16 { self.number_of_glyphs.0 } #[inline] pub(crate) fn check_glyph_id(&self, glyph_id: GlyphId) -> Result<()> { if glyph_id < self.number_of_glyphs { Ok(()) } else { Err(Error::NoGlyph) } } /// Outlines a glyph. Returns a tight glyph bounding box. /// /// This method support both `glyf` and `CFF` tables. /// /// # Example /// /// ``` /// use std::fmt::Write; /// use ttf_parser; /// /// struct Builder(String); /// /// impl ttf_parser::OutlineBuilder for Builder { /// fn move_to(&mut self, x: f32, y: f32) { /// write!(&mut self.0, "M {} {} ", x, y).unwrap(); /// } /// /// fn line_to(&mut self, x: f32, y: f32) { /// write!(&mut self.0, "L {} {} ", x, y).unwrap(); /// } /// /// fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { /// write!(&mut self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap(); /// } /// /// fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { /// write!(&mut self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap(); /// } /// /// fn close(&mut self) { /// write!(&mut self.0, "Z ").unwrap(); /// } /// } /// /// let data = std::fs::read("tests/fonts/glyphs.ttf").unwrap(); /// let font = ttf_parser::Font::from_data(&data, 0).unwrap(); /// let mut builder = Builder(String::new()); /// let glyph = font.outline_glyph(ttf_parser::GlyphId(0), &mut builder).unwrap(); /// assert_eq!(builder.0, "M 50 0 L 50 750 L 450 750 L 450 0 L 50 0 Z "); /// ``` #[inline] pub fn outline_glyph( &self, glyph_id: GlyphId, builder: &mut impl OutlineBuilder, ) -> Result { if self.glyf.is_some() { self.glyf_glyph_outline(glyph_id, builder) } else if self.cff_.is_some() { self.cff_glyph_outline(glyph_id, builder) } else { Err(Error::NoGlyph) } } } impl fmt::Debug for Font<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Font()") } } /// Returns a number of fonts stored in a TrueType font collection. /// /// Returns `None` if a provided data is not a TrueType font collection. #[inline] pub fn fonts_in_collection(data: &[u8]) -> Option { let table = raw::TTCHeader::new(data.get(0..raw::TTCHeader::SIZE)?); if &table.ttc_tag() != b"ttcf" { return None; } Some(table.num_fonts()) } ttf-parser-0.3.0/src/loca.rs010064400017500001750000000036601354314235500141070ustar0000000000000000// https://docs.microsoft.com/en-us/typography/opentype/spec/loca use core::ops::Range; use crate::parser::{Stream, LazyArray}; use crate::{Font, GlyphId, TableName, Result, Error}; impl<'a> Font<'a> { /// https://docs.microsoft.com/en-us/typography/opentype/spec/loca pub(crate) fn glyph_range(&self, glyph_id: GlyphId) -> Result> { use crate::head::IndexToLocationFormat as Format; // Check for overflow. if self.number_of_glyphs() == core::u16::MAX { return Err(Error::NoGlyph); } let glyph_id = glyph_id.0; if glyph_id == core::u16::MAX { return Err(Error::NoGlyph); } let total = self.number_of_glyphs() + 1; // Glyph ID must be smaller than total number of values in a `loca` array. if glyph_id + 1 >= total { return Err(Error::NoGlyph); } let format = self.index_to_location_format().ok_or(Error::NoGlyph)?; let data = self.loca.ok_or_else(|| Error::TableMissing(TableName::IndexToLocation))?; let mut s = Stream::new(data); let range = match format { Format::Short => { let array: LazyArray = s.read_array(total)?; // 'The actual local offset divided by 2 is stored.' array.at(glyph_id) as usize * 2 .. array.at(glyph_id + 1) as usize * 2 } Format::Long => { let array: LazyArray = s.read_array(total)?; array.at(glyph_id) as usize .. array.at(glyph_id + 1) as usize } }; // TODO: use Range::is_empty as soon as it became stable if range.start == range.end { // No outline. Err(Error::NoOutline) } else if range.start > range.end { // 'The offsets must be in ascending order.' Err(Error::NoGlyph) } else { Ok(range) } } } ttf-parser-0.3.0/src/name.rs010064400017500001750000000240271354314235500141110ustar0000000000000000// https://docs.microsoft.com/en-us/typography/opentype/spec/name #[cfg(feature = "std")] use std::vec::Vec; #[cfg(feature = "std")] use std::string::String; #[cfg(feature = "std")] use crate::parser::LazyArray; use crate::parser::Stream; use crate::{Font, TableName, Result, Error}; use crate::raw::name as raw; /// A list of [name ID](https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids)'s. pub mod name_id { #![allow(missing_docs)] pub const COPYRIGHT_NOTICE: u16 = 0; pub const FAMILY: u16 = 1; pub const SUBFAMILY: u16 = 2; pub const UNIQUE_ID: u16 = 3; pub const FULL_NAME: u16 = 4; pub const VERSION: u16 = 5; pub const POST_SCRIPT_NAME: u16 = 6; pub const TRADEMARK: u16 = 7; pub const MANUFACTURER: u16 = 8; pub const DESIGNER: u16 = 9; pub const DESCRIPTION: u16 = 10; pub const VENDOR_URL: u16 = 11; pub const DESIGNER_URL: u16 = 12; pub const LICENSE: u16 = 13; pub const LICENSE_URL: u16 = 14; // RESERVED = 15 pub const TYPOGRAPHIC_FAMILY: u16 = 16; pub const TYPOGRAPHIC_SUBFAMILY: u16 = 17; pub const COMPATIBLE_FULL: u16 = 18; pub const SAMPLE_TEXT: u16 = 19; pub const POST_SCRIPT_CID: u16 = 20; pub const WWS_FAMILY: u16 = 21; pub const WWS_SUBFAMILY: u16 = 22; pub const LIGHT_BACKGROUND_PALETTE: u16 = 23; pub const DARK_BACKGROUND_PALETTE: u16 = 24; pub const VARIATIONS_POST_SCRIPT_NAME_PREFIX: u16 = 25; } /// A [platform ID](https://docs.microsoft.com/en-us/typography/opentype/spec/name#platform-ids). #[derive(Clone, Copy, PartialEq, Debug)] #[allow(missing_docs)] pub enum PlatformId { Unicode, Macintosh, Iso, Windows, Custom, } impl PlatformId { pub(crate) fn from_u16(n: u16) -> Option { match n { 0 => Some(PlatformId::Unicode), 1 => Some(PlatformId::Macintosh), 2 => Some(PlatformId::Iso), 3 => Some(PlatformId::Windows), 4 => Some(PlatformId::Custom), _ => None, } } } #[cfg(feature = "std")] #[inline] fn is_unicode_encoding(platform_id: PlatformId, encoding_id: u16) -> bool { // https://docs.microsoft.com/en-us/typography/opentype/spec/name#windows-encoding-ids const WINDOWS_UNICODE_BMP_ENCODING_ID: u16 = 1; match platform_id { PlatformId::Unicode => true, PlatformId::Windows if encoding_id == WINDOWS_UNICODE_BMP_ENCODING_ID => true, _ => false, } } /// A [Name Record](https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-records). #[derive(Clone, Copy)] pub struct Name<'a> { data: raw::NameRecord, strings: &'a [u8], } impl<'a> Name<'a> { /// Parses the platform ID. pub fn platform_id(&self) -> Option { PlatformId::from_u16(self.data.platform_id()) } /// Parses the platform-specific encoding ID. pub fn encoding_id(&self) -> u16 { self.data.encoding_id() } /// Parses the language ID. pub fn language_id(&self) -> u16 { self.data.language_id() } /// Parses the [Name ID](https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids). /// /// A predefined list of ID's can be found in the [`name_id`](name_id/index.html) module. pub fn name_id(&self) -> u16 { self.data.name_id() } /// Parses the Name's data as bytes. /// /// Can be empty. pub fn name(&self) -> &'a [u8] { let start = self.data.offset() as usize; let end = start + self.data.length() as usize; self.strings.get(start..end).unwrap_or(&[]) } /// Parses the Name's data as a UTF-8 string. /// /// Only Unicode names are supported. And since they are stored as UTF-16BE, /// we can't return `&str` and have to allocate a `String`. /// /// Supports: /// - Unicode Platform ID /// - Windows Platform ID + Unicode BMP #[cfg(feature = "std")] #[inline(never)] pub fn name_utf8(&self) -> Option { if self.is_unicode() { self.name_from_utf16_be() } else { None } } #[cfg(feature = "std")] #[inline] fn is_unicode(&self) -> bool { is_unicode_encoding(self.platform_id().unwrap(), self.encoding_id()) } #[cfg(feature = "std")] #[inline(never)] fn name_from_utf16_be(&self) -> Option { let mut name: Vec = Vec::new(); for c in LazyArray::new(self.name()) { name.push(c); } String::from_utf16(&name).ok() } } #[cfg(feature = "std")] impl<'a> core::fmt::Debug for Name<'a> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { // TODO: https://github.com/rust-lang/rust/issues/50264 let name = self.name_utf8(); f.debug_struct("Name") .field("name", &name.as_ref().map(core::ops::Deref::deref) .unwrap_or("unsupported encoding")) .field("platform_id", &self.platform_id()) .field("encoding_id", &self.encoding_id()) .field("language_id", &self.language_id()) .field("name_id", &self.name_id()) .finish() } } #[cfg(not(feature = "std"))] impl<'a> core::fmt::Debug for Name<'a> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_struct("Name") .field("name", &self.name()) .field("platform_id", &self.platform_id()) .field("encoding_id", &self.encoding_id()) .field("language_id", &self.language_id()) .field("name_id", &self.name_id()) .finish() } } /// An iterator over font's names. #[derive(Clone, Copy)] #[allow(missing_debug_implementations)] pub struct Names<'a> { names: &'a [u8], storage: &'a [u8], index: u16, total: u16, } impl<'a> Names<'a> { fn new(names: &'a [u8], storage: &'a [u8]) -> Self { Names { names, storage, index: 0, total: (names.len() / raw::NameRecord::SIZE) as u16, } } } impl<'a> Iterator for Names<'a> { type Item = Name<'a>; fn next(&mut self) -> Option { if self.index < self.total { self.index += 1; self.nth(self.index as usize - 1) } else { None } } fn nth(&mut self, n: usize) -> Option { let start = raw::NameRecord::SIZE * n; let end = start + raw::NameRecord::SIZE; let data = self.names.get(start..end)?; Some(Name { data: raw::NameRecord::new(data), strings: self.storage, }) } } impl<'a> Font<'a> { /// Returns an iterator over [Name Records]. /// /// [Name Records]: https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-records pub fn names(&self) -> Names { match self._names() { Ok(v) => v, Err(_) => Names { names: &[], storage: &[], index: 0, total: 0 }, } } #[inline(never)] fn _names(&self) -> Result { // https://docs.microsoft.com/en-us/typography/opentype/spec/name#naming-table-format-1 const LANG_TAG_RECORD_SIZE: u16 = 4; let data = self.name.ok_or_else(|| Error::TableMissing(TableName::Naming))?; let mut s = Stream::new(data); let format: u16 = s.read()?; let count: u16 = s.read()?; s.skip::(); // offset if format == 0 { Ok(Names::new(s.read_bytes(raw::NameRecord::SIZE as u32 * count as u32)?, s.tail()?)) } else if format == 1 { let lang_tag_count: u16 = s.read()?; let lang_tag_len = lang_tag_count .checked_mul(LANG_TAG_RECORD_SIZE) .ok_or_else(|| Error::NotATrueType)?; s.skip_len(lang_tag_len); // langTagRecords Ok(Names::new(s.read_bytes(raw::NameRecord::SIZE as u32 * count as u32)?, s.tail()?)) } else { // Invalid format. // The error type doesn't matter, since we will ignore it anyway. Err(Error::NotATrueType) } } /// Returns font's family name. /// /// *Typographic Family* is preferred over *Family*. /// /// Note that font can have multiple names. You can use [`names()`] to list them all. /// /// [`names()`]: #method.names #[cfg(feature = "std")] pub fn family_name(&self) -> Option { let mut idx = None; let mut iter = self.names(); for (i, name) in iter.enumerate() { if name.name_id() == name_id::TYPOGRAPHIC_FAMILY && name.is_unicode() { // Break the loop as soon as we reached 'Typographic Family'. idx = Some(i); break; } else if name.name_id() == name_id::FAMILY && name.is_unicode() { idx = Some(i); // Do not break the loop since 'Typographic Family' can be set later // and it has a higher priority. } } iter.nth(idx?).and_then(|name| name.name_from_utf16_be()) } /// Returns font's PostScript name. /// /// Note that font can have multiple names. You can use [`names()`] to list them all. /// /// [`names()`]: #method.names #[cfg(feature = "std")] pub fn post_script_name(&self) -> Option { self.names() .find(|name| name.name_id() == name_id::POST_SCRIPT_NAME && name.is_unicode()) .and_then(|name| name.name_from_utf16_be()) } } ttf-parser-0.3.0/src/os2.rs010064400017500001750000000160121354314235500136670ustar0000000000000000// https://docs.microsoft.com/en-us/typography/opentype/spec/os2 use crate::parser::Stream; use crate::{Font, LineMetrics}; macro_rules! try_opt_or { ($value:expr, $ret:expr) => { match $value { Some(v) => v, None => return $ret, } }; } /// A font [weight](https://docs.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass). #[derive(Clone, Copy, PartialEq, Debug)] #[allow(missing_docs)] pub enum Weight { Thin, ExtraLight, Light, Normal, Medium, SemiBold, Bold, ExtraBold, Black, Other(u16), } impl Weight { /// Returns a numeric representation of a weight. #[inline] pub fn to_number(&self) -> u16 { match self { Weight::Thin => 100, Weight::ExtraLight => 200, Weight::Light => 300, Weight::Normal => 400, Weight::Medium => 500, Weight::SemiBold => 600, Weight::Bold => 700, Weight::ExtraBold => 800, Weight::Black => 900, Weight::Other(n) => *n, } } } impl From for Weight { #[inline] fn from(value: u16) -> Self { match value { 100 => Weight::Thin, 200 => Weight::ExtraLight, 300 => Weight::Light, 400 => Weight::Normal, 500 => Weight::Medium, 600 => Weight::SemiBold, 700 => Weight::Bold, 800 => Weight::ExtraBold, 900 => Weight::Black, _ => Weight::Other(value), } } } impl Default for Weight { #[inline] fn default() -> Self { Weight::Normal } } /// A font [width](https://docs.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass). #[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] #[allow(missing_docs)] pub enum Width { UltraCondensed, ExtraCondensed, Condensed, SemiCondensed, Normal, SemiExpanded, Expanded, ExtraExpanded, UltraExpanded, } impl Width { /// Returns a numeric representation of a width. #[inline] pub fn to_number(&self) -> u16 { match self { Width::UltraCondensed => 1, Width::ExtraCondensed => 2, Width::Condensed => 3, Width::SemiCondensed => 4, Width::Normal => 5, Width::SemiExpanded => 6, Width::Expanded => 7, Width::ExtraExpanded => 8, Width::UltraExpanded => 9, } } } impl Default for Width { #[inline] fn default() -> Self { Width::Normal } } /// A script metrics used by subscript and superscript. #[derive(Clone, Copy, PartialEq, Debug)] pub struct ScriptMetrics { /// Horizontal font size. pub x_size: i16, /// Vertical font size. pub y_size: i16, /// X offset. pub x_offset: i16, /// Y offset. pub y_offset: i16, } // We already checked that OS/2 table has a valid length, // so it's safe to use `SafeStream`. impl<'a> Font<'a> { /// Parses font's weight. /// /// Returns `Weight::Normal` when OS/2 table is not present. #[inline] pub fn weight(&self) -> Weight { let table = try_opt_or!(self.os_2_v0, Weight::default()); Weight::from(table.us_weight_class()) } /// Parses font's width. /// /// Returns `Width::Normal` when OS/2 table is not present or when value is invalid. #[inline] pub fn width(&self) -> Width { let table = try_opt_or!(self.os_2_v0, Width::default()); match table.us_width_class() { 1 => Width::UltraCondensed, 2 => Width::ExtraCondensed, 3 => Width::Condensed, 4 => Width::SemiCondensed, 5 => Width::Normal, 6 => Width::SemiExpanded, 7 => Width::Expanded, 8 => Width::ExtraExpanded, 9 => Width::UltraExpanded, _ => Width::Normal, } } /// Checks that font is marked as *Regular*. /// /// Returns `false` when OS/2 table is not present. #[inline] pub fn is_regular(&self) -> bool { const REGULAR_FLAG: u16 = 6; self.get_fs_selection(REGULAR_FLAG) } /// Checks that font is marked as *Italic*. /// /// Returns `false` when OS/2 table is not present. #[inline] pub fn is_italic(&self) -> bool { const ITALIC_FLAG: u16 = 0; self.get_fs_selection(ITALIC_FLAG) } /// Checks that font is marked as *Bold*. /// /// Returns `false` when OS/2 table is not present. #[inline] pub fn is_bold(&self) -> bool { const BOLD_FLAG: u16 = 5; self.get_fs_selection(BOLD_FLAG) } /// Checks that font is marked as *Oblique*. /// /// Returns `None` when OS/2 table is not present or when its version is < 4. #[inline] pub fn is_oblique(&self) -> bool { let table = try_opt_or!(self.os_2_v0, false); if table.version() < 4 { return false; } const OBLIQUE_FLAG: u16 = 9; self.get_fs_selection(OBLIQUE_FLAG) } #[inline] fn get_fs_selection(&self, bit: u16) -> bool { let table = try_opt_or!(self.os_2_v0, false); (table.fs_selection() >> bit) & 1 == 1 } /// Parses font's X height. /// /// Returns `None` when OS/2 table is not present or when its version is < 2. #[inline] pub fn x_height(&self) -> Option { const SX_HEIGHT_OFFSET: usize = 86; let table = self.os_2_v0?; if table.version() < 2 { return None; } // We cannot use SafeStream here, because X height is an optional data. Stream::read_at(self.os_2?, SX_HEIGHT_OFFSET).ok() } /// Parses font's strikeout metrics. /// /// Returns `None` when OS/2 table is not present. #[inline] pub fn strikeout_metrics(&self) -> Option { let table = self.os_2_v0?; Some(LineMetrics { thickness: table.y_strikeout_size(), position: table.y_strikeout_position(), }) } /// Parses font's subscript metrics. /// /// Returns `None` when OS/2 table is not present. #[inline] pub fn subscript_metrics(&self) -> Option { let table = self.os_2_v0?; Some(ScriptMetrics { x_size: table.y_subscript_x_size(), y_size: table.y_subscript_y_size(), x_offset: table.y_subscript_x_offset(), y_offset: table.y_subscript_y_offset(), }) } /// Parses font's superscript metrics. /// /// Returns `None` when OS/2 table is not present. #[inline] pub fn superscript_metrics(&self) -> Option { let table = self.os_2_v0?; Some(ScriptMetrics { x_size: table.y_superscript_x_size(), y_size: table.y_superscript_y_size(), x_offset: table.y_superscript_x_offset(), y_offset: table.y_superscript_y_offset(), }) } } ttf-parser-0.3.0/src/parser.rs010064400017500001750000000223271354314235500144660ustar0000000000000000use core::ops::Range; use crate::{Error, Result}; pub trait FromData: Sized { /// Stores an object size in raw data. /// /// `mem::size_of` by default. /// /// Override when size of `Self` != size of a raw data. /// For example, when you are parsing `u16`, but storing it as `u8`. /// In this case `size_of::()` == 1, but `FromData::SIZE` == 2. const SIZE: usize = core::mem::size_of::(); /// Parses an object from a raw data. /// /// This method **must** not panic and **must** not read past the bounds. fn parse(data: &[u8]) -> Self; } impl FromData for u8 { #[inline] fn parse(data: &[u8]) -> Self { data[0] } } impl FromData for i8 { #[inline] fn parse(data: &[u8]) -> Self { data[0] as i8 } } impl FromData for u16 { #[inline] fn parse(data: &[u8]) -> Self { u16::from_be_bytes([data[0], data[1]]) } } impl FromData for i16 { #[inline] fn parse(data: &[u8]) -> Self { i16::from_be_bytes([data[0], data[1]]) } } impl FromData for u32 { #[inline] fn parse(data: &[u8]) -> Self { // For u32 it's faster to use TryInto, but for u16/i16 it's faster to index. use core::convert::TryInto; u32::from_be_bytes(data.try_into().unwrap()) } } pub trait TryFromData: Sized { /// Stores an object size in raw data. /// /// `mem::size_of` by default. /// /// Override when size of `Self` != size of a raw data. /// For example, when you are parsing `u16`, but storing it as `u8`. /// In this case `size_of::()` == 1, but `FromData::SIZE` == 2. const SIZE: usize = core::mem::size_of::(); /// Parses an object from a raw data. fn try_parse(data: &[u8]) -> Result; } // Like `usize`, but for font. pub trait FSize { fn to_usize(&self) -> usize; } impl FSize for u16 { #[inline] fn to_usize(&self) -> usize { *self as usize } } impl FSize for u32 { #[inline] fn to_usize(&self) -> usize { *self as usize } } #[derive(Clone, Copy)] pub struct LazyArray<'a, T> { data: &'a [u8], phantom: core::marker::PhantomData, } impl<'a, T: FromData> LazyArray<'a, T> { #[inline] pub fn new(data: &'a [u8]) -> Self { LazyArray { data, phantom: core::marker::PhantomData, } } pub fn at(&self, index: L) -> T { let start = index.to_usize() * T::SIZE; let end = start + T::SIZE; T::parse(&self.data[start..end]) } pub fn get(&self, index: L) -> Option { if index.to_usize() < self.len() { let start = index.to_usize() * T::SIZE; let end = start + T::SIZE; Some(T::parse(&self.data[start..end])) } else { None } } #[inline] pub fn last(&self) -> Option { if !self.is_empty() { self.get(self.len() as u32 - 1) } else { None } } #[inline] pub fn len(&self) -> usize { self.data.len() / T::SIZE } #[inline] pub fn is_empty(&self) -> bool { self.len() == 0 } #[inline] pub fn binary_search_by(&self, mut f: F) -> Option where F: FnMut(&T) -> core::cmp::Ordering { // Based on Rust std implementation. use core::cmp::Ordering; let mut size = self.len() as u32; if size == 0 { return None; } let mut base = 0; while size > 1 { let half = size / 2; let mid = base + half; // mid is always in [0, size), that means mid is >= 0 and < size. // mid >= 0: by definition // mid < size: mid = size / 2 + size / 4 + size / 8 ... let cmp = f(&self.at(mid)); base = if cmp == Ordering::Greater { base } else { mid }; size -= half; } // base is always in [0, size) because base <= mid. let value = self.at(base); let cmp = f(&value); if cmp == Ordering::Equal { Some(value) } else { None } } } impl<'a, T: FromData + core::fmt::Debug + Copy> core::fmt::Debug for LazyArray<'a, T> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_list().entries(self.into_iter()).finish() } } impl<'a, T: FromData> IntoIterator for LazyArray<'a, T> { type Item = T; type IntoIter = LazyArrayIter<'a, T>; #[inline] fn into_iter(self) -> Self::IntoIter { LazyArrayIter { data: self, offset: 0, } } } pub struct LazyArrayIter<'a, T> { data: LazyArray<'a, T>, offset: u32, } impl<'a, T: FromData> Iterator for LazyArrayIter<'a, T> { type Item = T; #[inline] fn next(&mut self) -> Option { if self.offset as usize == self.data.len() { return None; } let index = self.offset; self.offset += 1; self.data.get(index) } } pub trait TrySlice<'a> { fn try_slice(&self, range: Range) -> Result<&'a [u8]>; } impl<'a> TrySlice<'a> for &'a [u8] { #[inline] fn try_slice(&self, range: Range) -> Result<&'a [u8]> { self.get(range.clone()) .ok_or_else(|| Error::SliceOutOfBounds { start: range.start as u32, end: range.end as u32, data_len: self.len() as u32, }) } } #[derive(Clone, Copy)] pub struct Stream<'a> { data: &'a [u8], offset: usize, } impl<'a> Stream<'a> { #[inline] pub fn new(data: &'a [u8]) -> Self { Stream { data, offset: 0, } } #[inline] pub fn new_at(data: &'a [u8], offset: usize) -> Self { Stream { data, offset, } } #[inline] pub fn at_end(&self) -> bool { self.offset == self.data.len() } #[inline] pub fn offset(&self) -> usize { self.offset } #[inline] pub fn tail(&self) -> Result<&'a [u8]> { self.data.try_slice(self.offset..self.data.len()) } #[inline] pub fn skip(&mut self) { self.offset += T::SIZE; } #[inline] pub fn skip_len(&mut self, len: L) { self.offset += len.to_usize(); } #[inline] pub fn read(&mut self) -> Result { let start = self.offset; self.offset += T::SIZE; let end = self.offset; let data = self.data.try_slice(start..end)?; Ok(T::parse(data)) } #[inline] pub fn try_read(&mut self) -> Result { let start = self.offset; self.offset += T::SIZE; let end = self.offset; let data = self.data.try_slice(start..end)?; T::try_parse(data) } #[inline] pub fn read_at(data: &[u8], mut offset: usize) -> Result { let start = offset; offset += T::SIZE; let end = offset; let data = data.try_slice(start..end)?; Ok(T::parse(data)) } #[inline] pub fn read_bytes(&mut self, len: L) -> Result<&'a [u8]> { let offset = self.offset; self.offset += len.to_usize(); self.data.try_slice(offset..(offset + len.to_usize())) } #[inline] pub fn read_array(&mut self, len: L) -> Result> { let len = len.to_usize() * T::SIZE; let data = self.read_bytes(len as u32)?; Ok(LazyArray::new(data)) } #[inline] pub fn read_array16(&mut self) -> Result> { let count: u16 = self.read()?; self.read_array(count) } #[inline] pub fn read_array32(&mut self) -> Result> { let count: u32 = self.read()?; self.read_array(count) } pub fn read_f2_14(&mut self) -> Result { Ok(self.read::()? as f32 / 16384.0) } } /// A "safe" stream. /// /// Unlike `Stream`, `SafeStream` doesn't perform bounds checking on each read. /// It leverages the type system, so we can sort of guarantee that /// we do not read past the bounds. /// /// For example, if we are iterating a `LazyArray` we already checked it's size /// and we can't read past the bounds, so we can remove useless checks. /// /// It's still not 100% guarantee, but it makes code easier to read and a bit faster. /// And we still backed by the Rust's bounds checking. #[derive(Clone, Copy)] pub struct SafeStream<'a> { data: &'a [u8], offset: usize, } impl<'a> SafeStream<'a> { #[inline] pub fn new(data: &'a [u8]) -> Self { SafeStream { data, offset: 0, } } #[inline] pub fn read(&mut self) -> T { T::parse(self.read_bytes(T::SIZE as u32)) } #[inline] pub fn read_bytes(&mut self, len: L) -> &'a [u8] { let offset = self.offset; self.offset += len.to_usize(); &self.data[offset..(offset + len.to_usize())] } #[inline] pub fn read_u24(&mut self) -> u32 { let d = self.data; let i = self.offset; let n = 0 << 24 | (d[i + 0] as u32) << 16 | (d[i + 1] as u32) << 8 | d[i + 2] as u32; self.offset += 3; n } } ttf-parser-0.3.0/src/post.rs010064400017500001750000000007421354314235500141540ustar0000000000000000// https://docs.microsoft.com/en-us/typography/opentype/spec/post use crate::{Font, LineMetrics}; impl<'a> Font<'a> { /// Parses font's underline metrics. /// /// Returns `None` when `post` table is not present. #[inline] pub fn underline_metrics(&self) -> Option { let table = self.post?; Some(LineMetrics { position: table.underline_position(), thickness: table.underline_thickness(), }) } } ttf-parser-0.3.0/src/raw.rs010064400017500001750000000413261354314235500137630ustar0000000000000000// This file is autogenerated by scripts/get-tables.py // Do not edit it! // By using static arrays we can have compile-time guaranties that // we are not reading out-ouf-bounds. // Also, it removes bounds-checking overhead. // Based on https://github.com/droundy/arrayref macro_rules! array_ref { ($arr:expr, $len:expr) => {{ // Always check that the slice length is the same as `$len`. assert_eq!($arr.len(), $len); unsafe { &*($arr.as_ptr() as *const [_; $len]) } }}; } use crate::parser::FromData; use core::convert::TryInto; #[derive(Clone, Copy)] pub struct TTCHeader<'a> { data: &'a [u8; 12], } impl<'a> TTCHeader<'a> { pub const SIZE: usize = 12; #[inline(always)] pub fn new(input: &'a [u8]) -> Self { TTCHeader { data: array_ref![input, 12], } } #[inline(always)] pub fn ttc_tag(&self) -> [u8; 4] { // Unwrap is safe, because an array and a slice have the same size. self.data[0..4].try_into().unwrap() } #[inline(always)] pub fn num_fonts(&self) -> u32 { u32::from_be_bytes([self.data[8], self.data[9], self.data[10], self.data[11]]) } } #[derive(Clone, Copy)] pub struct TableRecord { data: [u8; 16], } impl TableRecord { pub const SIZE: usize = 16; #[inline(always)] pub fn new(input: &[u8]) -> Self { let mut data = [0u8; Self::SIZE]; data.clone_from_slice(input); TableRecord { data } } #[inline(always)] pub fn table_tag(&self) -> [u8; 4] { // Unwrap is safe, because an array and a slice have the same size. self.data[0..4].try_into().unwrap() } #[inline(always)] pub fn offset(&self) -> u32 { u32::from_be_bytes([self.data[8], self.data[9], self.data[10], self.data[11]]) } #[inline(always)] pub fn length(&self) -> u32 { u32::from_be_bytes([self.data[12], self.data[13], self.data[14], self.data[15]]) } } impl FromData for TableRecord { const SIZE: usize = TableRecord::SIZE; #[inline] fn parse(data: &[u8]) -> Self { Self::new(data) } } pub mod head { #[derive(Clone, Copy)] pub struct Table<'a> { data: &'a [u8; 54], } impl<'a> Table<'a> { pub const SIZE: usize = 54; #[inline(always)] pub fn new(input: &'a [u8]) -> Self { Table { data: array_ref![input, 54], } } #[inline(always)] pub fn units_per_em(&self) -> u16 { u16::from_be_bytes([self.data[18], self.data[19]]) } #[inline(always)] pub fn index_to_loc_format(&self) -> i16 { i16::from_be_bytes([self.data[50], self.data[51]]) } } } pub mod maxp { #[derive(Clone, Copy)] pub struct Table<'a> { data: &'a [u8; 6], } impl<'a> Table<'a> { pub const SIZE: usize = 6; #[inline(always)] pub fn new(input: &'a [u8]) -> Self { Table { data: array_ref![input, 6], } } #[inline(always)] pub fn num_glyphs(&self) -> u16 { u16::from_be_bytes([self.data[4], self.data[5]]) } } } pub mod hhea { #[derive(Clone, Copy)] pub struct Table<'a> { data: &'a [u8; 36], } impl<'a> Table<'a> { pub const SIZE: usize = 36; #[inline(always)] pub fn new(input: &'a [u8]) -> Self { Table { data: array_ref![input, 36], } } #[inline(always)] pub fn ascender(&self) -> i16 { i16::from_be_bytes([self.data[4], self.data[5]]) } #[inline(always)] pub fn descender(&self) -> i16 { i16::from_be_bytes([self.data[6], self.data[7]]) } #[inline(always)] pub fn line_gap(&self) -> i16 { i16::from_be_bytes([self.data[8], self.data[9]]) } #[inline(always)] pub fn number_of_h_metrics(&self) -> u16 { u16::from_be_bytes([self.data[34], self.data[35]]) } } } pub mod hmtx { use crate::parser::FromData; #[derive(Clone, Copy)] pub struct HorizontalMetrics { data: [u8; 4], } impl HorizontalMetrics { pub const SIZE: usize = 4; #[inline(always)] pub fn new(input: &[u8]) -> Self { let mut data = [0u8; Self::SIZE]; data.clone_from_slice(input); HorizontalMetrics { data } } #[inline(always)] pub fn advance_width(&self) -> u16 { u16::from_be_bytes([self.data[0], self.data[1]]) } #[inline(always)] pub fn lsb(&self) -> i16 { i16::from_be_bytes([self.data[2], self.data[3]]) } } impl FromData for HorizontalMetrics { const SIZE: usize = HorizontalMetrics::SIZE; #[inline] fn parse(data: &[u8]) -> Self { Self::new(data) } } } pub mod vhea { #[derive(Clone, Copy)] pub struct Table<'a> { data: &'a [u8; 36], } impl<'a> Table<'a> { pub const SIZE: usize = 36; #[inline(always)] pub fn new(input: &'a [u8]) -> Self { Table { data: array_ref![input, 36], } } #[inline(always)] pub fn num_of_long_ver_metrics(&self) -> u16 { u16::from_be_bytes([self.data[34], self.data[35]]) } } } pub mod vmtx { use crate::parser::FromData; #[derive(Clone, Copy)] pub struct VerticalMetrics { data: [u8; 4], } impl VerticalMetrics { pub const SIZE: usize = 4; #[inline(always)] pub fn new(input: &[u8]) -> Self { let mut data = [0u8; Self::SIZE]; data.clone_from_slice(input); VerticalMetrics { data } } #[inline(always)] pub fn advance_height(&self) -> u16 { u16::from_be_bytes([self.data[0], self.data[1]]) } #[inline(always)] pub fn top_side_bearing(&self) -> i16 { i16::from_be_bytes([self.data[2], self.data[3]]) } } impl FromData for VerticalMetrics { const SIZE: usize = VerticalMetrics::SIZE; #[inline] fn parse(data: &[u8]) -> Self { Self::new(data) } } } pub mod post { #[derive(Clone, Copy)] pub struct Table<'a> { data: &'a [u8; 32], } impl<'a> Table<'a> { pub const SIZE: usize = 32; #[inline(always)] pub fn new(input: &'a [u8]) -> Self { Table { data: array_ref![input, 32], } } #[inline(always)] pub fn underline_position(&self) -> i16 { i16::from_be_bytes([self.data[8], self.data[9]]) } #[inline(always)] pub fn underline_thickness(&self) -> i16 { i16::from_be_bytes([self.data[10], self.data[11]]) } } } pub mod cmap { use crate::parser::FromData; use crate::GlyphId; #[derive(Clone, Copy)] pub struct EncodingRecord { data: [u8; 8], } impl EncodingRecord { pub const SIZE: usize = 8; #[inline(always)] pub fn new(input: &[u8]) -> Self { let mut data = [0u8; Self::SIZE]; data.clone_from_slice(input); EncodingRecord { data } } #[inline(always)] pub fn platform_id(&self) -> u16 { u16::from_be_bytes([self.data[0], self.data[1]]) } #[inline(always)] pub fn encoding_id(&self) -> u16 { u16::from_be_bytes([self.data[2], self.data[3]]) } #[inline(always)] pub fn offset(&self) -> u32 { u32::from_be_bytes([self.data[4], self.data[5], self.data[6], self.data[7]]) } } impl FromData for EncodingRecord { const SIZE: usize = EncodingRecord::SIZE; #[inline] fn parse(data: &[u8]) -> Self { Self::new(data) } } #[derive(Clone, Copy)] pub struct SubHeaderRecord { data: [u8; 8], } impl SubHeaderRecord { pub const SIZE: usize = 8; #[inline(always)] pub fn new(input: &[u8]) -> Self { let mut data = [0u8; Self::SIZE]; data.clone_from_slice(input); SubHeaderRecord { data } } #[inline(always)] pub fn first_code(&self) -> u16 { u16::from_be_bytes([self.data[0], self.data[1]]) } #[inline(always)] pub fn entry_count(&self) -> u16 { u16::from_be_bytes([self.data[2], self.data[3]]) } #[inline(always)] pub fn id_delta(&self) -> i16 { i16::from_be_bytes([self.data[4], self.data[5]]) } #[inline(always)] pub fn id_range_offset(&self) -> u16 { u16::from_be_bytes([self.data[6], self.data[7]]) } } impl FromData for SubHeaderRecord { const SIZE: usize = SubHeaderRecord::SIZE; #[inline] fn parse(data: &[u8]) -> Self { Self::new(data) } } #[derive(Clone, Copy)] pub struct SequentialMapGroup { data: [u8; 12], } impl SequentialMapGroup { pub const SIZE: usize = 12; #[inline(always)] pub fn new(input: &[u8]) -> Self { let mut data = [0u8; Self::SIZE]; data.clone_from_slice(input); SequentialMapGroup { data } } #[inline(always)] pub fn start_char_code(&self) -> u32 { u32::from_be_bytes([self.data[0], self.data[1], self.data[2], self.data[3]]) } #[inline(always)] pub fn end_char_code(&self) -> u32 { u32::from_be_bytes([self.data[4], self.data[5], self.data[6], self.data[7]]) } #[inline(always)] pub fn start_glyph_id(&self) -> u32 { u32::from_be_bytes([self.data[8], self.data[9], self.data[10], self.data[11]]) } } impl FromData for SequentialMapGroup { const SIZE: usize = SequentialMapGroup::SIZE; #[inline] fn parse(data: &[u8]) -> Self { Self::new(data) } } #[derive(Clone, Copy)] pub struct UnicodeRangeRecord { data: [u8; 4], } impl UnicodeRangeRecord { pub const SIZE: usize = 4; #[inline(always)] pub fn new(input: &[u8]) -> Self { let mut data = [0u8; Self::SIZE]; data.clone_from_slice(input); UnicodeRangeRecord { data } } #[inline(always)] pub fn start_unicode_value(&self) -> u32 { (self.data[0] as u32) << 16 | (self.data[1] as u32) << 8 | self.data[2] as u32 } #[inline(always)] pub fn additional_count(&self) -> u8 { self.data[3] } } impl FromData for UnicodeRangeRecord { const SIZE: usize = UnicodeRangeRecord::SIZE; #[inline] fn parse(data: &[u8]) -> Self { Self::new(data) } } #[derive(Clone, Copy)] pub struct UVSMappingRecord { data: [u8; 5], } impl UVSMappingRecord { pub const SIZE: usize = 5; #[inline(always)] pub fn new(input: &[u8]) -> Self { let mut data = [0u8; Self::SIZE]; data.clone_from_slice(input); UVSMappingRecord { data } } #[inline(always)] pub fn unicode_value(&self) -> u32 { (self.data[0] as u32) << 16 | (self.data[1] as u32) << 8 | self.data[2] as u32 } #[inline(always)] pub fn glyph_id(&self) -> GlyphId { GlyphId(u16::from_be_bytes([self.data[3], self.data[4]])) } } impl FromData for UVSMappingRecord { const SIZE: usize = UVSMappingRecord::SIZE; #[inline] fn parse(data: &[u8]) -> Self { Self::new(data) } } #[derive(Clone, Copy)] pub struct VariationSelectorRecord { data: [u8; 11], } impl VariationSelectorRecord { pub const SIZE: usize = 11; #[inline(always)] pub fn new(input: &[u8]) -> Self { let mut data = [0u8; Self::SIZE]; data.clone_from_slice(input); VariationSelectorRecord { data } } #[inline(always)] pub fn var_selector(&self) -> u32 { (self.data[0] as u32) << 16 | (self.data[1] as u32) << 8 | self.data[2] as u32 } #[inline(always)] pub fn default_uvs_offset(&self) -> Option { let n = u32::from_be_bytes([self.data[3], self.data[4], self.data[5], self.data[6]]); if n != 0 { Some(n) } else { None } } #[inline(always)] pub fn non_default_uvs_offset(&self) -> Option { let n = u32::from_be_bytes([self.data[7], self.data[8], self.data[9], self.data[10]]); if n != 0 { Some(n) } else { None } } } impl FromData for VariationSelectorRecord { const SIZE: usize = VariationSelectorRecord::SIZE; #[inline] fn parse(data: &[u8]) -> Self { Self::new(data) } } } pub mod os_2 { #[derive(Clone, Copy)] pub struct TableV0<'a> { data: &'a [u8; 78], } impl<'a> TableV0<'a> { pub const SIZE: usize = 78; #[inline(always)] pub fn new(input: &'a [u8]) -> Self { TableV0 { data: array_ref![input, 78], } } #[inline(always)] pub fn version(&self) -> u16 { u16::from_be_bytes([self.data[0], self.data[1]]) } #[inline(always)] pub fn us_weight_class(&self) -> u16 { u16::from_be_bytes([self.data[4], self.data[5]]) } #[inline(always)] pub fn us_width_class(&self) -> u16 { u16::from_be_bytes([self.data[6], self.data[7]]) } #[inline(always)] pub fn y_subscript_x_size(&self) -> i16 { i16::from_be_bytes([self.data[10], self.data[11]]) } #[inline(always)] pub fn y_subscript_y_size(&self) -> i16 { i16::from_be_bytes([self.data[12], self.data[13]]) } #[inline(always)] pub fn y_subscript_x_offset(&self) -> i16 { i16::from_be_bytes([self.data[14], self.data[15]]) } #[inline(always)] pub fn y_subscript_y_offset(&self) -> i16 { i16::from_be_bytes([self.data[16], self.data[17]]) } #[inline(always)] pub fn y_superscript_x_size(&self) -> i16 { i16::from_be_bytes([self.data[18], self.data[19]]) } #[inline(always)] pub fn y_superscript_y_size(&self) -> i16 { i16::from_be_bytes([self.data[20], self.data[21]]) } #[inline(always)] pub fn y_superscript_x_offset(&self) -> i16 { i16::from_be_bytes([self.data[22], self.data[23]]) } #[inline(always)] pub fn y_superscript_y_offset(&self) -> i16 { i16::from_be_bytes([self.data[24], self.data[25]]) } #[inline(always)] pub fn y_strikeout_size(&self) -> i16 { i16::from_be_bytes([self.data[26], self.data[27]]) } #[inline(always)] pub fn y_strikeout_position(&self) -> i16 { i16::from_be_bytes([self.data[28], self.data[29]]) } #[inline(always)] pub fn fs_selection(&self) -> u16 { u16::from_be_bytes([self.data[62], self.data[63]]) } } } pub mod name { #[derive(Clone, Copy)] pub struct NameRecord { data: [u8; 12], } impl NameRecord { pub const SIZE: usize = 12; #[inline(always)] pub fn new(input: &[u8]) -> Self { let mut data = [0u8; Self::SIZE]; data.clone_from_slice(input); NameRecord { data } } #[inline(always)] pub fn platform_id(&self) -> u16 { u16::from_be_bytes([self.data[0], self.data[1]]) } #[inline(always)] pub fn encoding_id(&self) -> u16 { u16::from_be_bytes([self.data[2], self.data[3]]) } #[inline(always)] pub fn language_id(&self) -> u16 { u16::from_be_bytes([self.data[4], self.data[5]]) } #[inline(always)] pub fn name_id(&self) -> u16 { u16::from_be_bytes([self.data[6], self.data[7]]) } #[inline(always)] pub fn length(&self) -> u16 { u16::from_be_bytes([self.data[8], self.data[9]]) } #[inline(always)] pub fn offset(&self) -> u16 { u16::from_be_bytes([self.data[10], self.data[11]]) } } } ttf-parser-0.3.0/src/vhea.rs010064400017500001750000000003731354314235500141120ustar0000000000000000// https://docs.microsoft.com/en-us/typography/opentype/spec/vhea use crate::Font; impl<'a> Font<'a> { #[inline] pub(crate) fn number_of_vmetrics(&self) -> Option { self.vhea.map(|table| table.num_of_long_ver_metrics()) } } ttf-parser-0.3.0/src/vmtx.rs010064400017500001750000000042441354314235500141660ustar0000000000000000// https://docs.microsoft.com/en-us/typography/opentype/spec/vmtx use crate::parser::{Stream, LazyArray}; use crate::{Font, GlyphId, VerticalMetrics, TableName, Result, Error}; use crate::raw::vmtx as raw; impl<'a> Font<'a> { /// Returns glyph's vertical metrics. pub fn glyph_ver_metrics(&self, glyph_id: GlyphId) -> Result { self.check_glyph_id(glyph_id)?; let data = self.vmtx.ok_or_else(|| Error::TableMissing(TableName::VerticalMetrics))?; let mut s = Stream::new(data); let number_of_vmetrics = self.number_of_vmetrics() .ok_or_else(|| Error::NoHorizontalMetrics)?; if number_of_vmetrics == 0 { return Err(Error::NoHorizontalMetrics); } let glyph_id = glyph_id.0; let array: LazyArray = s.read_array(number_of_vmetrics)?; if let Some(metrics) = array.get(glyph_id) { Ok(VerticalMetrics { advance: metrics.advance_height(), top_side_bearing: metrics.top_side_bearing(), }) } else { let advance = array.last().ok_or_else(|| Error::NoVerticalMetrics)?.advance_height(); // 'The number of entries in this array is calculated by subtracting the value of // numOfLongVerMetrics from the number of glyphs in the font.' // Check for overflow first. if self.number_of_glyphs() < number_of_vmetrics { return Err(Error::NoVerticalMetrics); } let tsb_array_len = self.number_of_glyphs() - number_of_vmetrics; // 'This array contains the top sidebearings of glyphs not represented in // the first array, and all the glyphs in this array must have the same advance // height as the last entry in the vMetrics array.' let array: LazyArray = s.read_array(tsb_array_len)?; let top_side_bearing = array .get(glyph_id - number_of_vmetrics) .ok_or_else(|| Error::NoVerticalMetrics)?; Ok(VerticalMetrics { advance, top_side_bearing, }) } } } ttf-parser-0.3.0/tests/fonts-src/README.md010064400017500001750000000002311354314235500163620ustar0000000000000000We are using [fonttools](https://github.com/fonttools/fonttools) to generate font files from XML. Usage: ``` ttx -o ../fonts/glyphs.ttf glyphs.ttx ``` ttf-parser-0.3.0/tests/fonts-src/cmap-10.ttx010064400017500001750000000034701354314235500170120ustar0000000000000000 000a0000 0000001a 00000000 00109423 00000003 001a001b 0020 ttf-parser-0.3.0/tests/fonts-src/cmap-6.ttx010064400017500001750000000040201354314235500167270ustar0000000000000000 ttf-parser-0.3.0/tests/fonts-src/glyphs.ttx010064400017500001750000000160361354314235500171640ustar0000000000000000 ttf-parser-0.3.0/tests/fonts-src/invalid-em.ttx010064400017500001750000000044741354314235500177060ustar0000000000000000 ttf-parser-0.3.0/tests/fonts-src/os2-invalid-width.ttx010064400017500001750000000072451354314235500211240ustar0000000000000000 ttf-parser-0.3.0/tests/fonts-src/os2-v0.ttx010064400017500001750000000072431354314235500167040ustar0000000000000000 ttf-parser-0.3.0/tests/fonts-src/vmtx.ttx010064400017500001750000000237461354314235500166620ustar0000000000000000 Mplus 1p Regular 1.061g;GoogleFonts;Mplus1p-Regular Mplus 1p Version 1.061 Mplus1p-Regular Copyright 2016 The M+ Project Authors. Mplus 1p Regular 1.061g;GoogleFonts;Mplus1p-Regular Mplus 1p Version 1.061 Mplus1p-Regular http://mplus-fonts.osdn.jp This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFL http://scripts.sil.org/OFL ttf-parser-0.3.0/tests/fonts/README.md010064400017500001750000000016631354314235500156070ustar0000000000000000## Fonts File | Source | License | Modifications --- | --- | --- | --- TestGPOSThree.ttf | [Link](https://github.com/unicode-org/text-rendering-tests/tree/master/fonts) | OFL-1.1/Apache 2.0 | `fpgm` table removed TestCMAP14.otf | [Link](https://github.com/unicode-org/text-rendering-tests/tree/master/fonts) | OFL/Apache 2.0 | - TestKERNOne.otf | [Link](https://github.com/unicode-org/text-rendering-tests/tree/master/fonts) | OFL/Apache 2.0 | - cmap0_font1.otf | [Link](https://github.com/harfbuzz/harfbuzz/tree/master/test/shaping/data/aots/fonts) | Apache 2.0 | - cmap2_font1.otf | [Link](https://github.com/harfbuzz/harfbuzz/tree/master/test/shaping/data/aots/fonts) | Apache 2.0 | - cff1_dotsect.nohints.otf | [Link](https://github.com/harfbuzz/harfbuzz/tree/master/test/api/fonts/) | OFL/MIT | - cff1_flex.otf | [Link](https://github.com/harfbuzz/harfbuzz/tree/master/test/api/fonts/) | OFL/MIT | - All other fonts are from `../fonts-src` ttf-parser-0.3.0/tests/fonts/TestCMAP14.otf010064400017500001750000000031341354314235500165620ustar0000000000000000OTTO CFF =\ "OS/2Z:`cmapSlheadnA.6hhea$hmtxDmaxpPname3e1_` post2 {_<$ؚp8>PXKX^2,UNIC@ p8x   4; Ub @| (  4 & 4@Copyright 2016 by Unicode Inc.OpenTypeTest CMAP 14RegularOpenTypeTestCMAP14-RegularSascha Brawerhttp://scripts.sil.org/OFLCopyright 2016 by Unicode Inc.OpenTypeTest CMAP 14RegularOpenTypeTestCMAP14-RegularSascha Brawerhttp://scripts.sil.org/OFLL0 "i "iݛ}[E+4<"i2OpenTypeTestCMAP14-Regular .>Ur\(/Souni82A6_uE0100uni82A6_uE0101uni2269FE00uni2269Copyright \(c\) 2016 by Unicode Inc.OpenTypeTest CMAP 14 Regular l*PPP"DB)*z 1q|̳VWnJK{B@B@tGt99{ $d3Oqww_PW|: q{ӱDAAAAzGz3? h! qLL h! JfvlCul 7 |W&-y46[6B " "  "ddXttf-parser-0.3.0/tests/fonts/TestGPOSThree.ttf010064400017500001750000000056041354314235500174460ustar0000000000000000pDSIG |GDEF& $GPOS+ 0 GSUB p OS/2[=jx`cmap  @gasp# glyf<head6hhea!4$hmtxXloca,maxp{PX nameM~gmpost 8H_wr_<r680LL820I dvXKX^2,UNIC@ L8L yXP\@  u u mbb y8L#)59=H!!3#35#53535#5#533#3535#35353#5#3535#53335#735dUVVVV,++VVVV+؂,+++[[؅[*W+0++0+,[[/~+0+[+NV,H=hᒒh>+=++=+P0&533>553#5_iC9-B#ii\Ela+?P2R1 m:6m #"&54632#"&5463"###"###"##""##"_'72fscc `!52DD&. 5U ly   '?f >m   @ . E *_  *  N 9Copyright 2017 by Unicode Inc.Test GPOS ThreeRegular1.000;UKWN;TestGPOSThree-RegularTest GPOS Three RegularVersion 1.000TestGPOSThree-RegularSascha Brawerhttp://www.brawer.ch/This Font Software is licensed under the SIL Open Font License Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFLhttps://opensource.org/licenses/OFL-1.1u___u__Copyright 2017 by Unicode Inc.Test GPOS ThreeRegular1.000;UKWN;TestGPOSThree-RegularTest GPOS Three RegularVersion 1.000TestGPOSThree-RegularSascha Brawerhttp://www.brawer.ch/This Font Software is licensed under the SIL Open Font License Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFLhttps://opensource.org/licenses/OFL-1.1uu2Xuni0308 acutecombuni0304   :DFLTmarkmkmk*0 :I 680;8D;ttf-parser-0.3.0/tests/fonts/TestKERNOne.otf010064400017500001750000000025441354314235500171020ustar0000000000000000OTTO  CFF ڐ[fOS/2Z`cmapLLhead ;C6hheaB$hmtx kernT40maxpPname`Dppost2 $0_<ؚ28& 8X22&PXKX^2,UNIC@ 1 8  3: U b <l *  6  0Copyright 2016 Unicode, Inc.OpenTypeTest KERN OneRegularOpenTypeTestKERNOne-RegularSascha BrawerTuTTuTCopyright 2016 Unicode, Inc.OpenTypeTest KERN OneRegularOpenTypeTestKERNOne-RegularSascha Brawer1TuT11TuT18  Tu1 Tu12OpenTypeTestKERNOne-Regular\"+!>Copyright (C) 2016 Unicode, Inc.OpenTypeTest KERN One Regular5V.|>:j'\:;jti':jIE'ͰIjgEi::jIfj:ͰJ9jkE\j:ѺE'\\'$'$'$'7 ]X222X,8888ttf-parser-0.3.0/tests/fonts/cff1_dotsect.nohints.otf010064400017500001750000000060741354314235500210700ustar0000000000000000OTTO CFF YJOS/2[.v`cmapXheadi6hheaRt$hmtxmaxpPname]Ή%qpost2  SourceSansPro-Regular1 @ ΋HX[e2.20Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.Copyright 2010, 2012, 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'.Source Sans Pro  u z9 " JMQp(p $YI I Z-Cb|mzz|! !&$ kkrvkl! % # " r # r_<ŋ_C5 XKX^2# ADBO@ii   < ii iiCA5YCP&&2<n  I g Lu I d *% O 4 C 2[   4     # 9Source Sans ProRegular2.020;ADBO;SourceSansPro-Regular;ADOBEVersion 2.020;hotconv 1.0.109;makeotfexe 2.5.65593SourceSansPro-Regular 2010 - 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name Source .Source Sans ProRegular2.020;ADBO;SourceSansPro-Regular;ADOBEVersion 2.020;hotconv 1.0.109;makeotfexe 2.5.65593SourceSansPro-RegularSource is a trademark of Adobe Systems Incorporated in the United States and/or other countries.Adobe Systems IncorporatedPaul D. Hunthttp://www.adobe.com/typeThis Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFL. This Font Software is distributed on an AS IS  BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the SIL Open Font License for the specific language, permissions and limitations governing your use of this Font Software.http://scripts.sil.org/OFLSlashed zeroStraight lAlternate aAlternate gSerifed I2ttf-parser-0.3.0/tests/fonts/cff1_flex.otf010064400017500001750000000061201354314235500166700ustar0000000000000000OTTO CFF 9 LOS/2[u`cmap Xheada]6hhea^$hmtx% HmaxpPname]Ή%`qpost2 , m1_<ŋ_54HPP XKX^2# ADBO@OO  &&2<n  I g Lu I d *% O 4 C 2[   4     # 9Source Sans ProRegular2.020;ADBO;SourceSansPro-Regular;ADOBEVersion 2.020;hotconv 1.0.109;makeotfexe 2.5.65593SourceSansPro-Regular 2010 - 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name Source .Source Sans ProRegular2.020;ADBO;SourceSansPro-Regular;ADOBEVersion 2.020;hotconv 1.0.109;makeotfexe 2.5.65593SourceSansPro-RegularSource is a trademark of Adobe Systems Incorporated in the United States and/or other countries.Adobe Systems IncorporatedPaul D. Hunthttp://www.adobe.com/typeThis Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFL. This Font Software is distributed on an AS IS  BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the SIL Open Font License for the specific language, permissions and limitations governing your use of this Font Software.http://scripts.sil.org/OFLSlashed zeroStraight lAlternate aAlternate gSerifed I < OO OO2SourceSansPro-Regular" @ ww(e2.20Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.Copyright 2010, 2012, 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'.Source Sans Pro0Np(p$YI I Z-Cb|mzz||w⦅ $w #'Y''Y' "'wY''Y' %{ۋۛ۽ #ۋۋۋ{ %;c;;{c; #;c{;;c; $Y4ttf-parser-0.3.0/tests/fonts/cmap-10.otf010064400017500001750000000003301354314235500161660ustar0000000000000000OTTO@cmapWi&head? L6hhea} $maxpP _<|%@[ f ,, P # ttf-parser-0.3.0/tests/fonts/cmap-6.otf010064400017500001750000000003141354314235500161150ustar0000000000000000OTTO@cmapKhead?L6hhea} $maxpP ׮_<|%@- f ,, P "ttf-parser-0.3.0/tests/fonts/cmap0_font1.otf010064400017500001750000000121141354314235500171420ustar0000000000000000OTTO CFF #V$OS/2lU`cmap L headhK046hhea} l$hmtx@t:maxpdP\name &#dpost2, dummyJ     f swQf !$'*-0369<?BEHKNQTWZ]`cfilorux{~  #fkg0g1g2g3g4g5g6g7g8g9g10g11g12g13g14g15g16g17g18g19g20g21g22g23g24g25g26g27g28g29g30g31g32g33g34g35g36g37g38g39g40g41g42g43g44g45g46g47g48g49g50g51g52g53g54g55g56g57g58g59g60g61g62g63g64g65g66g67g68g69g70g71g72g73g74g75g76g77g78g79g80g81g82g83g84g85g86g87g88g89g90g91g92g93g94g95g96g97g98g99Copyright (c) 2002 Adobe Systems Incorporated. All Rights Reserved.dummybd $).38=BGLQV[`ejoty~ #(-27<AFKPUZ_dinsx}0 0  !  "  #  $  %  &  '  (  ) ! ! ! ! " ! # ! $ ! % ! & ! ' ! ( ! ) " " ! " " " # " $ " % " & " ' " ( " ) # # ! # " # # # $ # % # & # ' # ( # ) $ $ ! $ " $ # $ $ $ % $ & $ ' $ ( $ ) % % ! % " % # % $ % % % & % ' % ( % ) & & ! & " & # & $ & % & & & ' & ( & ) ' ' ! ' " ' # ' $ ' % ' & ' ' ' ( ' ) ( ( ! ( " ( # ( $ ( % ( & ( ' ( ( ( ) ) ) ! ) " ) # ) $ ) % ) & ) ' ) ( ) ) r%D¤äӤJr%D¤äӤJ   Q@>Wny}%-A`o:@p(_Dz򌷐Ym3sno{{~YF| ~,a;Wm{kϘ} C)kkkaljkaf) #ơȲˎِ*k[? Tk<00cpsf, B-=/E*k^ddS{d__`aaZz,˽ɽɨ"4~Yo FD"^Yjǻwsu~Xh eMqnjojX֋ƍސנ`aM3r?wÅ򊰒hzr^qe+5 #(gfh,kSfhC!a0]Kdk:qg}sg|oyqqh)ҵ7>U)n甄% ʙ|ˉsjxeue6)>)xAf9%"Ĥï}k\Z[DHEzc}X6/ 83"'5tquvtwa|lqvRl8i{卅ѹDzʼn~y|v兟z}~yeo$`- #B ުX[[jfmi6˃tllmS Y[W=k,?{JJMjUc}It=UcxYxzz{RzwC7ihhx{xuuwvv}}|ViƝߐx}}|vU Z]}zM_7:(x>fǚŝ‡UUVf~8JeQkWxzzy}rPGHIacaҨҡT xSwO010F~H{xɑ+++rWyqtP@hUyqs}ݍge2<\GbPB(y=XRVO;?s{h֖yyzaa`}6xyx3 ;^dW[I+p4ڐО̛qi~}}xtQvihhvn}C uuuyj n WDw|?Z$N4ryM|4bUk+do8&jbLnTEZCBɑȊ_? 6:3WgW{Cui찑a !\ĊĎčhYZXو{yw{!.!mzT lyimka-}aך{ٜgC{BDgt{r{gZE    22  ADBE@AP ? AA 8 X_< f ,, ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,PdN   $ : P dcmap0_font1Regularcmap0_font1cmap0_font1Version1.0cmap0_font12ttf-parser-0.3.0/tests/fonts/cmap2_font1.otf010064400017500001750000000135601354314235500171520ustar0000000000000000OTTO CFF #V$OS/2lU`cmap u 8headhK0X6hhea} $hmtx@t:maxpdPname (#post2P dummyJ     f swQf !$'*-0369<?BEHKNQTWZ]`cfilorux{~  #fkg0g1g2g3g4g5g6g7g8g9g10g11g12g13g14g15g16g17g18g19g20g21g22g23g24g25g26g27g28g29g30g31g32g33g34g35g36g37g38g39g40g41g42g43g44g45g46g47g48g49g50g51g52g53g54g55g56g57g58g59g60g61g62g63g64g65g66g67g68g69g70g71g72g73g74g75g76g77g78g79g80g81g82g83g84g85g86g87g88g89g90g91g92g93g94g95g96g97g98g99Copyright (c) 2002 Adobe Systems Incorporated. All Rights Reserved.dummybd $).38=BGLQV[`ejoty~ #(-27<AFKPUZ_dinsx}0 0  !  "  #  $  %  &  '  (  ) ! ! ! ! " ! # ! $ ! % ! & ! ' ! ( ! ) " " ! " " " # " $ " % " & " ' " ( " ) # # ! # " # # # $ # % # & # ' # ( # ) $ $ ! $ " $ # $ $ $ % $ & $ ' $ ( $ ) % % ! % " % # % $ % % % & % ' % ( % ) & & ! & " & # & $ & % & & & ' & ( & ) ' ' ! ' " ' # ' $ ' % ' & ' ' ' ( ' ) ( ( ! ( " ( # ( $ ( % ( & ( ' ( ( ( ) ) ) ! ) " ) # ) $ ) % ) & ) ' ) ( ) ) r%D¤äӤJr%D¤äӤJ   Q@>Wny}%-A`o:@p(_Dz򌷐Ym3sno{{~YF| ~,a;Wm{kϘ} C)kkkaljkaf) #ơȲˎِ*k[? Tk<00cpsf, B-=/E*k^ddS{d__`aaZz,˽ɽɨ"4~Yo FD"^Yjǻwsu~Xh eMqnjojX֋ƍސנ`aM3r?wÅ򊰒hzr^qe+5 #(gfh,kSfhC!a0]Kdk:qg}sg|oyqqh)ҵ7>U)n甄% ʙ|ˉsjxeue6)>)xAf9%"Ĥï}k\Z[DHEzc}X6/ 83"'5tquvtwa|lqvRl8i{卅ѹDzʼn~y|v兟z}~yeo$`- #B ުX[[jfmi6˃tllmS Y[W=k,?{JJMjUc}It=UcxYxzz{RzwC7ihhx{xuuwvv}}|ViƝߐx}}|vU Z]}zM_7:(x>fǚŝ‡UUVf~8JeQkWxzzy}rPGHIacaҨҡT xSwO010F~H{xɑ+++rWyqtP@hUyqs}ݍge2<\GbPB(y=XRVO;?s{h֖yyzaa`}6xyx3 ;^dW[I+p4ڐО̛qi~}}xtQvihhvn}C uuuyj n WDw|?Z$N4ryM|4bUk+do8&jbLnTEZCBɑȊ_? 6:3WgW{Cui찑a !\ĊĎčhYZXو{yw{!.!mzT lyimka-}aך{ٜgC{BDgt{r{gZE    22  ADBE@AP ? AA ,2 28 Ua_< f ,, ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,PdN   $ : P dcmap2_font1Regularcmap2_font1cmap2_font1Version1.0cmap2_font12ttf-parser-0.3.0/tests/fonts/glyphs.ttf010064400017500001750000000014501354314235500163470ustar0000000000000000 OS/2?V`cmapLglyfqheadN6hheak $hmtxxlocaDmaxpB postw8b_<Bk  Z (AXXK& d^2B @ )GOOG@--% 2<8XNZ @ =fi=fi Fcow23!285!5!86GGGG###5754632&&#"3LX^^\R 5*,+,)h[ E ;?#N 2#"&546#AX &X0&2 ILf+spacettf-parser-0.3.0/tests/fonts/invalid-em.ttf010064400017500001750000000005741354314235500170740ustar0000000000000000cmap5 ,glyfV<head6hheaz$hmtx2loca 8maxp post2X$Z_<%2 222   23!22ttf-parser-0.3.0/tests/fonts/os2-invalid-width.ttf010064400017500001750000000007341354314235500203110ustar0000000000000000 OS/2Ncmap5l,glyfVhead{6hheaz$hmtx2hloca maxp post2$$_<%2 22AXXK& d^2B @ )GOOG--%2   23!22ttf-parser-0.3.0/tests/fonts/os2-v0.ttf010064400017500001750000000007341354314235500160730ustar0000000000000000 OS/2Ncmap5l,glyfVheadٌ6hheaz$hmtx2hloca maxp post2$_<%2 22AXXK& d^2B @ )GOOG--%2   23!22ttf-parser-0.3.0/tests/fonts/vmtx.ttf010064400017500001750000000031741354314235500160440ustar0000000000000000`OS/2qhVcmapr0cvt gaspHglyfd%%head;r6hheaw$$hmtxT{locaJmaxp!`H name post2( vheaP$vmtx;t]A_<+296Z 3ZZZ..1 M+ @ff\tZ3@`l!Z  ff$!yJZ  -35'35##!35'35#"''32655!'665!I\KL lX :ZGY# LM   D    43  g 4Mplus 1pRegular1.061g;GoogleFonts;Mplus1p-RegularVersion 1.061Mplus1p-RegularCopyright 2016 The M+ Project Authors.Mplus 1pRegular1.061g;GoogleFonts;Mplus1p-RegularVersion 1.061Mplus1p-Regularhttp://mplus-fonts.osdn.jpThis Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFLhttp://scripts.sil.org/OFL2ZS:Sttf-parser-0.3.0/tests/tests.rs010064400017500001750000000415211354314235500147040ustar0000000000000000use std::fs; use ttf_parser as ttf; use ttf::{Font, GlyphId}; struct Builder(svgtypes::Path); impl Builder { fn new() -> Self { Builder(svgtypes::Path::new()) } } impl ttf::OutlineBuilder for Builder { fn move_to(&mut self, x: f32, y: f32) { self.0.push(svgtypes::PathSegment::MoveTo { abs: true, x: x as f64, y: y as f64 }); } fn line_to(&mut self, x: f32, y: f32) { self.0.push(svgtypes::PathSegment::LineTo { abs: true, x: x as f64, y: y as f64 }); } fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { self.0.push(svgtypes::PathSegment::Quadratic { abs: true, x1: x1 as f64, y1: y1 as f64, x: x as f64, y: y as f64 }); } fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { self.0.push(svgtypes::PathSegment::CurveTo { abs: true, x1: x1 as f64, y1: y1 as f64, x2: x2 as f64, y2: y2 as f64, x: x as f64, y: y as f64 }); } fn close(&mut self) { self.0.push(svgtypes::PathSegment::ClosePath { abs: true }); } } #[test] fn empty_font() { assert_eq!(Font::from_data(&[], 0).unwrap_err().to_string(), "not a TrueType font"); } #[test] fn incomplete_header() { let data = &[ 0x00, 0x01, 0x00, 0x00, // magic 0x00, 0x00, // numTables 0x00, 0x00, // searchRange 0x00, 0x00, // entrySelector 0x00, 0x00, // rangeShift ]; for i in 0..data.len() { assert_eq!(Font::from_data(&data[0..i], 0).unwrap_err().to_string(), "not a TrueType font"); } } #[test] fn zero_tables() { let data = &[ 0x00, 0x01, 0x00, 0x00, // magic 0x00, 0x00, // numTables 0x00, 0x00, // searchRange 0x00, 0x00, // entrySelector 0x00, 0x00, // rangeShift ]; assert_eq!(Font::from_data(data, 0).unwrap_err().to_string(), "font doesn't have a Header table"); } #[test] fn tables_count_overflow() { let data = &[ 0x00, 0x01, 0x00, 0x00, // magic 0xFF, 0xFF, // numTables 0x00, 0x00, // searchRange 0x00, 0x00, // entrySelector 0x00, 0x00, // rangeShift ]; assert_eq!(Font::from_data(data, 0).unwrap_err().to_string(), "an attempt to slice 12..1048572 on 0..12"); } #[test] fn open_type_magic() { let data = &[ 0x4F, 0x54, 0x54, 0x4F, // magic 0x00, 0x00, // numTables 0x00, 0x00, // searchRange 0x00, 0x00, // entrySelector 0x00, 0x00, // rangeShift ]; assert_eq!(Font::from_data(data, 0).unwrap_err().to_string(), "font doesn't have a Header table"); } #[test] fn unknown_magic() { let data = &[ 0xFF, 0xFF, 0xFF, 0xFF, // magic 0x00, 0x00, // numTables 0x00, 0x00, // searchRange 0x00, 0x00, // entrySelector 0x00, 0x00, // rangeShift ]; assert_eq!(Font::from_data(data, 0).unwrap_err().to_string(), "not a TrueType font"); } #[test] fn empty_font_collection() { let data = &[ 0x74, 0x74, 0x63, 0x66, // magic/ttcf 0x00, 0x01, // majorVersion 0x00, 0x00, // minorVersion 0x00, 0x00, 0x00, 0x00, // numFonts ]; assert_eq!(ttf::fonts_in_collection(data), Some(0)); assert_eq!(Font::from_data(data, 0).unwrap_err().to_string(), "font index is out of bounds"); } #[test] fn font_collection_num_fonts_overflow() { let data = &[ 0x74, 0x74, 0x63, 0x66, // magic/ttcf 0x00, 0x01, // majorVersion 0x00, 0x00, // minorVersion 0xFF, 0xFF, 0xFF, 0xFF, // numFonts ]; assert_eq!(ttf::fonts_in_collection(data), Some(4294967295)); assert_eq!(Font::from_data(data, 0).unwrap_err().to_string(), "an attempt to slice 12..16 on 0..12"); } #[test] fn font_index_overflow() { let data = &[ 0xFF, 0xFF, 0xFF, 0xFF, // magic 0x00, 0x00, // numTables 0x00, 0x00, // searchRange 0x00, 0x00, // entrySelector 0x00, 0x00, // rangeShift ]; assert_eq!(Font::from_data(data, std::u32::MAX).unwrap_err().to_string(), "not a TrueType font"); } #[test] fn number_of_glyphs() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.number_of_glyphs(), 7); } #[test] fn outline_glyph_single_contour() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); let mut builder = Builder::new(); font.outline_glyph(GlyphId(0), &mut builder).unwrap(); assert_eq!(builder.0.to_string(), "M 50 0 L 50 750 L 450 750 L 450 0 L 50 0 Z"); } #[test] fn outline_glyph_two_contours() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); let mut builder = Builder::new(); font.outline_glyph(GlyphId(1), &mut builder).unwrap(); assert_eq!(builder.0.to_string(), "M 56 416 L 56 487 L 514 487 L 514 416 L 56 416 Z \ M 56 217 L 56 288 L 514 288 L 514 217 L 56 217 Z"); } #[test] fn outline_glyph_composite() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); let mut builder = Builder::new(); font.outline_glyph(GlyphId(4), &mut builder).unwrap(); assert_eq!(builder.0.to_string(), "M 332 468 L 197 468 L 197 0 L 109 0 L 109 468 L 15 468 L 15 509 L 109 539 \ L 109 570 Q 109 674 155 719.5 Q 201 765 283 765 Q 315 765 341.5 759.5 \ Q 368 754 387 747 L 364 678 Q 348 683 327 688 Q 306 693 284 693 \ Q 240 693 218.5 663.5 Q 197 634 197 571 L 197 536 L 332 536 L 332 468 Z \ M 474 737 Q 494 737 509.5 723.5 Q 525 710 525 681 Q 525 653 509.5 639 \ Q 494 625 474 625 Q 452 625 437 639 Q 422 653 422 681 Q 422 710 437 723.5 \ Q 452 737 474 737 Z M 517 536 L 517 0 L 429 0 L 429 536 L 517 536 Z"); } #[test] fn outline_glyph_single_point() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); let mut builder = Builder::new(); font.outline_glyph(GlyphId(5), &mut builder).unwrap(); assert_eq!(builder.0.to_string(), ""); } #[test] fn outline_glyph_single_point_2() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); let mut builder = Builder::new(); font.outline_glyph(GlyphId(6), &mut builder).unwrap(); assert_eq!(builder.0.to_string(), "M 332 468 L 197 468 L 197 0 L 109 0 L 109 468 L 15 468 L 15 509 L 109 539 \ L 109 570 Q 109 674 155 719.5 Q 201 765 283 765 Q 315 765 341.5 759.5 \ Q 368 754 387 747 L 364 678 Q 348 683 327 688 Q 306 693 284 693 \ Q 240 693 218.5 663.5 Q 197 634 197 571 L 197 536 L 332 536 L 332 468 Z"); } #[test] fn outline_glyph_cff_flex() { let data = fs::read("tests/fonts/cff1_flex.otf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); let mut builder = Builder::new(); font.outline_glyph(GlyphId(1), &mut builder).unwrap(); assert_eq!(builder.0.to_string(), "M 0 0 C 100 0 150 -20 250 -20 C 350 -20 400 0 500 0 C 500 100 520 150 520 250 \ C 520 350 500 400 500 500 C 400 500 350 520 250 520 C 150 520 100 500 0 500 \ C 0 400 -20 350 -20 250 C -20 150 0 100 0 0 Z M 50 50 C 50 130 34 170 34 250 \ C 34 330 50 370 50 450 C 130 450 170 466 250 466 C 330 466 370 450 450 450 \ C 450 370 466 330 466 250 C 466 170 450 130 450 50 C 370 50 330 34 250 34 \ C 170 34 130 50 50 50 Z"); } #[test] fn outline_glyph_cff_1() { let data = fs::read("tests/fonts/cff1_dotsect.nohints.otf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); let mut builder = Builder::new(); font.outline_glyph(GlyphId(1), &mut builder).unwrap(); assert_eq!(builder.0.to_string(), "M 82 0 L 164 0 L 164 486 L 82 486 Z M 124 586 C 156 586 181 608 181 639 \ C 181 671 156 692 124 692 C 92 692 67 671 67 639 C 67 608 92 586 124 586 Z"); } #[test] fn units_per_em() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.units_per_em(), Some(1000)); } #[test] fn units_per_em_invalid() { let data = fs::read("tests/fonts/invalid-em.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.units_per_em(), None); } #[test] fn ascender() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.ascender(), 900); } #[test] fn descender() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.descender(), -300); } #[test] fn line_gap() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.line_gap(), 200); } #[test] fn underline_metrics() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.underline_metrics().unwrap(), ttf::LineMetrics { position: -75, thickness: 50 }); } #[test] fn weight() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.weight(), ttf::Weight::SemiBold); } #[test] fn width() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.width(), ttf::Width::Expanded); } #[test] fn invalid_width() { let data = fs::read("tests/fonts/os2-invalid-width.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.width(), ttf::Width::default()); } #[test] fn x_height() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.x_height().unwrap(), 536); } #[test] fn no_x_height() { let data = fs::read("tests/fonts/os2-v0.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert!(font.x_height().is_none()); } #[test] fn strikeout_metrics() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.strikeout_metrics().unwrap(), ttf::LineMetrics { position: 322, thickness: 50 }); } #[test] fn is_regular() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.is_regular(), true); } #[test] fn is_italic() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.is_italic(), false); } #[test] fn os2_is_bold() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.is_bold(), false); } #[test] fn is_oblique() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.is_oblique(), false); } #[test] fn no_is_oblique() { let data = fs::read("tests/fonts/os2-v0.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.is_oblique(), false); } #[test] fn subscript_metrics() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!( font.subscript_metrics().unwrap(), ttf::ScriptMetrics { x_size: 650, y_size: 600, x_offset: 0, y_offset: 75 }, ); } #[test] fn superscript_metrics() { let data = fs::read("tests/fonts/glyphs.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!( font.superscript_metrics().unwrap(), ttf::ScriptMetrics { x_size: 550, y_size: 800, x_offset: 100, y_offset: 350 }, ); } // TODO: hmtx #[test] fn glyph_ver_metrics_1() { let data = fs::read("tests/fonts/vmtx.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!( font.glyph_ver_metrics(GlyphId(0)).unwrap(), ttf::VerticalMetrics { advance: 1000, top_side_bearing: 666 }, ); } #[test] fn glyph_ver_metrics_2() { let data = fs::read("tests/fonts/vmtx.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!( font.glyph_ver_metrics(GlyphId(1)).unwrap(), ttf::VerticalMetrics { advance: 1000, top_side_bearing: 83 }, ); } #[test] fn glyph_index_f00_01() { let data = fs::read("tests/fonts/cmap0_font1.otf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.glyph_index('4').unwrap(), GlyphId(17)); assert_eq!(font.glyph_index('5').unwrap(), GlyphId(56)); assert_eq!(font.glyph_index('6').unwrap(), GlyphId(12)); } #[test] fn glyph_index_f02_01() { let data = fs::read("tests/fonts/cmap2_font1.otf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.glyph_index('4').unwrap(), GlyphId(17)); assert_eq!(font.glyph_index('5').unwrap(), GlyphId(56)); assert_eq!(font.glyph_index('6').unwrap(), GlyphId(12)); assert_eq!(font.glyph_index('\u{8432}').unwrap(), GlyphId(20)); assert_eq!(font.glyph_index('\u{8433}').unwrap(), GlyphId(21)); assert_eq!(font.glyph_index('\u{8434}').unwrap(), GlyphId(22)); assert_eq!(font.glyph_index('\u{9232}').unwrap(), GlyphId(23)); assert_eq!(font.glyph_index('\u{9233}').unwrap(), GlyphId(24)); assert_eq!(font.glyph_index('\u{9234}').unwrap(), GlyphId(25)); } #[test] fn glyph_index_f04_01() { let data = fs::read("tests/fonts/TestCMAP14.otf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.glyph_index('芦').unwrap(), GlyphId(1)); } #[test] fn glyph_index_f06_01() { let data = fs::read("tests/fonts/cmap-6.otf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.glyph_index('"').unwrap(), GlyphId(6)); assert_eq!(font.glyph_index('#').unwrap(), GlyphId(7)); assert_eq!(font.glyph_index('$').unwrap(), GlyphId(5)); // Char before character map. // Should not overflow. assert!(font.glyph_index('!').is_err()); // Char after character map. // Should not read out of bounds. assert!(font.glyph_index('A').is_err()); } #[test] fn glyph_index_f10_01() { let data = fs::read("tests/fonts/cmap-10.otf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.glyph_index('\u{109423}').unwrap(), GlyphId(26)); assert_eq!(font.glyph_index('\u{109424}').unwrap(), GlyphId(27)); assert_eq!(font.glyph_index('\u{109425}').unwrap(), GlyphId(32)); // Char before character map. // Should not overflow. assert!(font.glyph_index('!').is_err()); // Char after character map. // Should not read out of bounds. assert!(font.glyph_index('\u{109426}').is_err()); } #[test] fn glyph_index_f12_01() { let data = fs::read("tests/fonts/vmtx.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.glyph_index('明').unwrap(), GlyphId(1)); } #[test] fn glyph_variation_index_01() { let data = fs::read("tests/fonts/TestCMAP14.otf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); assert_eq!(font.glyph_variation_index('芦', '\u{E0101}').unwrap(), GlyphId(2)); } #[test] fn glyphs_kerning_01() { let data = fs::read("tests/fonts/TestKERNOne.otf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); let t_id = font.glyph_index('T').unwrap(); let u_id = font.glyph_index('u').unwrap(); let dotless_i_id = font.glyph_index('\u{131}').unwrap(); assert_eq!(font.glyphs_kerning(t_id, dotless_i_id).unwrap(), -200); assert_eq!(font.glyphs_kerning(t_id, u_id).unwrap(), -200); assert_eq!(font.glyphs_kerning(dotless_i_id, t_id).unwrap(), -200); assert_eq!(font.glyphs_kerning(dotless_i_id, dotless_i_id).unwrap(), 500); assert_eq!(font.glyphs_kerning(u_id, t_id).unwrap(), -200); } #[test] fn glyphs_kerning_02() { let data = fs::read("tests/fonts/TestKERNOne.otf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); // Random GID's. assert!(font.glyphs_kerning(GlyphId(0), GlyphId(0)).is_err()); assert!(font.glyphs_kerning(GlyphId(0), GlyphId(100)).is_err()); } // An attempt to check that table slices are still valid after moving. #[test] fn slicing() { #[inline(never)] fn subslice1(font: &Font) { assert_eq!(font.ascender(), 984); } #[inline(never)] fn subslice2(font: Font) { assert_eq!(font.ascender(), 984); } let data = fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); let font = Font::from_data(&data, 0).unwrap(); subslice1(&font); subslice2(font.clone()); subslice2(font); } ttf-parser-0.3.0/.cargo_vcs_info.json0000644000000001120000000000000131540ustar00{ "git": { "sha1": "c5e032c945674dcc36a03357b17d3d4813ab0030" } } ttf-parser-0.3.0/Cargo.lock0000644000000077630000000000000111520ustar00# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "bencher" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "float-cmp" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "siphasher" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "svgtypes" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "float-cmp 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "time" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ttf-parser" version = "0.3.0" dependencies = [ "bencher 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "svgtypes 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "xmlwriter 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "xmlwriter" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum bencher 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7dfdb4953a096c551ce9ace855a604d702e6e62d77fac690575ae347571717f5" "checksum float-cmp 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7ef4eee449a2818084dad09f4fcd6e6e8932c482d8d94298493226782bb45b5e" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" "checksum svgtypes 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum xmlwriter 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"