├── .github └── workflows │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches ├── Cargo.toml ├── README.md ├── fonts │ ├── README.md │ ├── SourceSansPro-Regular.otf │ ├── SourceSansPro-Regular.ttf │ ├── SourceSansVariable-Roman.otf │ └── SourceSansVariable-Roman.ttf ├── methods_perf.rs ├── methods_perf_x1000.rs └── outline │ ├── .gitignore │ ├── README.md │ ├── meson.build │ ├── outline.cc │ └── subprojects │ ├── google-benchmark.wrap │ └── stb_truetype.wrap ├── c-api ├── .gitignore ├── Cargo.toml ├── README.md ├── cbindgen.toml ├── lib.rs ├── test.c └── ttfparser.h ├── examples ├── font-info.rs ├── font2svg.rs └── wasm │ ├── .gitignore │ ├── README.md │ ├── TTC.ttc │ └── index.html ├── meson.build ├── src ├── aat.rs ├── delta_set.rs ├── ggg │ ├── chained_context.rs │ ├── context.rs │ ├── feature_variations.rs │ ├── layout_table.rs │ ├── lookup.rs │ └── mod.rs ├── language.rs ├── lib.rs ├── parser.rs ├── tables │ ├── ankr.rs │ ├── avar.rs │ ├── cbdt.rs │ ├── cblc.rs │ ├── cff │ │ ├── argstack.rs │ │ ├── cff1.rs │ │ ├── cff2.rs │ │ ├── charset.rs │ │ ├── charstring.rs │ │ ├── dict.rs │ │ ├── encoding.rs │ │ ├── index.rs │ │ ├── mod.rs │ │ └── std_names.rs │ ├── cmap │ │ ├── format0.rs │ │ ├── format10.rs │ │ ├── format12.rs │ │ ├── format13.rs │ │ ├── format14.rs │ │ ├── format2.rs │ │ ├── format4.rs │ │ ├── format6.rs │ │ └── mod.rs │ ├── colr.rs │ ├── cpal.rs │ ├── feat.rs │ ├── fvar.rs │ ├── gdef.rs │ ├── glyf.rs │ ├── gpos.rs │ ├── gsub.rs │ ├── gvar.rs │ ├── head.rs │ ├── hhea.rs │ ├── hmtx.rs │ ├── hvar.rs │ ├── kern.rs │ ├── kerx.rs │ ├── loca.rs │ ├── math.rs │ ├── maxp.rs │ ├── mod.rs │ ├── morx.rs │ ├── mvar.rs │ ├── name.rs │ ├── os2.rs │ ├── post.rs │ ├── sbix.rs │ ├── stat.rs │ ├── svg.rs │ ├── trak.rs │ ├── vhea.rs │ ├── vorg.rs │ └── vvar.rs └── var_store.rs ├── testing-tools ├── font-view │ ├── .gitignore │ ├── README.md │ ├── font-view.pro │ ├── freetypefont.cpp │ ├── freetypefont.h │ ├── glyph.h │ ├── glyphsview.cpp │ ├── glyphsview.h │ ├── harfbuzzfont.cpp │ ├── harfbuzzfont.h │ ├── main.cpp │ ├── mainwindow.cpp │ ├── mainwindow.h │ ├── mainwindow.ui │ ├── ttfparserfont.cpp │ └── ttfparserfont.h └── ttf-fuzz │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── src │ ├── fuzz-glyph-index.rs │ ├── fuzz-outline.rs │ └── fuzz-variable-outline.rs │ └── strip-tables.py └── tests ├── bitmap.rs ├── fonts ├── bitmap.otb ├── colr_1.ttf ├── colr_1_LICENSE ├── colr_1_variable.ttf ├── demo.ttf └── demo.ttx └── tables ├── aat.rs ├── ankr.rs ├── cff1.rs ├── cmap.rs ├── colr.rs ├── feat.rs ├── glyf.rs ├── hmtx.rs ├── main.rs ├── maxp.rs ├── sbix.rs └── trak.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | rust: 14 | - 1.63.0 15 | - stable 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | 20 | - name: Install toolchain 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | profile: minimal 24 | toolchain: ${{ matrix.rust }} 25 | override: true 26 | 27 | - name: Build with no default features 28 | run: cargo build --no-default-features --features=no-std-float 29 | 30 | - name: Build with std 31 | run: cargo build --no-default-features --features=std 32 | 33 | - name: Build with variable-fonts 34 | run: cargo build --no-default-features --features=variable-fonts,no-std-float 35 | 36 | - name: Build with all features 37 | run: cargo build --all-features 38 | 39 | - name: Run tests 40 | run: cargo test 41 | 42 | - name: Build C API 43 | working-directory: c-api 44 | run: cargo build --no-default-features 45 | 46 | - name: Build C API with variable-fonts 47 | working-directory: c-api 48 | run: cargo build --no-default-features --features=variable-fonts 49 | 50 | - name: Test C API 51 | working-directory: c-api 52 | run: | 53 | cargo build 54 | gcc test.c -o test -L./target/debug/ -lttfparser -Werror -fsanitize=address 55 | env LD_LIBRARY_PATH=./target/debug/ ./test 56 | 57 | - name: Build benches 58 | working-directory: benches 59 | run: cargo bench dummy # `cargo build` will not actually build it 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .directory 4 | .DS_Store 5 | .vscode 6 | .idea 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ttf-parser" 3 | version = "0.25.1" 4 | authors = [ 5 | "Caleb Maclennan ", 6 | "Laurenz Stampfl ", 7 | "Yevhenii Reizner ", 8 | "خالد حسني (Khaled Hosny) " 9 | ] 10 | keywords = ["ttf", "truetype", "opentype"] 11 | categories = ["parser-implementations"] 12 | license = "MIT OR Apache-2.0" 13 | description = "A high-level, safe, zero-allocation font parser for TrueType, OpenType, and AAT." 14 | repository = "https://github.com/harfbuzz/ttf-parser" 15 | documentation = "https://docs.rs/ttf-parser/" 16 | readme = "README.md" 17 | edition = "2018" 18 | rust-version = "1.63.0" 19 | exclude = ["benches/**"] 20 | 21 | [dependencies] 22 | core_maths = { version = "0.1.0", optional = true } # only for no_std builds 23 | 24 | [features] 25 | default = ["std", "opentype-layout", "apple-layout", "variable-fonts", "glyph-names"] 26 | # Enables the use of the standard library. 27 | # When disabled, the `no-std-float` feature must be enabled instead. 28 | std = [] 29 | no-std-float = ["core_maths"] 30 | # Enables variable fonts support. Increases binary size almost twice. 31 | # Includes avar, CFF2, fvar, gvar, HVAR, MVAR and VVAR tables. 32 | variable-fonts = [] 33 | # Enables GDEF, GPOS, GSUB and MATH tables. 34 | opentype-layout = [] 35 | # Enables ankr, feat, format1 subtable in kern, kerx, morx and trak tables. 36 | apple-layout = [] 37 | # Enables glyph name query via `Face::glyph_name`. 38 | # TrueType fonts do not store default glyph names, to reduce file size, 39 | # which means we have to store them in ttf-parser. And there are almost 500 of them. 40 | # By disabling this feature a user can reduce binary size a bit. 41 | glyph-names = [] 42 | # Enables heap allocations during gvar table parsing used by Apple's variable fonts. 43 | # Due to the way gvar table is structured, we cannot avoid allocations. 44 | # By default, only up to 32 variable tuples will be allocated on the stack, 45 | # while the spec allows up to 4095. Most variable fonts use 10-20 tuples, 46 | # so our limit is suitable for most of the cases. But if you need full support, you have to 47 | # enable this feature. 48 | gvar-alloc = ["std"] 49 | 50 | [dev-dependencies] 51 | base64 = "0.22" 52 | pico-args = "0.5" 53 | tiny-skia-path = "0.11" 54 | xmlwriter = "0.1" 55 | 56 | [package.metadata.typos.default] 57 | locale = "en-us" 58 | 59 | [package.metadata.typos.default.extend-words] 60 | loca = "loca" 61 | ot = "ot" 62 | trak = "trak" 63 | wdth = "wdth" 64 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Yevhenii Reizner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /benches/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "benchmarks" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | bencher = "0.1" 8 | ttf-parser = { path = "../" } 9 | 10 | [[bench]] 11 | name = "methods_perf" 12 | path = "methods_perf.rs" 13 | harness = false 14 | 15 | [[bench]] 16 | name = "methods_perf_x1000" 17 | path = "methods_perf_x1000.rs" 18 | harness = false 19 | 20 | [profile.release] 21 | lto = true 22 | codegen-units = 1 23 | -------------------------------------------------------------------------------- /benches/README.md: -------------------------------------------------------------------------------- 1 | ## Build & Run 2 | 3 | ```sh 4 | cargo bench 5 | ``` 6 | -------------------------------------------------------------------------------- /benches/fonts/README.md: -------------------------------------------------------------------------------- 1 | ### License 2 | 3 | - SourceSansPro-Regular.otf - SIL OFL 1.1 4 | - SourceSansPro-Regular.ttf - SIL OFL 1.1 5 | - SourceSansVariable-Roman.otf - SIL OFL 1.1 6 | - SourceSansVariable-Roman.ttf - SIL OFL 1.1 7 | 8 | ### Origin 9 | 10 | https://github.com/adobe-fonts/source-sans-pro 11 | -------------------------------------------------------------------------------- /benches/fonts/SourceSansPro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harfbuzz/ttf-parser/a6813b4778f7513c207a389b4eab7e2369c20e70/benches/fonts/SourceSansPro-Regular.otf -------------------------------------------------------------------------------- /benches/fonts/SourceSansPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harfbuzz/ttf-parser/a6813b4778f7513c207a389b4eab7e2369c20e70/benches/fonts/SourceSansPro-Regular.ttf -------------------------------------------------------------------------------- /benches/fonts/SourceSansVariable-Roman.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harfbuzz/ttf-parser/a6813b4778f7513c207a389b4eab7e2369c20e70/benches/fonts/SourceSansVariable-Roman.otf -------------------------------------------------------------------------------- /benches/fonts/SourceSansVariable-Roman.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harfbuzz/ttf-parser/a6813b4778f7513c207a389b4eab7e2369c20e70/benches/fonts/SourceSansVariable-Roman.ttf -------------------------------------------------------------------------------- /benches/methods_perf_x1000.rs: -------------------------------------------------------------------------------- 1 | use ttf_parser as ttf; 2 | 3 | fn units_per_em(bencher: &mut bencher::Bencher) { 4 | let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); 5 | let face = ttf::Face::parse(&font_data, 0).unwrap(); 6 | bencher.iter(|| { 7 | for _ in 0..1000 { 8 | bencher::black_box(face.units_per_em()); 9 | } 10 | }) 11 | } 12 | 13 | fn width(bencher: &mut bencher::Bencher) { 14 | let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); 15 | let face = ttf::Face::parse(&font_data, 0).unwrap(); 16 | bencher.iter(|| { 17 | for _ in 0..1000 { 18 | bencher::black_box(face.width()); 19 | } 20 | }) 21 | } 22 | 23 | fn ascender(bencher: &mut bencher::Bencher) { 24 | let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); 25 | let face = ttf::Face::parse(&font_data, 0).unwrap(); 26 | bencher.iter(|| { 27 | for _ in 0..1000 { 28 | bencher::black_box(face.ascender()); 29 | } 30 | }) 31 | } 32 | 33 | fn underline_metrics(bencher: &mut bencher::Bencher) { 34 | let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); 35 | let face = ttf::Face::parse(&font_data, 0).unwrap(); 36 | bencher.iter(|| { 37 | for _ in 0..1000 { 38 | bencher::black_box(face.underline_metrics().unwrap()); 39 | } 40 | }) 41 | } 42 | 43 | fn strikeout_metrics(bencher: &mut bencher::Bencher) { 44 | let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); 45 | let face = ttf::Face::parse(&font_data, 0).unwrap(); 46 | bencher.iter(|| { 47 | for _ in 0..1000 { 48 | bencher::black_box(face.strikeout_metrics().unwrap()); 49 | } 50 | }) 51 | } 52 | 53 | fn subscript_metrics(bencher: &mut bencher::Bencher) { 54 | let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); 55 | let face = ttf::Face::parse(&font_data, 0).unwrap(); 56 | bencher.iter(|| { 57 | for _ in 0..1000 { 58 | bencher::black_box(face.subscript_metrics().unwrap()); 59 | } 60 | }) 61 | } 62 | 63 | fn x_height(bencher: &mut bencher::Bencher) { 64 | let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); 65 | let face = ttf::Face::parse(&font_data, 0).unwrap(); 66 | bencher.iter(|| { 67 | for _ in 0..1000 { 68 | bencher::black_box(face.x_height().unwrap()); 69 | } 70 | }) 71 | } 72 | 73 | fn glyph_hor_advance(bencher: &mut bencher::Bencher) { 74 | let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); 75 | let face = ttf::Face::parse(&font_data, 0).unwrap(); 76 | bencher.iter(|| { 77 | for _ in 0..1000 { 78 | bencher::black_box(face.glyph_hor_advance(ttf::GlyphId(2)).unwrap()); 79 | } 80 | }) 81 | } 82 | 83 | fn glyph_hor_side_bearing(bencher: &mut bencher::Bencher) { 84 | let font_data = std::fs::read("fonts/SourceSansPro-Regular.ttf").unwrap(); 85 | let face = ttf::Face::parse(&font_data, 0).unwrap(); 86 | bencher.iter(|| { 87 | for _ in 0..1000 { 88 | bencher::black_box(face.glyph_hor_side_bearing(ttf::GlyphId(2)).unwrap()); 89 | } 90 | }) 91 | } 92 | 93 | bencher::benchmark_group!( 94 | perf, 95 | units_per_em, 96 | width, 97 | ascender, 98 | underline_metrics, 99 | strikeout_metrics, 100 | subscript_metrics, 101 | x_height, 102 | glyph_hor_advance, 103 | glyph_hor_side_bearing 104 | ); 105 | bencher::benchmark_main!(perf); 106 | -------------------------------------------------------------------------------- /benches/outline/.gitignore: -------------------------------------------------------------------------------- 1 | /subprojects 2 | /builddir 3 | -------------------------------------------------------------------------------- /benches/outline/README.md: -------------------------------------------------------------------------------- 1 | ## Build & Run 2 | 3 | ```sh 4 | cargo build --release --manifest-path ../../c-api/Cargo.toml 5 | meson builddir --buildtype release 6 | ninja -C builddir 7 | builddir/outline 8 | ``` 9 | -------------------------------------------------------------------------------- /benches/outline/meson.build: -------------------------------------------------------------------------------- 1 | project('benchmarks', 'cpp', default_options : ['cpp_std=c++14']) 2 | 3 | google_benchmark = subproject('google-benchmark') 4 | google_benchmark_dep = google_benchmark.get_variable('google_benchmark_dep') 5 | 6 | stb_truetype = subproject('stb_truetype') 7 | stb_truetype_dep = stb_truetype.get_variable('stb_truetype_dep') 8 | 9 | freetype_dep = dependency('freetype2', version : '>=2.8') 10 | 11 | # maybe there is a better way 12 | ttf_parser_dep = meson.get_compiler('cpp').find_library('ttfparser', 13 | dirs : [meson.current_source_dir() + '/../../c-api/target/release']) 14 | 15 | executable('outline', 'outline.cc', 16 | dependencies : [google_benchmark_dep, freetype_dep, stb_truetype_dep, ttf_parser_dep], 17 | include_directories : ['../../c-api'], 18 | override_options : ['cpp_std=c++14']) 19 | -------------------------------------------------------------------------------- /benches/outline/subprojects/google-benchmark.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = benchmark-1.4.1 3 | source_url = https://github.com/google/benchmark/archive/v1.4.1.zip 4 | source_filename = benchmark-1.4.1.zip 5 | source_hash = 61ae07eb5d4a0b02753419eb17a82b7d322786bb36ab62bd3df331a4d47c00a7 6 | 7 | 8 | patch_url = https://wrapdb.mesonbuild.com/v1/projects/google-benchmark/1.4.1/1/get_zip 9 | patch_filename = google-benchmark-1.4.1-1-wrap.zip 10 | patch_hash = 4cc5fe02ebd4fc82e110919b7977d7463eb2a99e4ecb9feca920eab6fd911d67 11 | -------------------------------------------------------------------------------- /benches/outline/subprojects/stb_truetype.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/RazrFalcon/stb_truetype_meson.git 3 | revision = head 4 | -------------------------------------------------------------------------------- /c-api/.gitignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /c-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ttf-parser-capi" 3 | version = "0.25.1" 4 | authors = [ 5 | "Caleb Maclennan ", 6 | "Laurenz Stampfl ", 7 | "Yevhenii Reizner ", 8 | "خالد حسني (Khaled Hosny) " 9 | ] 10 | license = "MIT" 11 | edition = "2018" 12 | 13 | [lib] 14 | name = "ttfparser" 15 | path = "lib.rs" 16 | crate-type = ["cdylib"] 17 | 18 | [dependencies] 19 | ttf-parser = { path = "../", default-features = false, features = ["std", "glyph-names"] } 20 | 21 | [features] 22 | default = ["variable-fonts"] 23 | # Enables variable fonts support. Adds about 50KiB. 24 | variable-fonts = ["ttf-parser/variable-fonts"] 25 | # Enables heap allocations during gvar table parsing used by Apple's variable fonts. 26 | # Due to the way gvar table is structured, we cannot avoid allocations. 27 | # By default, only up to 32 variable tuples will be allocated on the stack, 28 | # while the spec allows up to 4095. Most variable fonts use 10-20 tuples, 29 | # so our limit is suitable for most of the cases. But if you need full support, you have to 30 | # enable this feature. 31 | gvar-alloc = ["ttf-parser/gvar-alloc"] 32 | # opentype-layout is not supported. 33 | # apple-layout is not supported. 34 | capi = [] 35 | 36 | [profile.release] 37 | lto = true 38 | 39 | [package.metadata.capi.header] 40 | generation = false 41 | 42 | [package.metadata.capi.install.include] 43 | asset = [{ from="ttfparser.h" }] 44 | -------------------------------------------------------------------------------- /c-api/README.md: -------------------------------------------------------------------------------- 1 | C bindings to the Rust's `ttf-parser` library. 2 | 3 | Provides access only to most common methods. 4 | 5 | ## Build 6 | 7 | ```sh 8 | cargo build --release 9 | ``` 10 | 11 | This will produce a dynamic library that can be located at `target/release`. 12 | Be sure to strip it. 13 | 14 | ## Run tests 15 | 16 | ```sh 17 | cargo build 18 | gcc test.c -g -o test -L./target/debug/ -lttfparser 19 | env LD_LIBRARY_PATH=./target/debug/ ./test 20 | ``` 21 | 22 | ## Using cargo-c 23 | This crate can be built and installed using [cargo-c](https://crates.io/crates/cargo-c). 24 | 25 | ### Build and test 26 | To build and used it uninstalled 27 | ```sh 28 | cargo cbuild -v --library-type staticlib 29 | export PKG_CONFIG_PATH= 30 | gcc test.c -g -o test `pkg-config --libs --cflags tffparser` 31 | ./test 32 | ``` 33 | 34 | ### Install 35 | To install it system-wide 36 | ```sh 37 | cargo cinstall --destdir /tmp/ttf-parser 38 | sudo cp -a /tmp/ttf-parser/* / 39 | ``` 40 | 41 | ## Safety 42 | 43 | - The library doesn't use `unsafe` (except the bindings itself). 44 | - All data access is bound checked. 45 | - No heap allocations, so crash due to OOM is not possible. 46 | - Technically, should use less than 64KiB of stack in worst case scenario. 47 | - All methods are thread-safe. 48 | - All recursive methods have a depth limit. 49 | - Most of arithmetic operations are checked. 50 | - Most of numeric casts are checked. 51 | - Rust panics will be caught at the FFI boundary. 52 | 53 | ## Header generation 54 | 55 | The `ttfparser.h` is generated via [cbindgen](https://github.com/eqrion/cbindgen) 56 | and then manually edited a bit. 57 | -------------------------------------------------------------------------------- /c-api/cbindgen.toml: -------------------------------------------------------------------------------- 1 | language = "C" 2 | include_guard = "TTFP_H" 3 | braces = "SameLine" 4 | tab_width = 4 5 | documentation_style = "doxy" 6 | header = "/**\n * @file ttfparser.h\n *\n * A C API for the Rust's ttf-parser library.\n */" 7 | cpp_compat = true 8 | 9 | [defines] 10 | "feature = variable-fonts" = "TTFP_VARIABLE_FONTS" 11 | 12 | [fn] 13 | sort_by = "None" 14 | 15 | [enum] 16 | rename_variants = "ScreamingSnakeCase" 17 | prefix_with_name = true 18 | 19 | [parse] 20 | parse_deps = true 21 | 22 | [export.rename] 23 | "GlyphId" = "uint16_t" 24 | "LineMetrics" = "ttfp_line_metrics" 25 | "NormalizedCoord" = "int16_t" 26 | "ScriptMetrics" = "ttfp_script_metrics" 27 | "TableName" = "ttfp_table_name" 28 | "Tag" = "ttfp_tag" 29 | "Rect" = "ttfp_rect" 30 | "VariationAxis" = "ttfp_variation_axis" 31 | -------------------------------------------------------------------------------- /c-api/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "ttfparser.h" 7 | 8 | void move_to_cb(float x, float y, void *data) 9 | { 10 | uint32_t *counter = (uint32_t*)data; 11 | *counter += 1; 12 | } 13 | 14 | void line_to_cb(float x, float y, void *data) 15 | { 16 | uint32_t *counter = (uint32_t*)data; 17 | *counter += 1; 18 | } 19 | 20 | void quad_to_cb(float x1, float y1, float x, float y, void *data) 21 | { 22 | uint32_t *counter = (uint32_t*)data; 23 | *counter += 1; 24 | } 25 | 26 | void curve_to_cb(float x1, float y1, float x2, float y2, float x, float y, void *data) 27 | { 28 | uint32_t *counter = (uint32_t*)data; 29 | *counter += 1; 30 | } 31 | 32 | void close_path_cb(void *data) 33 | { 34 | uint32_t *counter = (uint32_t*)data; 35 | *counter += 1; 36 | } 37 | 38 | int main() { 39 | // Read the file first. 40 | FILE *file = fopen("../benches/fonts/SourceSansPro-Regular.ttf", "rb"); 41 | if (file == NULL) { 42 | return -1; 43 | } 44 | 45 | fseek(file, 0, SEEK_END); 46 | long fsize = ftell(file); 47 | fseek(file, 0, SEEK_SET); 48 | 49 | char *font_data = (char*)malloc(fsize + 1); 50 | fread(font_data, 1, fsize, file); 51 | fclose(file); 52 | 53 | // Test functions. 54 | // We mainly interested in linking errors. 55 | assert(ttfp_fonts_in_collection(font_data, fsize) == -1); 56 | 57 | ttfp_face *face = (ttfp_face*)alloca(ttfp_face_size_of()); 58 | assert(ttfp_face_init(font_data, fsize, 0, face)); 59 | 60 | uint16_t a_gid = ttfp_get_glyph_index(face, 0x0041); // A 61 | assert(a_gid == 2); 62 | assert(ttfp_get_glyph_index(face, 0xFFFFFFFF) == 0); 63 | assert(ttfp_get_glyph_var_index(face, 0x0041, 0xFE03) == 0); 64 | 65 | assert(ttfp_get_glyph_hor_advance(face, 0x0041) == 544); 66 | assert(ttfp_get_glyph_hor_side_bearing(face, 0x0041) == 3); 67 | assert(ttfp_get_glyph_ver_advance(face, 0x0041) == 0); 68 | assert(ttfp_get_glyph_ver_side_bearing(face, 0x0041) == 0); 69 | assert(ttfp_get_glyph_y_origin(face, a_gid) == 0); 70 | 71 | assert(ttfp_get_name_records_count(face) == 20); 72 | ttfp_name_record record; 73 | assert(ttfp_get_name_record(face, 100, &record) == false); 74 | assert(ttfp_get_name_record(face, 1, &record) == true); 75 | assert(record.name_id == 1); 76 | 77 | char family_name[30]; 78 | assert(ttfp_get_name_record_string(face, 1, family_name, 30)); 79 | 80 | assert(ttfp_get_units_per_em(face) == 1000); 81 | assert(ttfp_get_ascender(face) == 984); 82 | assert(ttfp_get_descender(face) == -273); 83 | assert(ttfp_get_height(face) == 1257); 84 | assert(ttfp_get_line_gap(face) == 0); 85 | assert(ttfp_is_regular(face) == true); 86 | assert(ttfp_is_italic(face) == false); 87 | assert(ttfp_is_bold(face) == false); 88 | assert(ttfp_is_oblique(face) == false); 89 | assert(ttfp_get_weight(face) == 400); 90 | assert(ttfp_get_width(face) == 5); 91 | assert(ttfp_get_x_height(face) == 486); 92 | assert(ttfp_get_number_of_glyphs(face) == 1974); 93 | 94 | ttfp_rect g_bbox = ttfp_get_global_bounding_box(face); 95 | assert(g_bbox.x_min == -454); 96 | assert(g_bbox.y_min == -293); 97 | assert(g_bbox.x_max == 2159); 98 | assert(g_bbox.y_max == 968); 99 | 100 | ttfp_line_metrics line_metrics; 101 | assert(ttfp_get_underline_metrics(face, &line_metrics)); 102 | assert(line_metrics.position == -50); 103 | assert(line_metrics.thickness == 50); 104 | 105 | assert(ttfp_get_strikeout_metrics(face, &line_metrics)); 106 | assert(line_metrics.position == 291); 107 | assert(line_metrics.thickness == 50); 108 | 109 | ttfp_script_metrics script_metrics; 110 | assert(ttfp_get_subscript_metrics(face, &script_metrics)); 111 | assert(script_metrics.x_size == 650); 112 | assert(script_metrics.y_size == 600); 113 | assert(script_metrics.x_offset == 0); 114 | assert(script_metrics.y_offset == 75); 115 | 116 | assert(ttfp_get_superscript_metrics(face, &script_metrics)); 117 | assert(script_metrics.x_size == 650); 118 | assert(script_metrics.y_size == 600); 119 | assert(script_metrics.x_offset == 0); 120 | assert(script_metrics.y_offset == 350); 121 | 122 | ttfp_rect a_bbox = {0}; 123 | assert(ttfp_get_glyph_bbox(face, a_gid, &a_bbox)); 124 | assert(a_bbox.x_min == 3); 125 | assert(a_bbox.y_min == 0); 126 | assert(a_bbox.x_max == 541); 127 | assert(a_bbox.y_max == 656); 128 | 129 | assert(!ttfp_get_glyph_bbox(face, 0xFFFF, &a_bbox)); 130 | 131 | uint32_t counter = 0; 132 | ttfp_outline_builder builder; 133 | builder.move_to = move_to_cb; 134 | builder.line_to = line_to_cb; 135 | builder.quad_to = quad_to_cb; 136 | builder.curve_to = curve_to_cb; 137 | builder.close_path = close_path_cb; 138 | assert(ttfp_outline_glyph(face, builder, &counter, a_gid, &a_bbox)); 139 | assert(counter == 20); 140 | // The same as via ttfp_get_glyph_bbox() 141 | assert(a_bbox.x_min == 3); 142 | assert(a_bbox.y_min == 0); 143 | assert(a_bbox.x_max == 541); 144 | assert(a_bbox.y_max == 656); 145 | 146 | char glyph_name[256]; 147 | assert(ttfp_get_glyph_name(face, a_gid, glyph_name)); 148 | assert(strcmp(glyph_name, "A") == 0); 149 | 150 | free(font_data); 151 | 152 | return 0; 153 | } 154 | -------------------------------------------------------------------------------- /examples/font-info.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let args: Vec<_> = std::env::args().collect(); 3 | if args.len() != 2 { 4 | println!("Usage:\n\tfont-info font.ttf"); 5 | std::process::exit(1); 6 | } 7 | 8 | let font_data = std::fs::read(&args[1]).unwrap(); 9 | 10 | let now = std::time::Instant::now(); 11 | 12 | let face = match ttf_parser::Face::parse(&font_data, 0) { 13 | Ok(f) => f, 14 | Err(e) => { 15 | eprint!("Error: {}.", e); 16 | std::process::exit(1); 17 | } 18 | }; 19 | 20 | let mut family_names = Vec::new(); 21 | for name in face.names() { 22 | if name.name_id == ttf_parser::name_id::FULL_NAME && name.is_unicode() { 23 | if let Some(family_name) = name.to_string() { 24 | let language = name.language(); 25 | family_names.push(format!( 26 | "{} ({}, {})", 27 | family_name, 28 | language.primary_language(), 29 | language.region() 30 | )); 31 | } 32 | } 33 | } 34 | 35 | let post_script_name = face 36 | .names() 37 | .into_iter() 38 | .find(|name| name.name_id == ttf_parser::name_id::POST_SCRIPT_NAME && name.is_unicode()) 39 | .and_then(|name| name.to_string()); 40 | 41 | println!("Family names: {:?}", family_names); 42 | println!("PostScript name: {:?}", post_script_name); 43 | println!("Units per EM: {:?}", face.units_per_em()); 44 | println!("Ascender: {}", face.ascender()); 45 | println!("Descender: {}", face.descender()); 46 | println!("Line gap: {}", face.line_gap()); 47 | println!("Global bbox: {:?}", face.global_bounding_box()); 48 | println!("Number of glyphs: {}", face.number_of_glyphs()); 49 | println!("Underline: {:?}", face.underline_metrics()); 50 | println!("X height: {:?}", face.x_height()); 51 | println!("Weight: {:?}", face.weight()); 52 | println!("Width: {:?}", face.width()); 53 | println!("Regular: {}", face.is_regular()); 54 | println!("Italic: {}", face.is_italic()); 55 | println!("Bold: {}", face.is_bold()); 56 | println!("Oblique: {}", face.is_oblique()); 57 | println!("Strikeout: {:?}", face.strikeout_metrics()); 58 | println!("Subscript: {:?}", face.subscript_metrics()); 59 | println!("Superscript: {:?}", face.superscript_metrics()); 60 | println!("Permissions: {:?}", face.permissions()); 61 | println!("Variable: {:?}", face.is_variable()); 62 | 63 | #[cfg(feature = "opentype-layout")] 64 | { 65 | if let Some(ref table) = face.tables().gpos { 66 | print_opentype_layout("positioning", table); 67 | } 68 | 69 | if let Some(ref table) = face.tables().gsub { 70 | print_opentype_layout("substitution", table); 71 | } 72 | } 73 | 74 | #[cfg(feature = "variable-fonts")] 75 | { 76 | if face.is_variable() { 77 | println!("Variation axes:"); 78 | for axis in face.variation_axes() { 79 | println!( 80 | " {} {}..{}, default {}", 81 | axis.tag, axis.min_value, axis.max_value, axis.def_value 82 | ); 83 | } 84 | } 85 | } 86 | 87 | if let Some(stat) = face.tables().stat { 88 | println!("Style attributes:"); 89 | 90 | println!(" Axes:"); 91 | for axis in stat.axes { 92 | println!(" {}", axis.tag); 93 | if let Some(subtable) = stat.subtable_for_axis(axis.tag, None) { 94 | println!(" {:?}", subtable) 95 | } 96 | } 97 | } 98 | 99 | println!("Elapsed: {}us", now.elapsed().as_micros()); 100 | } 101 | 102 | fn print_opentype_layout(name: &str, table: &ttf_parser::opentype_layout::LayoutTable) { 103 | println!("OpenType {}:", name); 104 | println!(" Scripts:"); 105 | for script in table.scripts { 106 | println!(" {}", script.tag); 107 | 108 | if script.languages.is_empty() { 109 | println!(" No languages"); 110 | continue; 111 | } 112 | 113 | println!(" Languages:"); 114 | for lang in script.languages { 115 | println!(" {}", lang.tag); 116 | } 117 | } 118 | 119 | let mut features: Vec<_> = table.features.into_iter().map(|f| f.tag).collect(); 120 | features.dedup(); 121 | println!(" Features:"); 122 | for feature in features { 123 | println!(" {}", feature); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /examples/wasm/.gitignore: -------------------------------------------------------------------------------- 1 | *.wasm 2 | -------------------------------------------------------------------------------- /examples/wasm/README.md: -------------------------------------------------------------------------------- 1 | # ttf-parser as a WebAssembly module 2 | 3 | ## Build 4 | 5 | ```sh 6 | rustup target add wasm32-unknown-unknown 7 | 8 | cargo build --target wasm32-unknown-unknown --release --manifest-path ../../c-api/Cargo.toml 9 | cp ../../c-api/target/wasm32-unknown-unknown/release/ttfparser.wasm . 10 | ``` 11 | 12 | ## Run 13 | 14 | You can use any webserver that can serve `index.html`. Here is a Python example: 15 | 16 | ```sh 17 | python -m http.server 18 | ``` 19 | -------------------------------------------------------------------------------- /examples/wasm/TTC.ttc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harfbuzz/ttf-parser/a6813b4778f7513c207a389b4eab7e2369c20e70/examples/wasm/TTC.ttc -------------------------------------------------------------------------------- /examples/wasm/index.html: -------------------------------------------------------------------------------- 1 |

ttf-parser in WebAssembly

2 |

(supports font files drag and drop)

3 |

TTC.ttc:

4 |

ttfp_fonts_in_collection():

5 |

ttfp_is_variable():

6 |

ttfp_get_weight():

7 | 68 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('ttf-parser', 'rust') 2 | 3 | add_project_arguments(['--edition=2018'], language: 'rust') 4 | 5 | ttf_parser = static_library('ttf_parser_capi', 'c-api/lib.rs', rust_crate_type: 'staticlib', 6 | link_with: static_library('ttf_parser', 'src/lib.rs'), 7 | ) 8 | 9 | ttf_parser_dep = declare_dependency( 10 | link_with: ttf_parser, 11 | include_directories: 'c-api/', 12 | ) 13 | -------------------------------------------------------------------------------- /src/delta_set.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | 3 | use crate::parser::Stream; 4 | 5 | #[derive(Clone, Copy, Debug)] 6 | pub(crate) struct DeltaSetIndexMap<'a> { 7 | data: &'a [u8], 8 | } 9 | 10 | impl<'a> DeltaSetIndexMap<'a> { 11 | #[inline] 12 | pub(crate) fn new(data: &'a [u8]) -> Self { 13 | DeltaSetIndexMap { data } 14 | } 15 | 16 | #[inline] 17 | pub(crate) fn map(&self, mut index: u32) -> Option<(u16, u16)> { 18 | let mut s = Stream::new(self.data); 19 | let format = s.read::()?; 20 | let entry_format = s.read::()?; 21 | let map_count = if format == 0 { 22 | s.read::()? as u32 23 | } else { 24 | s.read::()? 25 | }; 26 | 27 | if map_count == 0 { 28 | return None; 29 | } 30 | 31 | // 'If a given glyph ID is greater than mapCount-1, then the last entry is used.' 32 | if index >= map_count { 33 | index = map_count - 1; 34 | } 35 | 36 | let entry_size = ((entry_format >> 4) & 3) + 1; 37 | let inner_index_bit_count = u32::from((entry_format & 0xF) + 1); 38 | 39 | s.advance(usize::from(entry_size) * usize::try_from(index).ok()?); 40 | 41 | let mut n = 0u32; 42 | for b in s.read_bytes(usize::from(entry_size))? { 43 | n = (n << 8) + u32::from(*b); 44 | } 45 | 46 | let outer_index = n >> inner_index_bit_count; 47 | let inner_index = n & ((1 << inner_index_bit_count) - 1); 48 | Some(( 49 | u16::try_from(outer_index).ok()?, 50 | u16::try_from(inner_index).ok()?, 51 | )) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ggg/context.rs: -------------------------------------------------------------------------------- 1 | use super::{ClassDefinition, Coverage, LookupIndex}; 2 | use crate::parser::{FromData, FromSlice, LazyArray16, LazyOffsetArray16, Stream}; 3 | 4 | /// A [Contextual Lookup Subtable]( 5 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#seqctxt1). 6 | #[allow(missing_docs)] 7 | #[derive(Clone, Copy, Debug)] 8 | pub enum ContextLookup<'a> { 9 | /// Simple glyph contexts. 10 | Format1 { 11 | coverage: Coverage<'a>, 12 | sets: SequenceRuleSets<'a>, 13 | }, 14 | /// Class-based glyph contexts. 15 | Format2 { 16 | coverage: Coverage<'a>, 17 | classes: ClassDefinition<'a>, 18 | sets: SequenceRuleSets<'a>, 19 | }, 20 | /// Coverage-based glyph contexts. 21 | Format3 { 22 | coverage: Coverage<'a>, 23 | coverages: LazyOffsetArray16<'a, Coverage<'a>>, 24 | lookups: LazyArray16<'a, SequenceLookupRecord>, 25 | }, 26 | } 27 | 28 | impl<'a> ContextLookup<'a> { 29 | pub(crate) fn parse(data: &'a [u8]) -> Option { 30 | let mut s = Stream::new(data); 31 | match s.read::()? { 32 | 1 => { 33 | let coverage = Coverage::parse(s.read_at_offset16(data)?)?; 34 | let count = s.read::()?; 35 | let offsets = s.read_array16(count)?; 36 | Some(Self::Format1 { 37 | coverage, 38 | sets: SequenceRuleSets::new(data, offsets), 39 | }) 40 | } 41 | 2 => { 42 | let coverage = Coverage::parse(s.read_at_offset16(data)?)?; 43 | let classes = ClassDefinition::parse(s.read_at_offset16(data)?)?; 44 | let count = s.read::()?; 45 | let offsets = s.read_array16(count)?; 46 | Some(Self::Format2 { 47 | coverage, 48 | classes, 49 | sets: SequenceRuleSets::new(data, offsets), 50 | }) 51 | } 52 | 3 => { 53 | let input_count = s.read::()?; 54 | let lookup_count = s.read::()?; 55 | let coverage = Coverage::parse(s.read_at_offset16(data)?)?; 56 | let coverages = s.read_array16(input_count.checked_sub(1)?)?; 57 | let lookups = s.read_array16(lookup_count)?; 58 | Some(Self::Format3 { 59 | coverage, 60 | coverages: LazyOffsetArray16::new(data, coverages), 61 | lookups, 62 | }) 63 | } 64 | _ => None, 65 | } 66 | } 67 | 68 | /// Returns the subtable coverage. 69 | #[inline] 70 | pub fn coverage(&self) -> Coverage<'a> { 71 | match self { 72 | Self::Format1 { coverage, .. } => *coverage, 73 | Self::Format2 { coverage, .. } => *coverage, 74 | Self::Format3 { coverage, .. } => *coverage, 75 | } 76 | } 77 | } 78 | 79 | /// A list of [`SequenceRuleSet`]s. 80 | pub type SequenceRuleSets<'a> = LazyOffsetArray16<'a, SequenceRuleSet<'a>>; 81 | 82 | impl<'a> FromSlice<'a> for SequenceRuleSet<'a> { 83 | fn parse(data: &'a [u8]) -> Option { 84 | Self::parse(data) 85 | } 86 | } 87 | 88 | impl<'a> FromSlice<'a> for SequenceRule<'a> { 89 | fn parse(data: &'a [u8]) -> Option { 90 | let mut s = Stream::new(data); 91 | let input_count = s.read::()?; 92 | let lookup_count = s.read::()?; 93 | let input = s.read_array16(input_count.checked_sub(1)?)?; 94 | let lookups = s.read_array16(lookup_count)?; 95 | Some(Self { input, lookups }) 96 | } 97 | } 98 | 99 | /// A set of [`SequenceRule`]s. 100 | pub type SequenceRuleSet<'a> = LazyOffsetArray16<'a, SequenceRule<'a>>; 101 | 102 | /// A sequence rule. 103 | #[allow(missing_docs)] 104 | #[derive(Clone, Copy, Debug)] 105 | pub struct SequenceRule<'a> { 106 | pub input: LazyArray16<'a, u16>, 107 | pub lookups: LazyArray16<'a, SequenceLookupRecord>, 108 | } 109 | 110 | /// A sequence rule record. 111 | #[allow(missing_docs)] 112 | #[derive(Clone, Copy, Debug)] 113 | pub struct SequenceLookupRecord { 114 | pub sequence_index: u16, 115 | pub lookup_list_index: LookupIndex, 116 | } 117 | 118 | impl FromData for SequenceLookupRecord { 119 | const SIZE: usize = 4; 120 | 121 | #[inline] 122 | fn parse(data: &[u8]) -> Option { 123 | let mut s = Stream::new(data); 124 | Some(Self { 125 | sequence_index: s.read::()?, 126 | lookup_list_index: s.read::()?, 127 | }) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/ggg/lookup.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::{ 2 | FromData, FromSlice, LazyArray16, LazyOffsetArray16, Offset, Offset16, Offset32, Stream, 3 | }; 4 | 5 | /// A list of [`Lookup`] values. 6 | pub type LookupList<'a> = LazyOffsetArray16<'a, Lookup<'a>>; 7 | 8 | /// A [Lookup Table](https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#lookup-table). 9 | #[derive(Clone, Copy, Debug)] 10 | pub struct Lookup<'a> { 11 | /// Lookup qualifiers. 12 | pub flags: LookupFlags, 13 | /// Available subtables. 14 | pub subtables: LookupSubtables<'a>, 15 | /// Index into GDEF mark glyph sets structure. 16 | pub mark_filtering_set: Option, 17 | } 18 | 19 | impl<'a> FromSlice<'a> for Lookup<'a> { 20 | fn parse(data: &'a [u8]) -> Option { 21 | let mut s = Stream::new(data); 22 | let kind = s.read::()?; 23 | let flags = s.read::()?; 24 | let count = s.read::()?; 25 | let offsets = s.read_array16(count)?; 26 | 27 | let mut mark_filtering_set: Option = None; 28 | if flags.use_mark_filtering_set() { 29 | mark_filtering_set = Some(s.read::()?); 30 | } 31 | 32 | Some(Self { 33 | flags, 34 | subtables: LookupSubtables { 35 | kind, 36 | data, 37 | offsets, 38 | }, 39 | mark_filtering_set, 40 | }) 41 | } 42 | } 43 | 44 | /// A trait for parsing Lookup subtables. 45 | /// 46 | /// Internal use only. 47 | pub trait LookupSubtable<'a>: Sized { 48 | /// Parses raw data. 49 | fn parse(data: &'a [u8], kind: u16) -> Option; 50 | } 51 | 52 | /// A list of lookup subtables. 53 | #[derive(Clone, Copy)] 54 | pub struct LookupSubtables<'a> { 55 | kind: u16, 56 | data: &'a [u8], 57 | offsets: LazyArray16<'a, Offset16>, 58 | } 59 | 60 | impl core::fmt::Debug for LookupSubtables<'_> { 61 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 62 | write!(f, "LookupSubtables {{ ... }}") 63 | } 64 | } 65 | 66 | impl<'a> LookupSubtables<'a> { 67 | /// Returns a number of items in the LookupSubtables. 68 | #[inline] 69 | pub fn len(&self) -> u16 { 70 | self.offsets.len() 71 | } 72 | 73 | /// Checks if there are any items. 74 | pub fn is_empty(&self) -> bool { 75 | self.offsets.is_empty() 76 | } 77 | 78 | /// Parses a subtable at index. 79 | /// 80 | /// Accepts either 81 | /// [`PositioningSubtable`](crate::gpos::PositioningSubtable) 82 | /// or [`SubstitutionSubtable`](crate::gsub::SubstitutionSubtable). 83 | /// 84 | /// Technically, we can enforce it at compile time, but it makes code too convoluted. 85 | pub fn get>(&self, index: u16) -> Option { 86 | let offset = self.offsets.get(index)?.to_usize(); 87 | let data = self.data.get(offset..)?; 88 | T::parse(data, self.kind) 89 | } 90 | 91 | /// Creates an iterator over subtables. 92 | /// 93 | /// We cannot use `IntoIterator` here, because we have to use user-provided base type. 94 | #[allow(clippy::should_implement_trait)] 95 | pub fn into_iter>(self) -> LookupSubtablesIter<'a, T> { 96 | LookupSubtablesIter { 97 | data: self, 98 | index: 0, 99 | data_type: core::marker::PhantomData, 100 | } 101 | } 102 | } 103 | 104 | /// An iterator over lookup subtables. 105 | #[allow(missing_debug_implementations)] 106 | pub struct LookupSubtablesIter<'a, T: LookupSubtable<'a>> { 107 | data: LookupSubtables<'a>, 108 | index: u16, 109 | data_type: core::marker::PhantomData, 110 | } 111 | 112 | impl<'a, T: LookupSubtable<'a>> Iterator for LookupSubtablesIter<'a, T> { 113 | type Item = T; 114 | 115 | fn next(&mut self) -> Option { 116 | if self.index < self.data.len() { 117 | self.index += 1; 118 | self.data.get(self.index - 1) 119 | } else { 120 | None 121 | } 122 | } 123 | } 124 | 125 | /// Lookup table flags. 126 | #[allow(missing_docs)] 127 | #[derive(Clone, Copy, Debug)] 128 | pub struct LookupFlags(pub u16); 129 | 130 | #[rustfmt::skip] 131 | #[allow(missing_docs)] 132 | impl LookupFlags { 133 | #[inline] pub fn right_to_left(self) -> bool { self.0 & 0x0001 != 0 } 134 | #[inline] pub fn ignore_base_glyphs(self) -> bool { self.0 & 0x0002 != 0 } 135 | #[inline] pub fn ignore_ligatures(self) -> bool { self.0 & 0x0004 != 0 } 136 | #[inline] pub fn ignore_marks(self) -> bool { self.0 & 0x0008 != 0 } 137 | #[inline] pub fn ignore_flags(self) -> bool { self.0 & 0x000E != 0 } 138 | #[inline] pub fn use_mark_filtering_set(self) -> bool { self.0 & 0x0010 != 0 } 139 | #[inline] pub fn mark_attachment_type(self) -> u8 { ((self.0 & 0xFF00) >> 8) as u8 } 140 | } 141 | 142 | impl FromData for LookupFlags { 143 | const SIZE: usize = 2; 144 | 145 | #[inline] 146 | fn parse(data: &[u8]) -> Option { 147 | u16::parse(data).map(Self) 148 | } 149 | } 150 | 151 | pub(crate) fn parse_extension_lookup<'a, T: 'a>( 152 | data: &'a [u8], 153 | parse: impl FnOnce(&'a [u8], u16) -> Option, 154 | ) -> Option { 155 | let mut s = Stream::new(data); 156 | let format = s.read::()?; 157 | match format { 158 | 1 => { 159 | let kind = s.read::()?; 160 | let offset = s.read::()?.to_usize(); 161 | parse(data.get(offset..)?, kind) 162 | } 163 | _ => None, 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/ggg/mod.rs: -------------------------------------------------------------------------------- 1 | //! Common data types used by GDEF/GPOS/GSUB tables. 2 | //! 3 | //! 4 | 5 | // A heavily modified port of https://github.com/harfbuzz/rustybuzz implementation 6 | // originally written by https://github.com/laurmaedje 7 | 8 | use crate::parser::{FromData, FromSlice, LazyArray16, Stream}; 9 | use crate::GlyphId; 10 | 11 | mod chained_context; 12 | mod context; 13 | #[cfg(feature = "variable-fonts")] 14 | mod feature_variations; 15 | mod layout_table; 16 | mod lookup; 17 | 18 | pub use chained_context::*; 19 | pub use context::*; 20 | #[cfg(feature = "variable-fonts")] 21 | pub use feature_variations::*; 22 | pub use layout_table::*; 23 | pub use lookup::*; 24 | 25 | /// A record that describes a range of glyph IDs. 26 | #[derive(Clone, Copy, Debug)] 27 | pub struct RangeRecord { 28 | /// First glyph ID in the range 29 | pub start: GlyphId, 30 | /// Last glyph ID in the range 31 | pub end: GlyphId, 32 | /// Coverage Index of first glyph ID in range. 33 | pub value: u16, 34 | } 35 | 36 | impl LazyArray16<'_, RangeRecord> { 37 | /// Returns a [`RangeRecord`] for a glyph. 38 | pub fn range(&self, glyph: GlyphId) -> Option { 39 | self.binary_search_by(|record| { 40 | if glyph < record.start { 41 | core::cmp::Ordering::Greater 42 | } else if glyph <= record.end { 43 | core::cmp::Ordering::Equal 44 | } else { 45 | core::cmp::Ordering::Less 46 | } 47 | }) 48 | .map(|p| p.1) 49 | } 50 | } 51 | 52 | impl FromData for RangeRecord { 53 | const SIZE: usize = 6; 54 | 55 | #[inline] 56 | fn parse(data: &[u8]) -> Option { 57 | let mut s = Stream::new(data); 58 | Some(RangeRecord { 59 | start: s.read::()?, 60 | end: s.read::()?, 61 | value: s.read::()?, 62 | }) 63 | } 64 | } 65 | 66 | /// A [Coverage Table]( 67 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table). 68 | #[allow(missing_docs)] 69 | #[derive(Clone, Copy, Debug)] 70 | pub enum Coverage<'a> { 71 | Format1 { 72 | /// Array of glyph IDs. Sorted. 73 | glyphs: LazyArray16<'a, GlyphId>, 74 | }, 75 | Format2 { 76 | /// Array of glyph ranges. Ordered by `RangeRecord.start`. 77 | records: LazyArray16<'a, RangeRecord>, 78 | }, 79 | } 80 | 81 | impl<'a> FromSlice<'a> for Coverage<'a> { 82 | fn parse(data: &'a [u8]) -> Option { 83 | let mut s = Stream::new(data); 84 | match s.read::()? { 85 | 1 => { 86 | let count = s.read::()?; 87 | let glyphs = s.read_array16(count)?; 88 | Some(Self::Format1 { glyphs }) 89 | } 90 | 2 => { 91 | let count = s.read::()?; 92 | let records = s.read_array16(count)?; 93 | Some(Self::Format2 { records }) 94 | } 95 | _ => None, 96 | } 97 | } 98 | } 99 | 100 | impl<'a> Coverage<'a> { 101 | /// Checks that glyph is present. 102 | pub fn contains(&self, glyph: GlyphId) -> bool { 103 | self.get(glyph).is_some() 104 | } 105 | 106 | /// Returns the coverage index of the glyph or `None` if it is not covered. 107 | pub fn get(&self, glyph: GlyphId) -> Option { 108 | match self { 109 | Self::Format1 { glyphs } => glyphs.binary_search(&glyph).map(|p| p.0), 110 | Self::Format2 { records } => { 111 | let record = records.range(glyph)?; 112 | let offset = glyph.0 - record.start.0; 113 | record.value.checked_add(offset) 114 | } 115 | } 116 | } 117 | } 118 | 119 | /// A value of [Class Definition Table]( 120 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table). 121 | pub type Class = u16; 122 | 123 | /// A [Class Definition Table]( 124 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table). 125 | #[allow(missing_docs)] 126 | #[derive(Clone, Copy, Debug)] 127 | pub enum ClassDefinition<'a> { 128 | Format1 { 129 | start: GlyphId, 130 | classes: LazyArray16<'a, Class>, 131 | }, 132 | Format2 { 133 | records: LazyArray16<'a, RangeRecord>, 134 | }, 135 | Empty, 136 | } 137 | 138 | impl<'a> ClassDefinition<'a> { 139 | #[inline] 140 | pub(crate) fn parse(data: &'a [u8]) -> Option { 141 | let mut s = Stream::new(data); 142 | match s.read::()? { 143 | 1 => { 144 | let start = s.read::()?; 145 | let count = s.read::()?; 146 | let classes = s.read_array16(count)?; 147 | Some(Self::Format1 { start, classes }) 148 | } 149 | 2 => { 150 | let count = s.read::()?; 151 | let records = s.read_array16(count)?; 152 | Some(Self::Format2 { records }) 153 | } 154 | _ => None, 155 | } 156 | } 157 | 158 | /// Returns the glyph class of the glyph (zero if it is not defined). 159 | pub fn get(&self, glyph: GlyphId) -> Class { 160 | match self { 161 | Self::Format1 { start, classes } => glyph 162 | .0 163 | .checked_sub(start.0) 164 | .and_then(|index| classes.get(index)), 165 | Self::Format2 { records } => records.range(glyph).map(|record| record.value), 166 | Self::Empty => Some(0), 167 | } 168 | .unwrap_or(0) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/tables/ankr.rs: -------------------------------------------------------------------------------- 1 | //! An [Anchor Point Table]( 2 | //! https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6ankr.html) implementation. 3 | 4 | use core::num::NonZeroU16; 5 | 6 | use crate::aat; 7 | use crate::parser::{FromData, LazyArray32, Offset, Offset32, Stream}; 8 | use crate::GlyphId; 9 | 10 | /// An anchor point. 11 | #[allow(missing_docs)] 12 | #[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] 13 | pub struct Point { 14 | pub x: i16, 15 | pub y: i16, 16 | } 17 | 18 | impl FromData for Point { 19 | const SIZE: usize = 4; 20 | 21 | #[inline] 22 | fn parse(data: &[u8]) -> Option { 23 | let mut s = Stream::new(data); 24 | Some(Point { 25 | x: s.read::()?, 26 | y: s.read::()?, 27 | }) 28 | } 29 | } 30 | 31 | /// An [Anchor Point Table]( 32 | /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6ankr.html). 33 | #[derive(Clone)] 34 | pub struct Table<'a> { 35 | lookup: aat::Lookup<'a>, 36 | // Ideally, Glyphs Data can be represented as an array, 37 | // but Apple's spec doesn't specify that Glyphs Data members have padding or not. 38 | // Meaning we cannot simply iterate over them. 39 | glyphs_data: &'a [u8], 40 | } 41 | 42 | impl core::fmt::Debug for Table<'_> { 43 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 44 | write!(f, "Table {{ ... }}") 45 | } 46 | } 47 | 48 | impl<'a> Table<'a> { 49 | /// Parses a table from raw data. 50 | /// 51 | /// `number_of_glyphs` is from the `maxp` table. 52 | pub fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option { 53 | let mut s = Stream::new(data); 54 | 55 | let version = s.read::()?; 56 | if version != 0 { 57 | return None; 58 | } 59 | 60 | s.skip::(); // reserved 61 | 62 | // TODO: we should probably check that offset is larger than the header size (8) 63 | let lookup_table = s.read_at_offset32(data)?; 64 | let glyphs_data = s.read_at_offset32(data)?; 65 | 66 | Some(Table { 67 | lookup: aat::Lookup::parse(number_of_glyphs, lookup_table)?, 68 | glyphs_data, 69 | }) 70 | } 71 | 72 | /// Returns a list of anchor points for the specified glyph. 73 | pub fn points(&self, glyph_id: GlyphId) -> Option> { 74 | let offset = self.lookup.value(glyph_id)?; 75 | 76 | let mut s = Stream::new_at(self.glyphs_data, usize::from(offset))?; 77 | let number_of_points = s.read::()?; 78 | s.read_array32::(number_of_points) 79 | } 80 | } 81 | 82 | trait StreamExt<'a> { 83 | fn read_at_offset32(&mut self, data: &'a [u8]) -> Option<&'a [u8]>; 84 | } 85 | 86 | impl<'a> StreamExt<'a> for Stream<'a> { 87 | fn read_at_offset32(&mut self, data: &'a [u8]) -> Option<&'a [u8]> { 88 | let offset = self.read::()?.to_usize(); 89 | data.get(offset..) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/tables/avar.rs: -------------------------------------------------------------------------------- 1 | //! An [Axis Variations Table]( 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/avar) implementation. 3 | 4 | use core::convert::TryFrom; 5 | 6 | use crate::parser::{FromData, LazyArray16, Stream}; 7 | use crate::NormalizedCoordinate; 8 | 9 | /// An axis value map. 10 | #[derive(Clone, Copy, Debug)] 11 | pub struct AxisValueMap { 12 | /// A normalized coordinate value obtained using default normalization. 13 | pub from_coordinate: i16, 14 | /// The modified, normalized coordinate value. 15 | pub to_coordinate: i16, 16 | } 17 | 18 | impl FromData for AxisValueMap { 19 | const SIZE: usize = 4; 20 | 21 | #[inline] 22 | fn parse(data: &[u8]) -> Option { 23 | let mut s = Stream::new(data); 24 | Some(AxisValueMap { 25 | from_coordinate: s.read::()?, 26 | to_coordinate: s.read::()?, 27 | }) 28 | } 29 | } 30 | 31 | /// A list of segment maps. 32 | /// 33 | /// Can be empty. 34 | /// 35 | /// The internal data layout is not designed for random access, 36 | /// therefore we're not providing the `get()` method and only an iterator. 37 | #[derive(Clone, Copy)] 38 | pub struct SegmentMaps<'a> { 39 | count: u16, 40 | data: &'a [u8], 41 | } 42 | 43 | impl<'a> SegmentMaps<'a> { 44 | /// Returns the number of segments. 45 | pub fn len(&self) -> u16 { 46 | self.count 47 | } 48 | 49 | /// Checks if there are any segments. 50 | pub fn is_empty(&self) -> bool { 51 | self.count == 0 52 | } 53 | } 54 | 55 | impl core::fmt::Debug for SegmentMaps<'_> { 56 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 57 | write!(f, "SegmentMaps {{ ... }}") 58 | } 59 | } 60 | 61 | impl<'a> IntoIterator for SegmentMaps<'a> { 62 | type Item = LazyArray16<'a, AxisValueMap>; 63 | type IntoIter = SegmentMapsIter<'a>; 64 | 65 | #[inline] 66 | fn into_iter(self) -> Self::IntoIter { 67 | SegmentMapsIter { 68 | stream: Stream::new(self.data), 69 | } 70 | } 71 | } 72 | 73 | /// An iterator over maps. 74 | #[allow(missing_debug_implementations)] 75 | pub struct SegmentMapsIter<'a> { 76 | stream: Stream<'a>, 77 | } 78 | 79 | impl<'a> Iterator for SegmentMapsIter<'a> { 80 | type Item = LazyArray16<'a, AxisValueMap>; 81 | 82 | fn next(&mut self) -> Option { 83 | let count = self.stream.read::()?; 84 | self.stream.read_array16::(count) 85 | } 86 | } 87 | 88 | /// An [Axis Variations Table]( 89 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/avar). 90 | #[derive(Clone, Copy, Debug)] 91 | pub struct Table<'a> { 92 | /// The segment maps array — one segment map for each axis 93 | /// in the order of axes specified in the `fvar` table. 94 | pub segment_maps: SegmentMaps<'a>, 95 | } 96 | 97 | impl<'a> Table<'a> { 98 | /// Parses a table from raw data. 99 | pub fn parse(data: &'a [u8]) -> Option { 100 | let mut s = Stream::new(data); 101 | 102 | let version = s.read::()?; 103 | if version != 0x00010000 { 104 | return None; 105 | } 106 | 107 | s.skip::(); // reserved 108 | Some(Self { 109 | segment_maps: SegmentMaps { 110 | // TODO: check that `axisCount` is the same as in `fvar`? 111 | count: s.read::()?, 112 | data: s.tail()?, 113 | }, 114 | }) 115 | } 116 | 117 | /// Maps a single coordinate 118 | pub fn map_coordinate( 119 | &self, 120 | coordinates: &mut [NormalizedCoordinate], 121 | coordinate_index: usize, 122 | ) -> Option<()> { 123 | if usize::from(self.segment_maps.count) != coordinates.len() { 124 | return None; 125 | } 126 | 127 | if let Some((map, coord)) = self 128 | .segment_maps 129 | .into_iter() 130 | .zip(coordinates) 131 | .nth(coordinate_index) 132 | { 133 | *coord = NormalizedCoordinate::from(map_value(&map, coord.0)?); 134 | } 135 | 136 | Some(()) 137 | } 138 | } 139 | 140 | fn map_value(map: &LazyArray16, value: i16) -> Option { 141 | // This code is based on harfbuzz implementation. 142 | 143 | if map.is_empty() { 144 | return Some(value); 145 | } else if map.len() == 1 { 146 | let record = map.get(0)?; 147 | return Some(value - record.from_coordinate + record.to_coordinate); 148 | } 149 | 150 | let record_0 = map.get(0)?; 151 | if value <= record_0.from_coordinate { 152 | return Some(value - record_0.from_coordinate + record_0.to_coordinate); 153 | } 154 | 155 | let mut i = 1; 156 | while i < map.len() && value > map.get(i)?.from_coordinate { 157 | i += 1; 158 | } 159 | 160 | if i == map.len() { 161 | i -= 1; 162 | } 163 | 164 | let record_curr = map.get(i)?; 165 | let curr_from = record_curr.from_coordinate; 166 | let curr_to = record_curr.to_coordinate; 167 | if value >= curr_from { 168 | return Some(value - curr_from + curr_to); 169 | } 170 | 171 | let record_prev = map.get(i - 1)?; 172 | let prev_from = record_prev.from_coordinate; 173 | let prev_to = record_prev.to_coordinate; 174 | if prev_from == curr_from { 175 | return Some(prev_to); 176 | } 177 | 178 | let curr_from = i32::from(curr_from); 179 | let curr_to = i32::from(curr_to); 180 | let prev_from = i32::from(prev_from); 181 | let prev_to = i32::from(prev_to); 182 | 183 | let denom = curr_from - prev_from; 184 | let k = (curr_to - prev_to) * (i32::from(value) - prev_from) + denom / 2; 185 | let value = prev_to + k / denom; 186 | i16::try_from(value).ok() 187 | } 188 | -------------------------------------------------------------------------------- /src/tables/cbdt.rs: -------------------------------------------------------------------------------- 1 | //! A [Color Bitmap Data Table]( 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/cbdt) implementation. 3 | 4 | use crate::cblc::{self, BitmapDataFormat, Metrics, MetricsFormat}; 5 | use crate::parser::{NumFrom, Stream}; 6 | use crate::{GlyphId, RasterGlyphImage, RasterImageFormat}; 7 | 8 | /// A [Color Bitmap Data Table]( 9 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/cbdt). 10 | /// 11 | /// EBDT and bdat also share the same structure, so this is re-used for them. 12 | #[derive(Clone, Copy)] 13 | pub struct Table<'a> { 14 | locations: cblc::Table<'a>, 15 | data: &'a [u8], 16 | } 17 | 18 | impl<'a> Table<'a> { 19 | /// Parses a table from raw data. 20 | pub fn parse(locations: cblc::Table<'a>, data: &'a [u8]) -> Option { 21 | Some(Self { locations, data }) 22 | } 23 | 24 | /// Returns a raster image for the glyph. 25 | pub fn get(&self, glyph_id: GlyphId, pixels_per_em: u16) -> Option> { 26 | let location = self.locations.get(glyph_id, pixels_per_em)?; 27 | let mut s = Stream::new_at(self.data, location.offset)?; 28 | let metrics = match location.format.metrics { 29 | MetricsFormat::Small => { 30 | let height = s.read::()?; 31 | let width = s.read::()?; 32 | let bearing_x = s.read::()?; 33 | let bearing_y = s.read::()?; 34 | s.skip::(); // advance 35 | Metrics { 36 | x: bearing_x, 37 | y: bearing_y, 38 | width, 39 | height, 40 | } 41 | } 42 | MetricsFormat::Big => { 43 | let height = s.read::()?; 44 | let width = s.read::()?; 45 | let hor_bearing_x = s.read::()?; 46 | let hor_bearing_y = s.read::()?; 47 | s.skip::(); // hor_advance 48 | s.skip::(); // ver_bearing_x 49 | s.skip::(); // ver_bearing_y 50 | s.skip::(); // ver_advance 51 | Metrics { 52 | x: hor_bearing_x, 53 | y: hor_bearing_y, 54 | width, 55 | height, 56 | } 57 | } 58 | MetricsFormat::Shared => location.metrics, 59 | }; 60 | match location.format.data { 61 | BitmapDataFormat::ByteAligned { bit_depth } => { 62 | let row_len = (u32::from(metrics.width) * u32::from(bit_depth) + 7) / 8; 63 | let data_len = row_len * u32::from(metrics.height); 64 | let data = s.read_bytes(usize::num_from(data_len))?; 65 | Some(RasterGlyphImage { 66 | x: i16::from(metrics.x), 67 | // `y` in CBDT is a bottom bound, not top one. 68 | y: i16::from(metrics.y) - i16::from(metrics.height), 69 | width: u16::from(metrics.width), 70 | height: u16::from(metrics.height), 71 | pixels_per_em: location.ppem, 72 | format: match bit_depth { 73 | 1 => RasterImageFormat::BitmapMono, 74 | 2 => RasterImageFormat::BitmapGray2, 75 | 4 => RasterImageFormat::BitmapGray4, 76 | 8 => RasterImageFormat::BitmapGray8, 77 | 32 => RasterImageFormat::BitmapPremulBgra32, 78 | _ => return None, 79 | }, 80 | data, 81 | }) 82 | } 83 | BitmapDataFormat::BitAligned { bit_depth } => { 84 | let data_len = { 85 | let w = u32::from(metrics.width); 86 | let h = u32::from(metrics.height); 87 | let d = u32::from(bit_depth); 88 | (w * h * d + 7) / 8 89 | }; 90 | 91 | let data = s.read_bytes(usize::num_from(data_len))?; 92 | Some(RasterGlyphImage { 93 | x: i16::from(metrics.x), 94 | // `y` in CBDT is a bottom bound, not top one. 95 | y: i16::from(metrics.y) - i16::from(metrics.height), 96 | width: u16::from(metrics.width), 97 | height: u16::from(metrics.height), 98 | pixels_per_em: location.ppem, 99 | format: match bit_depth { 100 | 1 => RasterImageFormat::BitmapMonoPacked, 101 | 2 => RasterImageFormat::BitmapGray2Packed, 102 | 4 => RasterImageFormat::BitmapGray4Packed, 103 | 8 => RasterImageFormat::BitmapGray8, 104 | 32 => RasterImageFormat::BitmapPremulBgra32, 105 | _ => return None, 106 | }, 107 | data, 108 | }) 109 | } 110 | BitmapDataFormat::PNG => { 111 | let data_len = s.read::()?; 112 | let data = s.read_bytes(usize::num_from(data_len))?; 113 | Some(RasterGlyphImage { 114 | x: i16::from(metrics.x), 115 | // `y` in CBDT is a bottom bound, not top one. 116 | y: i16::from(metrics.y) - i16::from(metrics.height), 117 | width: u16::from(metrics.width), 118 | height: u16::from(metrics.height), 119 | pixels_per_em: location.ppem, 120 | format: RasterImageFormat::PNG, 121 | data, 122 | }) 123 | } 124 | } 125 | } 126 | } 127 | 128 | impl core::fmt::Debug for Table<'_> { 129 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 130 | write!(f, "Table {{ ... }}") 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/tables/cff/argstack.rs: -------------------------------------------------------------------------------- 1 | use super::CFFError; 2 | 3 | pub struct ArgumentsStack<'a> { 4 | pub data: &'a mut [f32], 5 | pub len: usize, 6 | pub max_len: usize, 7 | } 8 | 9 | impl<'a> ArgumentsStack<'a> { 10 | #[inline] 11 | pub fn len(&self) -> usize { 12 | self.len 13 | } 14 | 15 | #[inline] 16 | pub fn is_empty(&self) -> bool { 17 | self.len == 0 18 | } 19 | 20 | #[inline] 21 | pub fn push(&mut self, n: f32) -> Result<(), CFFError> { 22 | if self.len == self.max_len { 23 | Err(CFFError::ArgumentsStackLimitReached) 24 | } else { 25 | self.data[self.len] = n; 26 | self.len += 1; 27 | Ok(()) 28 | } 29 | } 30 | 31 | #[inline] 32 | pub fn at(&self, index: usize) -> f32 { 33 | self.data[index] 34 | } 35 | 36 | #[inline] 37 | pub fn pop(&mut self) -> f32 { 38 | debug_assert!(!self.is_empty()); 39 | self.len -= 1; 40 | self.data[self.len] 41 | } 42 | 43 | #[inline] 44 | pub fn reverse(&mut self) { 45 | if self.is_empty() { 46 | return; 47 | } 48 | 49 | // Reverse only the actual data and not the whole stack. 50 | let (first, _) = self.data.split_at_mut(self.len); 51 | first.reverse(); 52 | } 53 | 54 | #[inline] 55 | pub fn clear(&mut self) { 56 | self.len = 0; 57 | } 58 | } 59 | 60 | impl core::fmt::Debug for ArgumentsStack<'_> { 61 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 62 | f.debug_list().entries(&self.data[..self.len]).finish() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/tables/cff/mod.rs: -------------------------------------------------------------------------------- 1 | mod argstack; 2 | pub mod cff1; 3 | #[cfg(feature = "variable-fonts")] 4 | pub mod cff2; 5 | mod charset; 6 | mod charstring; 7 | mod dict; 8 | mod encoding; 9 | mod index; 10 | #[cfg(feature = "glyph-names")] 11 | mod std_names; 12 | 13 | use core::convert::TryFrom; 14 | 15 | use crate::parser::{FromData, TryNumFrom}; 16 | use crate::{OutlineBuilder, RectF}; 17 | 18 | /// A list of errors that can occur during a CFF glyph outlining. 19 | #[allow(missing_docs)] 20 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 21 | pub enum CFFError { 22 | NoGlyph, 23 | ReadOutOfBounds, 24 | ZeroBBox, 25 | InvalidOperator, 26 | UnsupportedOperator, 27 | MissingEndChar, 28 | DataAfterEndChar, 29 | NestingLimitReached, 30 | ArgumentsStackLimitReached, 31 | InvalidArgumentsStackLength, 32 | BboxOverflow, 33 | MissingMoveTo, 34 | InvalidSubroutineIndex, 35 | NoLocalSubroutines, 36 | InvalidSeacCode, 37 | #[cfg(feature = "variable-fonts")] 38 | InvalidItemVariationDataIndex, 39 | #[cfg(feature = "variable-fonts")] 40 | InvalidNumberOfBlendOperands, 41 | #[cfg(feature = "variable-fonts")] 42 | BlendRegionsLimitReached, 43 | } 44 | 45 | pub(crate) struct Builder<'a> { 46 | builder: &'a mut dyn OutlineBuilder, 47 | bbox: RectF, 48 | } 49 | 50 | impl<'a> Builder<'a> { 51 | #[inline] 52 | fn move_to(&mut self, x: f32, y: f32) { 53 | self.bbox.extend_by(x, y); 54 | self.builder.move_to(x, y); 55 | } 56 | 57 | #[inline] 58 | fn line_to(&mut self, x: f32, y: f32) { 59 | self.bbox.extend_by(x, y); 60 | self.builder.line_to(x, y); 61 | } 62 | 63 | #[inline] 64 | fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { 65 | self.bbox.extend_by(x1, y1); 66 | self.bbox.extend_by(x2, y2); 67 | self.bbox.extend_by(x, y); 68 | self.builder.curve_to(x1, y1, x2, y2, x, y); 69 | } 70 | 71 | #[inline] 72 | fn close(&mut self) { 73 | self.builder.close(); 74 | } 75 | } 76 | 77 | /// A type-safe wrapper for string ID. 78 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Debug)] 79 | pub struct StringId(u16); 80 | 81 | impl FromData for StringId { 82 | const SIZE: usize = 2; 83 | 84 | #[inline] 85 | fn parse(data: &[u8]) -> Option { 86 | u16::parse(data).map(StringId) 87 | } 88 | } 89 | 90 | pub trait IsEven { 91 | fn is_even(&self) -> bool; 92 | fn is_odd(&self) -> bool; 93 | } 94 | 95 | impl IsEven for usize { 96 | #[inline] 97 | fn is_even(&self) -> bool { 98 | (*self) & 1 == 0 99 | } 100 | 101 | #[inline] 102 | fn is_odd(&self) -> bool { 103 | !self.is_even() 104 | } 105 | } 106 | 107 | #[cfg(feature = "std")] 108 | #[inline] 109 | pub fn f32_abs(n: f32) -> f32 { 110 | n.abs() 111 | } 112 | 113 | #[cfg(not(feature = "std"))] 114 | #[inline] 115 | pub fn f32_abs(n: f32) -> f32 { 116 | if n.is_sign_negative() { 117 | -n 118 | } else { 119 | n 120 | } 121 | } 122 | 123 | #[inline] 124 | pub fn conv_subroutine_index(index: f32, bias: u16) -> Result { 125 | conv_subroutine_index_impl(index, bias).ok_or(CFFError::InvalidSubroutineIndex) 126 | } 127 | 128 | #[inline] 129 | fn conv_subroutine_index_impl(index: f32, bias: u16) -> Option { 130 | let index = i32::try_num_from(index)?; 131 | let bias = i32::from(bias); 132 | 133 | let index = index.checked_add(bias)?; 134 | u32::try_from(index).ok() 135 | } 136 | 137 | // Adobe Technical Note #5176, Chapter 16 "Local / Global Subrs INDEXes" 138 | #[inline] 139 | pub fn calc_subroutine_bias(len: u32) -> u16 { 140 | if len < 1240 { 141 | 107 142 | } else if len < 33900 { 143 | 1131 144 | } else { 145 | 32768 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/tables/cmap/format0.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::{NumFrom, Stream}; 2 | use crate::GlyphId; 3 | 4 | /// A [format 0](https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-0-byte-encoding-table) 5 | /// subtable. 6 | #[derive(Clone, Copy, Debug)] 7 | pub struct Subtable0<'a> { 8 | /// Just a list of 256 8bit glyph IDs. 9 | pub glyph_ids: &'a [u8], 10 | } 11 | 12 | impl<'a> Subtable0<'a> { 13 | /// Parses a subtable from raw data. 14 | pub fn parse(data: &'a [u8]) -> Option { 15 | let mut s = Stream::new(data); 16 | s.skip::(); // format 17 | s.skip::(); // length 18 | s.skip::(); // language 19 | let glyph_ids = s.read_bytes(256)?; 20 | Some(Self { glyph_ids }) 21 | } 22 | 23 | /// Returns a glyph index for a code point. 24 | pub fn glyph_index(&self, code_point: u32) -> Option { 25 | let glyph_id = *self.glyph_ids.get(usize::num_from(code_point))?; 26 | // Make sure that the glyph is not zero, the array always has 256 ids, 27 | // but some codepoints may be mapped to zero. 28 | if glyph_id != 0 { 29 | Some(GlyphId(u16::from(glyph_id))) 30 | } else { 31 | None 32 | } 33 | } 34 | 35 | /// Calls `f` for each codepoint defined in this table. 36 | pub fn codepoints(&self, mut f: impl FnMut(u32)) { 37 | for (i, glyph_id) in self.glyph_ids.iter().enumerate() { 38 | // In contrast to every other format, here we take a look at the glyph 39 | // id and check whether it is zero because otherwise this method would 40 | // always simply call `f` for `0..256` which would be kind of pointless 41 | // (this array always has length 256 even when the face has fewer glyphs). 42 | if *glyph_id != 0 { 43 | f(i as u32); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/tables/cmap/format10.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::{LazyArray32, Stream}; 2 | use crate::GlyphId; 3 | 4 | /// A [format 10](https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-10-trimmed-array) 5 | /// subtable. 6 | #[derive(Clone, Copy, Debug)] 7 | pub struct Subtable10<'a> { 8 | /// First character code covered. 9 | pub first_code_point: u32, 10 | /// Array of glyph indices for the character codes covered. 11 | pub glyphs: LazyArray32<'a, GlyphId>, 12 | } 13 | 14 | impl<'a> Subtable10<'a> { 15 | /// Parses a subtable from raw data. 16 | pub fn parse(data: &'a [u8]) -> Option { 17 | let mut s = Stream::new(data); 18 | s.skip::(); // format 19 | s.skip::(); // reserved 20 | s.skip::(); // length 21 | s.skip::(); // language 22 | let first_code_point = s.read::()?; 23 | let count = s.read::()?; 24 | let glyphs = s.read_array32::(count)?; 25 | Some(Self { 26 | first_code_point, 27 | glyphs, 28 | }) 29 | } 30 | 31 | /// Returns a glyph index for a code point. 32 | pub fn glyph_index(&self, code_point: u32) -> Option { 33 | let idx = code_point.checked_sub(self.first_code_point)?; 34 | self.glyphs.get(idx) 35 | } 36 | 37 | /// Calls `f` for each codepoint defined in this table. 38 | pub fn codepoints(&self, mut f: impl FnMut(u32)) { 39 | for i in 0..self.glyphs.len() { 40 | if let Some(code_point) = self.first_code_point.checked_add(i) { 41 | f(code_point); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/tables/cmap/format12.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | 3 | use crate::parser::{FromData, LazyArray32, Stream}; 4 | use crate::GlyphId; 5 | 6 | #[derive(Clone, Copy)] 7 | pub struct SequentialMapGroup { 8 | pub start_char_code: u32, 9 | pub end_char_code: u32, 10 | pub start_glyph_id: u32, 11 | } 12 | 13 | impl FromData for SequentialMapGroup { 14 | const SIZE: usize = 12; 15 | 16 | #[inline] 17 | fn parse(data: &[u8]) -> Option { 18 | let mut s = Stream::new(data); 19 | Some(SequentialMapGroup { 20 | start_char_code: s.read::()?, 21 | end_char_code: s.read::()?, 22 | start_glyph_id: s.read::()?, 23 | }) 24 | } 25 | } 26 | 27 | /// A [format 12](https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-12-segmented-coverage) 28 | /// subtable. 29 | #[derive(Clone, Copy)] 30 | pub struct Subtable12<'a> { 31 | groups: LazyArray32<'a, SequentialMapGroup>, 32 | } 33 | 34 | impl<'a> Subtable12<'a> { 35 | /// Parses a subtable from raw data. 36 | pub fn parse(data: &'a [u8]) -> Option { 37 | let mut s = Stream::new(data); 38 | s.skip::(); // format 39 | s.skip::(); // reserved 40 | s.skip::(); // length 41 | s.skip::(); // language 42 | let count = s.read::()?; 43 | let groups = s.read_array32::(count)?; 44 | Some(Self { groups }) 45 | } 46 | 47 | /// Returns a glyph index for a code point. 48 | pub fn glyph_index(&self, code_point: u32) -> Option { 49 | let (_, group) = self.groups.binary_search_by(|range| { 50 | use core::cmp::Ordering; 51 | 52 | if range.start_char_code > code_point { 53 | Ordering::Greater 54 | } else if range.end_char_code < code_point { 55 | Ordering::Less 56 | } else { 57 | Ordering::Equal 58 | } 59 | })?; 60 | 61 | let id = group 62 | .start_glyph_id 63 | .checked_add(code_point)? 64 | .checked_sub(group.start_char_code)?; 65 | u16::try_from(id).ok().map(GlyphId) 66 | } 67 | 68 | /// Calls `f` for each codepoint defined in this table. 69 | pub fn codepoints(&self, mut f: impl FnMut(u32)) { 70 | for group in self.groups { 71 | for code_point in group.start_char_code..=group.end_char_code { 72 | f(code_point); 73 | } 74 | } 75 | } 76 | } 77 | 78 | impl core::fmt::Debug for Subtable12<'_> { 79 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 80 | write!(f, "Subtable12 {{ ... }}") 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/tables/cmap/format13.rs: -------------------------------------------------------------------------------- 1 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-13-many-to-one-range-mappings 2 | 3 | use core::convert::TryFrom; 4 | 5 | use super::format12::SequentialMapGroup; 6 | use crate::parser::{LazyArray32, Stream}; 7 | use crate::GlyphId; 8 | 9 | /// A [format 13](https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-13-segmented-coverage) 10 | /// subtable. 11 | #[derive(Clone, Copy)] 12 | pub struct Subtable13<'a> { 13 | groups: LazyArray32<'a, SequentialMapGroup>, 14 | } 15 | 16 | impl<'a> Subtable13<'a> { 17 | /// Parses a subtable from raw data. 18 | pub fn parse(data: &'a [u8]) -> Option { 19 | let mut s = Stream::new(data); 20 | s.skip::(); // format 21 | s.skip::(); // reserved 22 | s.skip::(); // length 23 | s.skip::(); // language 24 | let count = s.read::()?; 25 | let groups = s.read_array32::(count)?; 26 | Some(Self { groups }) 27 | } 28 | 29 | /// Returns a glyph index for a code point. 30 | pub fn glyph_index(&self, code_point: u32) -> Option { 31 | for group in self.groups { 32 | let start_char_code = group.start_char_code; 33 | if code_point >= start_char_code && code_point <= group.end_char_code { 34 | return u16::try_from(group.start_glyph_id).ok().map(GlyphId); 35 | } 36 | } 37 | 38 | None 39 | } 40 | 41 | /// Calls `f` for each codepoint defined in this table. 42 | pub fn codepoints(&self, mut f: impl FnMut(u32)) { 43 | for group in self.groups { 44 | for code_point in group.start_char_code..=group.end_char_code { 45 | f(code_point); 46 | } 47 | } 48 | } 49 | } 50 | 51 | impl core::fmt::Debug for Subtable13<'_> { 52 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 53 | write!(f, "Subtable13 {{ ... }}") 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/tables/cmap/format14.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::{FromData, LazyArray32, Offset, Offset32, Stream, U24}; 2 | use crate::GlyphId; 3 | 4 | #[derive(Clone, Copy)] 5 | struct VariationSelectorRecord { 6 | var_selector: u32, 7 | default_uvs_offset: Option, 8 | non_default_uvs_offset: Option, 9 | } 10 | 11 | impl FromData for VariationSelectorRecord { 12 | const SIZE: usize = 11; 13 | 14 | #[inline] 15 | fn parse(data: &[u8]) -> Option { 16 | let mut s = Stream::new(data); 17 | Some(VariationSelectorRecord { 18 | var_selector: s.read::()?.0, 19 | default_uvs_offset: s.read::>()?, 20 | non_default_uvs_offset: s.read::>()?, 21 | }) 22 | } 23 | } 24 | 25 | #[derive(Clone, Copy)] 26 | struct UVSMappingRecord { 27 | unicode_value: u32, 28 | glyph_id: GlyphId, 29 | } 30 | 31 | impl FromData for UVSMappingRecord { 32 | const SIZE: usize = 5; 33 | 34 | #[inline] 35 | fn parse(data: &[u8]) -> Option { 36 | let mut s = Stream::new(data); 37 | Some(UVSMappingRecord { 38 | unicode_value: s.read::()?.0, 39 | glyph_id: s.read::()?, 40 | }) 41 | } 42 | } 43 | 44 | #[derive(Clone, Copy)] 45 | struct UnicodeRangeRecord { 46 | start_unicode_value: u32, 47 | additional_count: u8, 48 | } 49 | 50 | impl UnicodeRangeRecord { 51 | fn contains(&self, c: u32) -> bool { 52 | // Never overflows, since `start_unicode_value` is actually u24. 53 | let end = self.start_unicode_value + u32::from(self.additional_count); 54 | (self.start_unicode_value..=end).contains(&c) 55 | } 56 | } 57 | 58 | impl FromData for UnicodeRangeRecord { 59 | const SIZE: usize = 4; 60 | 61 | #[inline] 62 | fn parse(data: &[u8]) -> Option { 63 | let mut s = Stream::new(data); 64 | Some(UnicodeRangeRecord { 65 | start_unicode_value: s.read::()?.0, 66 | additional_count: s.read::()?, 67 | }) 68 | } 69 | } 70 | 71 | /// A result of a variation glyph mapping. 72 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 73 | pub enum GlyphVariationResult { 74 | /// Glyph was found in the variation encoding table. 75 | Found(GlyphId), 76 | /// Glyph should be looked in other, non-variation tables. 77 | /// 78 | /// Basically, you should use `Encoding::glyph_index` or `Face::glyph_index` 79 | /// in this case. 80 | UseDefault, 81 | } 82 | 83 | /// A [format 14](https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-14-unicode-variation-sequences) 84 | /// subtable. 85 | #[derive(Clone, Copy)] 86 | pub struct Subtable14<'a> { 87 | records: LazyArray32<'a, VariationSelectorRecord>, 88 | // The whole subtable data. 89 | data: &'a [u8], 90 | } 91 | 92 | impl<'a> Subtable14<'a> { 93 | /// Parses a subtable from raw data. 94 | pub fn parse(data: &'a [u8]) -> Option { 95 | let mut s = Stream::new(data); 96 | s.skip::(); // format 97 | s.skip::(); // length 98 | let count = s.read::()?; 99 | let records = s.read_array32::(count)?; 100 | Some(Self { records, data }) 101 | } 102 | 103 | /// Returns a glyph index for a code point. 104 | pub fn glyph_index(&self, code_point: u32, variation: u32) -> Option { 105 | let (_, record) = self 106 | .records 107 | .binary_search_by(|v| v.var_selector.cmp(&variation))?; 108 | 109 | if let Some(offset) = record.default_uvs_offset { 110 | let data = self.data.get(offset.to_usize()..)?; 111 | let mut s = Stream::new(data); 112 | let count = s.read::()?; 113 | let ranges = s.read_array32::(count)?; 114 | for range in ranges { 115 | if range.contains(code_point) { 116 | return Some(GlyphVariationResult::UseDefault); 117 | } 118 | } 119 | } 120 | 121 | if let Some(offset) = record.non_default_uvs_offset { 122 | let data = self.data.get(offset.to_usize()..)?; 123 | let mut s = Stream::new(data); 124 | let count = s.read::()?; 125 | let uvs_mappings = s.read_array32::(count)?; 126 | let (_, mapping) = 127 | uvs_mappings.binary_search_by(|v| v.unicode_value.cmp(&code_point))?; 128 | return Some(GlyphVariationResult::Found(mapping.glyph_id)); 129 | } 130 | 131 | None 132 | } 133 | } 134 | 135 | impl core::fmt::Debug for Subtable14<'_> { 136 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 137 | write!(f, "Subtable14 {{ ... }}") 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/tables/cmap/format4.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | 3 | use crate::parser::{LazyArray16, Stream}; 4 | use crate::GlyphId; 5 | 6 | /// A [format 4](https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-4-segment-mapping-to-delta-values) 7 | /// subtable. 8 | #[derive(Clone, Copy)] 9 | pub struct Subtable4<'a> { 10 | start_codes: LazyArray16<'a, u16>, 11 | end_codes: LazyArray16<'a, u16>, 12 | id_deltas: LazyArray16<'a, i16>, 13 | id_range_offsets: LazyArray16<'a, u16>, 14 | id_range_offset_pos: usize, 15 | // The whole subtable data. 16 | data: &'a [u8], 17 | } 18 | 19 | impl<'a> Subtable4<'a> { 20 | /// Parses a subtable from raw data. 21 | pub fn parse(data: &'a [u8]) -> Option { 22 | let mut s = Stream::new(data); 23 | s.advance(6); // format + length + language 24 | let seg_count_x2 = s.read::()?; 25 | if seg_count_x2 < 2 { 26 | return None; 27 | } 28 | 29 | let seg_count = seg_count_x2 / 2; 30 | s.advance(6); // searchRange + entrySelector + rangeShift 31 | 32 | let end_codes = s.read_array16::(seg_count)?; 33 | s.skip::(); // reservedPad 34 | let start_codes = s.read_array16::(seg_count)?; 35 | let id_deltas = s.read_array16::(seg_count)?; 36 | let id_range_offset_pos = s.offset(); 37 | let id_range_offsets = s.read_array16::(seg_count)?; 38 | 39 | Some(Self { 40 | start_codes, 41 | end_codes, 42 | id_deltas, 43 | id_range_offsets, 44 | id_range_offset_pos, 45 | data, 46 | }) 47 | } 48 | 49 | /// Returns a glyph index for a code point. 50 | /// 51 | /// Returns `None` when `code_point` is larger than `u16`. 52 | pub fn glyph_index(&self, code_point: u32) -> Option { 53 | // This subtable supports code points only in a u16 range. 54 | let code_point = u16::try_from(code_point).ok()?; 55 | 56 | // A custom binary search. 57 | let mut start = 0; 58 | let mut end = self.start_codes.len(); 59 | while end > start { 60 | let index = (start + end) / 2; 61 | let end_value = self.end_codes.get(index)?; 62 | if end_value >= code_point { 63 | let start_value = self.start_codes.get(index)?; 64 | if start_value > code_point { 65 | end = index; 66 | } else { 67 | let id_range_offset = self.id_range_offsets.get(index)?; 68 | let id_delta = self.id_deltas.get(index)?; 69 | if id_range_offset == 0 { 70 | return Some(GlyphId(code_point.wrapping_add(id_delta as u16))); 71 | } else if id_range_offset == 0xFFFF { 72 | // Some malformed fonts have 0xFFFF as the last offset, 73 | // which is invalid and should be ignored. 74 | return None; 75 | } 76 | 77 | let delta = (u32::from(code_point) - u32::from(start_value)) * 2; 78 | let delta = u16::try_from(delta).ok()?; 79 | 80 | let id_range_offset_pos = 81 | (self.id_range_offset_pos + usize::from(index) * 2) as u16; 82 | let pos = id_range_offset_pos.wrapping_add(delta); 83 | let pos = pos.wrapping_add(id_range_offset); 84 | 85 | let glyph_array_value: u16 = Stream::read_at(self.data, usize::from(pos))?; 86 | 87 | // 0 indicates missing glyph. 88 | if glyph_array_value == 0 { 89 | return None; 90 | } 91 | 92 | let glyph_id = (glyph_array_value as i16).wrapping_add(id_delta); 93 | return u16::try_from(glyph_id).ok().map(GlyphId); 94 | } 95 | } else { 96 | start = index + 1; 97 | } 98 | } 99 | 100 | None 101 | } 102 | 103 | /// Calls `f` for each codepoint defined in this table. 104 | pub fn codepoints(&self, mut f: impl FnMut(u32)) { 105 | for (start, end) in self.start_codes.into_iter().zip(self.end_codes) { 106 | // OxFFFF value is special and indicates codes end. 107 | if start == end && start == 0xFFFF { 108 | break; 109 | } 110 | 111 | for code_point in start..=end { 112 | f(u32::from(code_point)); 113 | } 114 | } 115 | } 116 | } 117 | 118 | impl core::fmt::Debug for Subtable4<'_> { 119 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 120 | write!(f, "Subtable4 {{ ... }}") 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/tables/cmap/format6.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | 3 | use crate::parser::{LazyArray16, Stream}; 4 | use crate::GlyphId; 5 | 6 | /// A [format 6](https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-6-trimmed-table-mapping) 7 | /// subtable. 8 | #[derive(Clone, Copy, Debug)] 9 | pub struct Subtable6<'a> { 10 | /// First character code of subrange. 11 | pub first_code_point: u16, 12 | /// Array of glyph indexes for character codes in the range. 13 | pub glyphs: LazyArray16<'a, GlyphId>, 14 | } 15 | 16 | impl<'a> Subtable6<'a> { 17 | /// Parses a subtable from raw data. 18 | pub fn parse(data: &'a [u8]) -> Option { 19 | let mut s = Stream::new(data); 20 | s.skip::(); // format 21 | s.skip::(); // length 22 | s.skip::(); // language 23 | let first_code_point = s.read::()?; 24 | let count = s.read::()?; 25 | let glyphs = s.read_array16::(count)?; 26 | Some(Self { 27 | first_code_point, 28 | glyphs, 29 | }) 30 | } 31 | 32 | /// Returns a glyph index for a code point. 33 | /// 34 | /// Returns `None` when `code_point` is larger than `u16`. 35 | pub fn glyph_index(&self, code_point: u32) -> Option { 36 | // This subtable supports code points only in a u16 range. 37 | let code_point = u16::try_from(code_point).ok()?; 38 | let idx = code_point.checked_sub(self.first_code_point)?; 39 | self.glyphs.get(idx) 40 | } 41 | 42 | /// Calls `f` for each codepoint defined in this table. 43 | pub fn codepoints(&self, mut f: impl FnMut(u32)) { 44 | for i in 0..self.glyphs.len() { 45 | if let Some(code_point) = self.first_code_point.checked_add(i) { 46 | f(u32::from(code_point)); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/tables/cpal.rs: -------------------------------------------------------------------------------- 1 | //! A [Color Palette Table]( 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/cpal) implementation. 3 | 4 | use core::num::NonZeroU16; 5 | 6 | use crate::parser::{FromData, LazyArray16, Offset, Offset32, Stream}; 7 | use crate::RgbaColor; 8 | 9 | /// A [Color Palette Table]( 10 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/cpal). 11 | #[derive(Clone, Copy, Debug)] 12 | pub struct Table<'a> { 13 | color_indices: LazyArray16<'a, u16>, 14 | colors: LazyArray16<'a, BgraColor>, 15 | } 16 | 17 | impl<'a> Table<'a> { 18 | /// Parses a table from raw data. 19 | pub fn parse(data: &'a [u8]) -> Option { 20 | let mut s = Stream::new(data); 21 | 22 | let version = s.read::()?; 23 | if version > 1 { 24 | return None; 25 | } 26 | 27 | s.skip::(); // number of palette entries 28 | 29 | let num_palettes = s.read::()?; 30 | if num_palettes == 0 { 31 | return None; // zero palettes is an error 32 | } 33 | 34 | let num_colors = s.read::()?; 35 | let color_records_offset = s.read::()?; 36 | let color_indices = s.read_array16::(num_palettes)?; 37 | 38 | let colors = Stream::new_at(data, color_records_offset.to_usize())? 39 | .read_array16::(num_colors)?; 40 | 41 | Some(Self { 42 | color_indices, 43 | colors, 44 | }) 45 | } 46 | 47 | /// Returns the number of palettes. 48 | pub fn palettes(&self) -> NonZeroU16 { 49 | // Already checked during parsing. 50 | NonZeroU16::new(self.color_indices.len()).unwrap() 51 | } 52 | 53 | /// Returns the color at the given index into the given palette. 54 | pub fn get(&self, palette_index: u16, palette_entry: u16) -> Option { 55 | let index = self 56 | .color_indices 57 | .get(palette_index)? 58 | .checked_add(palette_entry)?; 59 | self.colors.get(index).map(|c| c.to_rgba()) 60 | } 61 | } 62 | 63 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 64 | struct BgraColor { 65 | blue: u8, 66 | green: u8, 67 | red: u8, 68 | alpha: u8, 69 | } 70 | 71 | impl BgraColor { 72 | #[inline] 73 | fn to_rgba(self) -> RgbaColor { 74 | RgbaColor::new(self.red, self.green, self.blue, self.alpha) 75 | } 76 | } 77 | 78 | impl FromData for BgraColor { 79 | const SIZE: usize = 4; 80 | 81 | fn parse(data: &[u8]) -> Option { 82 | let mut s = Stream::new(data); 83 | Some(Self { 84 | blue: s.read::()?, 85 | green: s.read::()?, 86 | red: s.read::()?, 87 | alpha: s.read::()?, 88 | }) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/tables/feat.rs: -------------------------------------------------------------------------------- 1 | //! A [Feature Name Table]( 2 | //! https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6feat.html) implementation. 3 | 4 | use crate::parser::{FromData, LazyArray16, Offset, Offset32, Stream}; 5 | 6 | #[derive(Clone, Copy, Debug)] 7 | struct FeatureNameRecord { 8 | feature: u16, 9 | setting_table_records_count: u16, 10 | // Offset from the beginning of the table. 11 | setting_table_offset: Offset32, 12 | flags: u8, 13 | default_setting_index: u8, 14 | name_index: u16, 15 | } 16 | 17 | impl FromData for FeatureNameRecord { 18 | const SIZE: usize = 12; 19 | 20 | #[inline] 21 | fn parse(data: &[u8]) -> Option { 22 | let mut s = Stream::new(data); 23 | Some(FeatureNameRecord { 24 | feature: s.read::()?, 25 | setting_table_records_count: s.read::()?, 26 | setting_table_offset: s.read::()?, 27 | flags: s.read::()?, 28 | default_setting_index: s.read::()?, 29 | name_index: s.read::()?, 30 | }) 31 | } 32 | } 33 | 34 | /// A setting name. 35 | #[derive(Clone, Copy, Debug)] 36 | pub struct SettingName { 37 | /// The setting. 38 | pub setting: u16, 39 | /// The `name` table index for the feature's name in a 256..32768 range. 40 | pub name_index: u16, 41 | } 42 | 43 | impl FromData for SettingName { 44 | const SIZE: usize = 4; 45 | 46 | #[inline] 47 | fn parse(data: &[u8]) -> Option { 48 | let mut s = Stream::new(data); 49 | Some(SettingName { 50 | setting: s.read::()?, 51 | name_index: s.read::()?, 52 | }) 53 | } 54 | } 55 | 56 | /// A feature names. 57 | #[derive(Clone, Copy, Debug)] 58 | pub struct FeatureName<'a> { 59 | /// The feature's ID. 60 | pub feature: u16, 61 | /// The feature's setting names. 62 | pub setting_names: LazyArray16<'a, SettingName>, 63 | /// The index of the default setting in the `setting_names`. 64 | pub default_setting_index: u8, 65 | /// The feature's exclusive settings. If set, the feature settings are mutually exclusive. 66 | pub exclusive: bool, 67 | /// The `name` table index for the feature's name in a 256..32768 range. 68 | pub name_index: u16, 69 | } 70 | 71 | /// A list of feature names. 72 | #[derive(Clone, Copy)] 73 | pub struct FeatureNames<'a> { 74 | data: &'a [u8], 75 | records: LazyArray16<'a, FeatureNameRecord>, 76 | } 77 | 78 | impl<'a> FeatureNames<'a> { 79 | /// Returns a feature name at an index. 80 | pub fn get(&self, index: u16) -> Option> { 81 | let record = self.records.get(index)?; 82 | let data = self.data.get(record.setting_table_offset.to_usize()..)?; 83 | let mut s = Stream::new(data); 84 | let setting_names = s.read_array16::(record.setting_table_records_count)?; 85 | Some(FeatureName { 86 | feature: record.feature, 87 | setting_names, 88 | default_setting_index: if record.flags & 0x40 != 0 { 89 | record.default_setting_index 90 | } else { 91 | 0 92 | }, 93 | exclusive: record.flags & 0x80 != 0, 94 | name_index: record.name_index, 95 | }) 96 | } 97 | 98 | /// Finds a feature name by ID. 99 | pub fn find(&self, feature: u16) -> Option> { 100 | let index = self 101 | .records 102 | .binary_search_by(|name| name.feature.cmp(&feature)) 103 | .map(|(i, _)| i)?; 104 | self.get(index) 105 | } 106 | 107 | /// Returns the number of feature names. 108 | pub fn len(&self) -> u16 { 109 | self.records.len() 110 | } 111 | 112 | /// Checks if there are any feature names. 113 | pub fn is_empty(&self) -> bool { 114 | self.records.is_empty() 115 | } 116 | } 117 | 118 | impl<'a> core::fmt::Debug for FeatureNames<'a> { 119 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 120 | f.debug_list().entries(*self).finish() 121 | } 122 | } 123 | 124 | impl<'a> IntoIterator for FeatureNames<'a> { 125 | type Item = FeatureName<'a>; 126 | type IntoIter = FeatureNamesIter<'a>; 127 | 128 | #[inline] 129 | fn into_iter(self) -> Self::IntoIter { 130 | FeatureNamesIter { 131 | names: self, 132 | index: 0, 133 | } 134 | } 135 | } 136 | 137 | /// An iterator over [`FeatureNames`]. 138 | #[allow(missing_debug_implementations)] 139 | pub struct FeatureNamesIter<'a> { 140 | names: FeatureNames<'a>, 141 | index: u16, 142 | } 143 | 144 | impl<'a> Iterator for FeatureNamesIter<'a> { 145 | type Item = FeatureName<'a>; 146 | 147 | fn next(&mut self) -> Option { 148 | if self.index < self.names.len() { 149 | self.index += 1; 150 | self.names.get(self.index - 1) 151 | } else { 152 | None 153 | } 154 | } 155 | } 156 | 157 | /// A [Feature Name Table]( 158 | /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6feat.html). 159 | #[derive(Clone, Copy, Debug)] 160 | pub struct Table<'a> { 161 | /// A list of feature names. Sorted by `FeatureName.feature`. 162 | pub names: FeatureNames<'a>, 163 | } 164 | 165 | impl<'a> Table<'a> { 166 | /// Parses a table from raw data. 167 | pub fn parse(data: &'a [u8]) -> Option { 168 | let mut s = Stream::new(data); 169 | 170 | let version = s.read::()?; 171 | if version != 0x00010000 { 172 | return None; 173 | } 174 | 175 | let count = s.read::()?; 176 | s.advance_checked(6)?; // reserved 177 | let records = s.read_array16::(count)?; 178 | 179 | Some(Table { 180 | names: FeatureNames { data, records }, 181 | }) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/tables/fvar.rs: -------------------------------------------------------------------------------- 1 | //! A [Font Variations Table]( 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/fvar) implementation. 3 | 4 | use core::num::NonZeroU16; 5 | 6 | use crate::parser::{f32_bound, Fixed, FromData, LazyArray16, Offset, Offset16, Stream}; 7 | use crate::{NormalizedCoordinate, Tag}; 8 | 9 | /// A [variation axis](https://docs.microsoft.com/en-us/typography/opentype/spec/fvar#variationaxisrecord). 10 | #[repr(C)] 11 | #[allow(missing_docs)] 12 | #[derive(Clone, Copy, PartialEq, Debug)] 13 | pub struct VariationAxis { 14 | pub tag: Tag, 15 | pub min_value: f32, 16 | pub def_value: f32, 17 | pub max_value: f32, 18 | /// An axis name in the `name` table. 19 | pub name_id: u16, 20 | pub hidden: bool, 21 | } 22 | 23 | impl FromData for VariationAxis { 24 | const SIZE: usize = 20; 25 | 26 | fn parse(data: &[u8]) -> Option { 27 | let mut s = Stream::new(data); 28 | let tag = s.read::()?; 29 | let min_value = s.read::()?; 30 | let def_value = s.read::()?; 31 | let max_value = s.read::()?; 32 | let flags = s.read::()?; 33 | let name_id = s.read::()?; 34 | 35 | Some(VariationAxis { 36 | tag, 37 | min_value: def_value.0.min(min_value.0), 38 | def_value: def_value.0, 39 | max_value: def_value.0.max(max_value.0), 40 | name_id, 41 | hidden: (flags >> 3) & 1 == 1, 42 | }) 43 | } 44 | } 45 | 46 | impl VariationAxis { 47 | /// Returns a normalized variation coordinate for this axis. 48 | pub(crate) fn normalized_value(&self, mut v: f32) -> NormalizedCoordinate { 49 | // Based on 50 | // https://docs.microsoft.com/en-us/typography/opentype/spec/avar#overview 51 | 52 | v = f32_bound(self.min_value, v, self.max_value); 53 | if v == self.def_value { 54 | v = 0.0; 55 | } else if v < self.def_value { 56 | v = (v - self.def_value) / (self.def_value - self.min_value); 57 | } else { 58 | v = (v - self.def_value) / (self.max_value - self.def_value); 59 | } 60 | 61 | NormalizedCoordinate::from(v) 62 | } 63 | } 64 | 65 | /// A [Font Variations Table]( 66 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/fvar). 67 | #[derive(Clone, Copy, Debug)] 68 | pub struct Table<'a> { 69 | /// A list of variation axes. 70 | pub axes: LazyArray16<'a, VariationAxis>, 71 | } 72 | 73 | impl<'a> Table<'a> { 74 | /// Parses a table from raw data. 75 | pub fn parse(data: &'a [u8]) -> Option { 76 | let mut s = Stream::new(data); 77 | let version = s.read::()?; 78 | if version != 0x00010000 { 79 | return None; 80 | } 81 | 82 | let axes_array_offset = s.read::()?; 83 | s.skip::(); // reserved 84 | let axis_count = s.read::()?; 85 | 86 | // 'If axisCount is zero, then the font is not functional as a variable font, 87 | // and must be treated as a non-variable font; 88 | // any variation-specific tables or data is ignored.' 89 | let axis_count = NonZeroU16::new(axis_count)?; 90 | 91 | let mut s = Stream::new_at(data, axes_array_offset.to_usize())?; 92 | let axes = s.read_array16::(axis_count.get())?; 93 | 94 | Some(Table { axes }) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/tables/head.rs: -------------------------------------------------------------------------------- 1 | //! A [Font Header Table]( 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/head) implementation. 3 | 4 | use crate::parser::{Fixed, Stream}; 5 | use crate::Rect; 6 | 7 | /// An index format used by the [Index to Location Table]( 8 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/loca). 9 | #[allow(missing_docs)] 10 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 11 | pub enum IndexToLocationFormat { 12 | Short, 13 | Long, 14 | } 15 | 16 | /// A [Font Header Table](https://docs.microsoft.com/en-us/typography/opentype/spec/head). 17 | #[derive(Clone, Copy, Debug)] 18 | pub struct Table { 19 | /// Units per EM. 20 | /// 21 | /// Guarantee to be in a 16..=16384 range. 22 | pub units_per_em: u16, 23 | /// A bounding box that large enough to enclose any glyph from the face. 24 | pub global_bbox: Rect, 25 | /// An index format used by the [Index to Location Table]( 26 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/loca). 27 | pub index_to_location_format: IndexToLocationFormat, 28 | } 29 | 30 | impl Table { 31 | /// Parses a table from raw data. 32 | pub fn parse(data: &[u8]) -> Option { 33 | // Do not check the exact length, because some fonts include 34 | // padding in table's length in table records, which is incorrect. 35 | if data.len() < 54 { 36 | return None; 37 | } 38 | 39 | let mut s = Stream::new(data); 40 | s.skip::(); // version 41 | s.skip::(); // font revision 42 | s.skip::(); // checksum adjustment 43 | s.skip::(); // magic number 44 | s.skip::(); // flags 45 | let units_per_em = s.read::()?; 46 | s.skip::(); // created time 47 | s.skip::(); // modified time 48 | let x_min = s.read::()?; 49 | let y_min = s.read::()?; 50 | let x_max = s.read::()?; 51 | let y_max = s.read::()?; 52 | s.skip::(); // mac style 53 | s.skip::(); // lowest PPEM 54 | s.skip::(); // font direction hint 55 | let index_to_location_format = s.read::()?; 56 | 57 | if !(16..=16384).contains(&units_per_em) { 58 | return None; 59 | } 60 | 61 | let index_to_location_format = match index_to_location_format { 62 | 0 => IndexToLocationFormat::Short, 63 | 1 => IndexToLocationFormat::Long, 64 | _ => return None, 65 | }; 66 | 67 | Some(Table { 68 | units_per_em, 69 | global_bbox: Rect { 70 | x_min, 71 | y_min, 72 | x_max, 73 | y_max, 74 | }, 75 | index_to_location_format, 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/tables/hhea.rs: -------------------------------------------------------------------------------- 1 | //! A [Horizontal Header Table]( 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/hhea) implementation. 3 | 4 | use crate::parser::Stream; 5 | 6 | /// A [Horizontal Header Table](https://docs.microsoft.com/en-us/typography/opentype/spec/hhea). 7 | #[derive(Clone, Copy, Debug)] 8 | pub struct Table { 9 | /// Face ascender. 10 | pub ascender: i16, 11 | /// Face descender. 12 | pub descender: i16, 13 | /// Face line gap. 14 | pub line_gap: i16, 15 | /// Number of metrics in the `hmtx` table. 16 | pub number_of_metrics: u16, 17 | } 18 | 19 | impl Table { 20 | /// Parses a table from raw data. 21 | pub fn parse(data: &[u8]) -> Option { 22 | // Do not check the exact length, because some fonts include 23 | // padding in table's length in table records, which is incorrect. 24 | if data.len() < 36 { 25 | return None; 26 | } 27 | 28 | let mut s = Stream::new(data); 29 | s.skip::(); // version 30 | let ascender = s.read::()?; 31 | let descender = s.read::()?; 32 | let line_gap = s.read::()?; 33 | s.advance(24); 34 | let number_of_metrics = s.read::()?; 35 | 36 | Some(Table { 37 | ascender, 38 | descender, 39 | line_gap, 40 | number_of_metrics, 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/tables/hmtx.rs: -------------------------------------------------------------------------------- 1 | //! A [Horizontal/Vertical Metrics Table]( 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx) implementation. 3 | 4 | use core::num::NonZeroU16; 5 | 6 | use crate::parser::{FromData, LazyArray16, Stream}; 7 | use crate::GlyphId; 8 | 9 | /// Horizontal/Vertical Metrics. 10 | #[derive(Clone, Copy, Debug)] 11 | pub struct Metrics { 12 | /// Width/Height advance for `hmtx`/`vmtx`. 13 | pub advance: u16, 14 | /// Left/Top side bearing for `hmtx`/`vmtx`. 15 | pub side_bearing: i16, 16 | } 17 | 18 | impl FromData for Metrics { 19 | const SIZE: usize = 4; 20 | 21 | #[inline] 22 | fn parse(data: &[u8]) -> Option { 23 | let mut s = Stream::new(data); 24 | Some(Metrics { 25 | advance: s.read::()?, 26 | side_bearing: s.read::()?, 27 | }) 28 | } 29 | } 30 | 31 | /// A [Horizontal/Vertical Metrics Table]( 32 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx). 33 | /// 34 | /// `hmtx` and `vmtx` tables has the same structure, so we're reusing the same struct for both. 35 | #[derive(Clone, Copy, Debug)] 36 | pub struct Table<'a> { 37 | /// A list of metrics indexed by glyph ID. 38 | pub metrics: LazyArray16<'a, Metrics>, 39 | /// Side bearings for glyph IDs greater than or equal to the number of `metrics` values. 40 | pub bearings: LazyArray16<'a, i16>, 41 | /// Sum of long metrics + bearings. 42 | pub number_of_metrics: u16, 43 | } 44 | 45 | impl<'a> Table<'a> { 46 | /// Parses a table from raw data. 47 | /// 48 | /// - `number_of_metrics` is from the `hhea`/`vhea` table. 49 | /// - `number_of_glyphs` is from the `maxp` table. 50 | pub fn parse( 51 | mut number_of_metrics: u16, 52 | number_of_glyphs: NonZeroU16, 53 | data: &'a [u8], 54 | ) -> Option { 55 | if number_of_metrics == 0 { 56 | return None; 57 | } 58 | 59 | let mut s = Stream::new(data); 60 | let metrics = s.read_array16::(number_of_metrics)?; 61 | 62 | // 'If the number_of_metrics is less than the total number of glyphs, 63 | // then that array is followed by an array for the left side bearing values 64 | // of the remaining glyphs.' 65 | let bearings_count = number_of_glyphs.get().checked_sub(number_of_metrics); 66 | let bearings = if let Some(count) = bearings_count { 67 | number_of_metrics += count; 68 | // Some malformed fonts can skip "left side bearing values" 69 | // even when they are expected. 70 | // Therefore if we weren't able to parser them, simply fallback to an empty array. 71 | // No need to mark the whole table as malformed. 72 | s.read_array16::(count).unwrap_or_default() 73 | } else { 74 | LazyArray16::default() 75 | }; 76 | 77 | Some(Table { 78 | metrics, 79 | bearings, 80 | number_of_metrics, 81 | }) 82 | } 83 | 84 | /// Returns advance for a glyph. 85 | #[inline] 86 | pub fn advance(&self, glyph_id: GlyphId) -> Option { 87 | if glyph_id.0 >= self.number_of_metrics { 88 | return None; 89 | } 90 | 91 | if let Some(metrics) = self.metrics.get(glyph_id.0) { 92 | Some(metrics.advance) 93 | } else { 94 | // 'As an optimization, the number of records can be less than the number of glyphs, 95 | // in which case the advance value of the last record applies 96 | // to all remaining glyph IDs.' 97 | self.metrics.last().map(|m| m.advance) 98 | } 99 | } 100 | 101 | /// Returns side bearing for a glyph. 102 | #[inline] 103 | pub fn side_bearing(&self, glyph_id: GlyphId) -> Option { 104 | if let Some(metrics) = self.metrics.get(glyph_id.0) { 105 | Some(metrics.side_bearing) 106 | } else { 107 | // 'If the number_of_metrics is less than the total number of glyphs, 108 | // then that array is followed by an array for the side bearing values 109 | // of the remaining glyphs.' 110 | self.bearings 111 | .get(glyph_id.0.checked_sub(self.metrics.len())?) 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/tables/hvar.rs: -------------------------------------------------------------------------------- 1 | //! A [Horizontal Metrics Variations Table]( 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/hvar) implementation. 3 | 4 | use crate::delta_set::DeltaSetIndexMap; 5 | use crate::parser::{Offset, Offset32, Stream}; 6 | use crate::var_store::ItemVariationStore; 7 | use crate::{GlyphId, NormalizedCoordinate}; 8 | 9 | /// A [Horizontal Metrics Variations Table]( 10 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/hvar). 11 | #[derive(Clone, Copy)] 12 | pub struct Table<'a> { 13 | data: &'a [u8], 14 | variation_store: ItemVariationStore<'a>, 15 | advance_width_mapping_offset: Option, 16 | lsb_mapping_offset: Option, 17 | rsb_mapping_offset: Option, 18 | } 19 | 20 | impl<'a> Table<'a> { 21 | /// Parses a table from raw data. 22 | pub fn parse(data: &'a [u8]) -> Option { 23 | let mut s = Stream::new(data); 24 | 25 | let version = s.read::()?; 26 | if version != 0x00010000 { 27 | return None; 28 | } 29 | 30 | let variation_store_offset = s.read::()?; 31 | let var_store_s = Stream::new_at(data, variation_store_offset.to_usize())?; 32 | let variation_store = ItemVariationStore::parse(var_store_s)?; 33 | 34 | Some(Table { 35 | data, 36 | variation_store, 37 | advance_width_mapping_offset: s.read::>()?, 38 | lsb_mapping_offset: s.read::>()?, 39 | rsb_mapping_offset: s.read::>()?, 40 | }) 41 | } 42 | 43 | /// Returns the advance width offset for a glyph. 44 | #[inline] 45 | pub fn advance_offset( 46 | &self, 47 | glyph_id: GlyphId, 48 | coordinates: &[NormalizedCoordinate], 49 | ) -> Option { 50 | let (outer_idx, inner_idx) = if let Some(offset) = self.advance_width_mapping_offset { 51 | DeltaSetIndexMap::new(self.data.get(offset.to_usize()..)?).map(glyph_id.0 as u32)? 52 | } else { 53 | // 'If there is no delta-set index mapping table for advance widths, 54 | // then glyph IDs implicitly provide the indices: 55 | // for a given glyph ID, the delta-set outer-level index is zero, 56 | // and the glyph ID is the delta-set inner-level index.' 57 | (0, glyph_id.0) 58 | }; 59 | 60 | self.variation_store 61 | .parse_delta(outer_idx, inner_idx, coordinates) 62 | } 63 | 64 | /// Returns the left side bearing offset for a glyph. 65 | #[inline] 66 | pub fn left_side_bearing_offset( 67 | &self, 68 | glyph_id: GlyphId, 69 | coordinates: &[NormalizedCoordinate], 70 | ) -> Option { 71 | let set_data = self.data.get(self.lsb_mapping_offset?.to_usize()..)?; 72 | self.side_bearing_offset(glyph_id, coordinates, set_data) 73 | } 74 | 75 | /// Returns the right side bearing offset for a glyph. 76 | #[inline] 77 | pub fn right_side_bearing_offset( 78 | &self, 79 | glyph_id: GlyphId, 80 | coordinates: &[NormalizedCoordinate], 81 | ) -> Option { 82 | let set_data = self.data.get(self.rsb_mapping_offset?.to_usize()..)?; 83 | self.side_bearing_offset(glyph_id, coordinates, set_data) 84 | } 85 | 86 | fn side_bearing_offset( 87 | &self, 88 | glyph_id: GlyphId, 89 | coordinates: &[NormalizedCoordinate], 90 | set_data: &[u8], 91 | ) -> Option { 92 | let (outer_idx, inner_idx) = DeltaSetIndexMap::new(set_data).map(glyph_id.0 as u32)?; 93 | self.variation_store 94 | .parse_delta(outer_idx, inner_idx, coordinates) 95 | } 96 | } 97 | 98 | impl core::fmt::Debug for Table<'_> { 99 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 100 | write!(f, "Table {{ ... }}") 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/tables/loca.rs: -------------------------------------------------------------------------------- 1 | //! An [Index to Location Table](https://docs.microsoft.com/en-us/typography/opentype/spec/loca) 2 | //! implementation. 3 | 4 | use core::convert::TryFrom; 5 | use core::num::NonZeroU16; 6 | use core::ops::Range; 7 | 8 | use crate::parser::{LazyArray16, NumFrom, Stream}; 9 | use crate::{GlyphId, IndexToLocationFormat}; 10 | 11 | /// An [Index to Location Table](https://docs.microsoft.com/en-us/typography/opentype/spec/loca). 12 | #[derive(Clone, Copy, Debug)] 13 | pub enum Table<'a> { 14 | /// Short offsets. 15 | Short(LazyArray16<'a, u16>), 16 | /// Long offsets. 17 | Long(LazyArray16<'a, u32>), 18 | } 19 | 20 | impl<'a> Table<'a> { 21 | /// Parses a table from raw data. 22 | /// 23 | /// - `number_of_glyphs` is from the `maxp` table. 24 | /// - `format` is from the `head` table. 25 | pub fn parse( 26 | number_of_glyphs: NonZeroU16, 27 | format: IndexToLocationFormat, 28 | data: &'a [u8], 29 | ) -> Option { 30 | // The number of ranges is `maxp.numGlyphs + 1`. 31 | // 32 | // Check for overflow first. 33 | let mut total = if number_of_glyphs.get() == u16::MAX { 34 | number_of_glyphs.get() 35 | } else { 36 | number_of_glyphs.get() + 1 37 | }; 38 | 39 | // By the spec, the number of `loca` offsets is `maxp.numGlyphs + 1`. 40 | // But some malformed fonts can have less glyphs than that. 41 | // In which case we try to parse only the available offsets 42 | // and do not return an error, since the expected data length 43 | // would go beyond table's length. 44 | // 45 | // In case when `loca` has more data than needed we simply ignore the rest. 46 | let actual_total = match format { 47 | IndexToLocationFormat::Short => data.len() / 2, 48 | IndexToLocationFormat::Long => data.len() / 4, 49 | }; 50 | let actual_total = u16::try_from(actual_total).ok()?; 51 | total = total.min(actual_total); 52 | 53 | let mut s = Stream::new(data); 54 | match format { 55 | IndexToLocationFormat::Short => Some(Table::Short(s.read_array16::(total)?)), 56 | IndexToLocationFormat::Long => Some(Table::Long(s.read_array16::(total)?)), 57 | } 58 | } 59 | 60 | /// Returns the number of offsets. 61 | #[inline] 62 | pub fn len(&self) -> u16 { 63 | match self { 64 | Table::Short(ref array) => array.len(), 65 | Table::Long(ref array) => array.len(), 66 | } 67 | } 68 | 69 | /// Checks if there are any offsets. 70 | pub fn is_empty(&self) -> bool { 71 | self.len() == 0 72 | } 73 | 74 | /// Returns glyph's range in the `glyf` table. 75 | #[inline] 76 | pub fn glyph_range(&self, glyph_id: GlyphId) -> Option> { 77 | let glyph_id = glyph_id.0; 78 | if glyph_id == u16::MAX { 79 | return None; 80 | } 81 | 82 | // Glyph ID must be smaller than total number of values in a `loca` array. 83 | if glyph_id + 1 >= self.len() { 84 | return None; 85 | } 86 | 87 | let range = match self { 88 | Table::Short(ref array) => { 89 | // 'The actual local offset divided by 2 is stored.' 90 | usize::from(array.get(glyph_id)?) * 2..usize::from(array.get(glyph_id + 1)?) * 2 91 | } 92 | Table::Long(ref array) => { 93 | usize::num_from(array.get(glyph_id)?)..usize::num_from(array.get(glyph_id + 1)?) 94 | } 95 | }; 96 | 97 | if range.start >= range.end { 98 | // 'The offsets must be in ascending order.' 99 | // And range cannot be empty. 100 | None 101 | } else { 102 | Some(range) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/tables/maxp.rs: -------------------------------------------------------------------------------- 1 | //! A [Maximum Profile Table]( 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/maxp) implementation. 3 | 4 | use core::num::NonZeroU16; 5 | 6 | use crate::parser::Stream; 7 | 8 | /// A [Maximum Profile Table](https://docs.microsoft.com/en-us/typography/opentype/spec/maxp). 9 | #[derive(Clone, Copy, Debug)] 10 | pub struct Table { 11 | /// The total number of glyphs in the face. 12 | pub number_of_glyphs: NonZeroU16, 13 | } 14 | 15 | impl Table { 16 | /// Parses a table from raw data. 17 | pub fn parse(data: &[u8]) -> Option { 18 | let mut s = Stream::new(data); 19 | let version = s.read::()?; 20 | if !(version == 0x00005000 || version == 0x00010000) { 21 | return None; 22 | } 23 | 24 | let n = s.read::()?; 25 | let number_of_glyphs = NonZeroU16::new(n)?; 26 | Some(Table { number_of_glyphs }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/tables/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cbdt; 2 | pub mod cblc; 3 | mod cff; 4 | pub mod cmap; 5 | pub mod colr; 6 | pub mod cpal; 7 | pub mod glyf; 8 | pub mod head; 9 | pub mod hhea; 10 | pub mod hmtx; 11 | pub mod kern; 12 | pub mod loca; 13 | pub mod maxp; 14 | pub mod name; 15 | pub mod os2; 16 | pub mod post; 17 | pub mod sbix; 18 | pub mod stat; 19 | pub mod svg; 20 | pub mod vhea; 21 | pub mod vorg; 22 | 23 | #[cfg(feature = "opentype-layout")] 24 | pub mod gdef; 25 | #[cfg(feature = "opentype-layout")] 26 | pub mod gpos; 27 | #[cfg(feature = "opentype-layout")] 28 | pub mod gsub; 29 | #[cfg(feature = "opentype-layout")] 30 | pub mod math; 31 | 32 | #[cfg(feature = "apple-layout")] 33 | pub mod ankr; 34 | #[cfg(feature = "apple-layout")] 35 | pub mod feat; 36 | #[cfg(feature = "apple-layout")] 37 | pub mod kerx; 38 | #[cfg(feature = "apple-layout")] 39 | pub mod morx; 40 | #[cfg(feature = "apple-layout")] 41 | pub mod trak; 42 | 43 | #[cfg(feature = "variable-fonts")] 44 | pub mod avar; 45 | #[cfg(feature = "variable-fonts")] 46 | pub mod fvar; 47 | #[cfg(feature = "variable-fonts")] 48 | pub mod gvar; 49 | #[cfg(feature = "variable-fonts")] 50 | pub mod hvar; 51 | #[cfg(feature = "variable-fonts")] 52 | pub mod mvar; 53 | #[cfg(feature = "variable-fonts")] 54 | pub mod vvar; 55 | 56 | pub use cff::cff1; 57 | #[cfg(feature = "variable-fonts")] 58 | pub use cff::cff2; 59 | pub use cff::CFFError; 60 | -------------------------------------------------------------------------------- /src/tables/mvar.rs: -------------------------------------------------------------------------------- 1 | //! A [Metrics Variations Table]( 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/mvar) implementation. 3 | 4 | use crate::parser::{FromData, LazyArray16, Offset, Offset16, Stream}; 5 | use crate::var_store::ItemVariationStore; 6 | use crate::{NormalizedCoordinate, Tag}; 7 | 8 | #[derive(Clone, Copy)] 9 | struct ValueRecord { 10 | value_tag: Tag, 11 | delta_set_outer_index: u16, 12 | delta_set_inner_index: u16, 13 | } 14 | 15 | impl FromData for ValueRecord { 16 | const SIZE: usize = 8; 17 | 18 | #[inline] 19 | fn parse(data: &[u8]) -> Option { 20 | let mut s = Stream::new(data); 21 | Some(ValueRecord { 22 | value_tag: s.read::()?, 23 | delta_set_outer_index: s.read::()?, 24 | delta_set_inner_index: s.read::()?, 25 | }) 26 | } 27 | } 28 | 29 | /// A [Metrics Variations Table]( 30 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/mvar). 31 | #[derive(Clone, Copy)] 32 | pub struct Table<'a> { 33 | variation_store: ItemVariationStore<'a>, 34 | records: LazyArray16<'a, ValueRecord>, 35 | } 36 | 37 | impl<'a> Table<'a> { 38 | /// Parses a table from raw data. 39 | pub fn parse(data: &'a [u8]) -> Option { 40 | let mut s = Stream::new(data); 41 | 42 | let version = s.read::()?; 43 | if version != 0x00010000 { 44 | return None; 45 | } 46 | 47 | s.skip::(); // reserved 48 | let value_record_size = s.read::()?; 49 | 50 | if usize::from(value_record_size) != ValueRecord::SIZE { 51 | return None; 52 | } 53 | 54 | let count = s.read::()?; 55 | if count == 0 { 56 | return None; 57 | } 58 | 59 | let var_store_offset = s.read::>()??.to_usize(); 60 | let records = s.read_array16::(count)?; 61 | let variation_store = ItemVariationStore::parse(Stream::new_at(data, var_store_offset)?)?; 62 | 63 | Some(Table { 64 | variation_store, 65 | records, 66 | }) 67 | } 68 | 69 | /// Returns a metric offset by tag. 70 | pub fn metric_offset(&self, tag: Tag, coordinates: &[NormalizedCoordinate]) -> Option { 71 | let (_, record) = self.records.binary_search_by(|r| r.value_tag.cmp(&tag))?; 72 | self.variation_store.parse_delta( 73 | record.delta_set_outer_index, 74 | record.delta_set_inner_index, 75 | coordinates, 76 | ) 77 | } 78 | } 79 | 80 | impl core::fmt::Debug for Table<'_> { 81 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 82 | write!(f, "Table {{ ... }}") 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/tables/svg.rs: -------------------------------------------------------------------------------- 1 | //! An [SVG Table](https://docs.microsoft.com/en-us/typography/opentype/spec/svg) implementation. 2 | 3 | use crate::parser::{FromData, LazyArray16, NumFrom, Offset, Offset32, Stream}; 4 | use crate::GlyphId; 5 | 6 | /// An [SVG documents]( 7 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/svg#svg-document-list). 8 | #[derive(Clone, Copy, Debug)] 9 | pub struct SvgDocument<'a> { 10 | /// The SVG document data. 11 | /// 12 | /// Can be stored as a string or as a gzip compressed data, aka SVGZ. 13 | pub data: &'a [u8], 14 | /// The first glyph ID for the range covered by this record. 15 | pub start_glyph_id: GlyphId, 16 | /// The last glyph ID, *inclusive*, for the range covered by this record. 17 | pub end_glyph_id: GlyphId, 18 | } 19 | 20 | impl SvgDocument<'_> { 21 | /// Returns the glyphs range. 22 | pub fn glyphs_range(&self) -> core::ops::RangeInclusive { 23 | self.start_glyph_id..=self.end_glyph_id 24 | } 25 | } 26 | 27 | #[derive(Clone, Copy)] 28 | struct SvgDocumentRecord { 29 | start_glyph_id: GlyphId, 30 | end_glyph_id: GlyphId, 31 | svg_doc_offset: Option, 32 | svg_doc_length: u32, 33 | } 34 | 35 | impl SvgDocumentRecord { 36 | fn glyphs_range(&self) -> core::ops::RangeInclusive { 37 | self.start_glyph_id..=self.end_glyph_id 38 | } 39 | } 40 | 41 | impl FromData for SvgDocumentRecord { 42 | const SIZE: usize = 12; 43 | 44 | #[inline] 45 | fn parse(data: &[u8]) -> Option { 46 | let mut s = Stream::new(data); 47 | Some(SvgDocumentRecord { 48 | start_glyph_id: s.read::()?, 49 | end_glyph_id: s.read::()?, 50 | svg_doc_offset: s.read::>()?, 51 | svg_doc_length: s.read::()?, 52 | }) 53 | } 54 | } 55 | 56 | /// A list of [SVG documents]( 57 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/svg#svg-document-list). 58 | #[derive(Clone, Copy)] 59 | pub struct SvgDocumentsList<'a> { 60 | data: &'a [u8], 61 | records: LazyArray16<'a, SvgDocumentRecord>, 62 | } 63 | 64 | impl<'a> SvgDocumentsList<'a> { 65 | /// Returns SVG document data at index. 66 | /// 67 | /// `index` is not a GlyphId. You should use [`find()`](SvgDocumentsList::find) instead. 68 | #[inline] 69 | pub fn get(&self, index: u16) -> Option> { 70 | let record = self.records.get(index)?; 71 | let offset = record.svg_doc_offset?.to_usize(); 72 | self.data 73 | .get(offset..offset + usize::num_from(record.svg_doc_length)) 74 | .map(|data| SvgDocument { 75 | data, 76 | start_glyph_id: record.start_glyph_id, 77 | end_glyph_id: record.end_glyph_id, 78 | }) 79 | } 80 | 81 | /// Returns a SVG document data by glyph ID. 82 | #[inline] 83 | pub fn find(&self, glyph_id: GlyphId) -> Option> { 84 | let index = self 85 | .records 86 | .into_iter() 87 | .position(|v| v.glyphs_range().contains(&glyph_id))?; 88 | self.get(index as u16) 89 | } 90 | 91 | /// Returns the number of SVG documents in the list. 92 | pub fn len(&self) -> u16 { 93 | self.records.len() 94 | } 95 | 96 | /// Checks if the list is empty. 97 | pub fn is_empty(&self) -> bool { 98 | self.records.is_empty() 99 | } 100 | } 101 | 102 | impl core::fmt::Debug for SvgDocumentsList<'_> { 103 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 104 | write!(f, "SvgDocumentsList {{ ... }}") 105 | } 106 | } 107 | 108 | impl<'a> IntoIterator for SvgDocumentsList<'a> { 109 | type Item = SvgDocument<'a>; 110 | type IntoIter = SvgDocumentsListIter<'a>; 111 | 112 | #[inline] 113 | fn into_iter(self) -> Self::IntoIter { 114 | SvgDocumentsListIter { 115 | list: self, 116 | index: 0, 117 | } 118 | } 119 | } 120 | 121 | /// An iterator over [`SvgDocumentsList`] values. 122 | #[derive(Clone, Copy)] 123 | #[allow(missing_debug_implementations)] 124 | pub struct SvgDocumentsListIter<'a> { 125 | list: SvgDocumentsList<'a>, 126 | index: u16, 127 | } 128 | 129 | impl<'a> Iterator for SvgDocumentsListIter<'a> { 130 | type Item = SvgDocument<'a>; 131 | 132 | #[inline] 133 | fn next(&mut self) -> Option { 134 | if self.index < self.list.len() { 135 | self.index += 1; 136 | self.list.get(self.index - 1) 137 | } else { 138 | None 139 | } 140 | } 141 | 142 | #[inline] 143 | fn count(self) -> usize { 144 | usize::from(self.list.len().saturating_sub(self.index)) 145 | } 146 | } 147 | 148 | /// An [SVG Table](https://docs.microsoft.com/en-us/typography/opentype/spec/svg). 149 | #[derive(Clone, Copy, Debug)] 150 | pub struct Table<'a> { 151 | /// A list of SVG documents. 152 | pub documents: SvgDocumentsList<'a>, 153 | } 154 | 155 | impl<'a> Table<'a> { 156 | /// Parses a table from raw data. 157 | pub fn parse(data: &'a [u8]) -> Option { 158 | let mut s = Stream::new(data); 159 | s.skip::(); // version 160 | let doc_list_offset = s.read::>()??; 161 | 162 | let mut s = Stream::new_at(data, doc_list_offset.to_usize())?; 163 | let count = s.read::()?; 164 | let records = s.read_array16::(count)?; 165 | 166 | Some(Table { 167 | documents: SvgDocumentsList { 168 | data: &data[doc_list_offset.0 as usize..], 169 | records, 170 | }, 171 | }) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/tables/trak.rs: -------------------------------------------------------------------------------- 1 | //! A [Tracking Table]( 2 | //! https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6trak.html) implementation. 3 | 4 | use crate::parser::{Fixed, FromData, LazyArray16, Offset, Offset16, Offset32, Stream}; 5 | 6 | #[derive(Clone, Copy, Debug)] 7 | struct TrackTableRecord { 8 | value: Fixed, 9 | name_id: u16, 10 | offset: Offset16, // Offset from start of the table. 11 | } 12 | 13 | impl FromData for TrackTableRecord { 14 | const SIZE: usize = 8; 15 | 16 | #[inline] 17 | fn parse(data: &[u8]) -> Option { 18 | let mut s = Stream::new(data); 19 | Some(TrackTableRecord { 20 | value: s.read::()?, 21 | name_id: s.read::()?, 22 | offset: s.read::()?, 23 | }) 24 | } 25 | } 26 | 27 | /// A single track. 28 | #[derive(Clone, Copy, Debug)] 29 | pub struct Track<'a> { 30 | /// A track value. 31 | pub value: f32, 32 | /// The `name` table index for the track's name. 33 | pub name_index: u16, 34 | /// A list of tracking values for each size. 35 | pub values: LazyArray16<'a, i16>, 36 | } 37 | 38 | /// A list of tracks. 39 | #[derive(Clone, Copy, Default, Debug)] 40 | pub struct Tracks<'a> { 41 | data: &'a [u8], // the whole table 42 | records: LazyArray16<'a, TrackTableRecord>, 43 | sizes_count: u16, 44 | } 45 | 46 | impl<'a> Tracks<'a> { 47 | /// Returns a track at index. 48 | pub fn get(&self, index: u16) -> Option> { 49 | let record = self.records.get(index)?; 50 | let mut s = Stream::new(self.data.get(record.offset.to_usize()..)?); 51 | Some(Track { 52 | value: record.value.0, 53 | values: s.read_array16::(self.sizes_count)?, 54 | name_index: record.name_id, 55 | }) 56 | } 57 | 58 | /// Returns the number of tracks. 59 | pub fn len(&self) -> u16 { 60 | self.records.len() 61 | } 62 | 63 | /// Checks if there are any tracks. 64 | pub fn is_empty(&self) -> bool { 65 | self.records.is_empty() 66 | } 67 | } 68 | 69 | impl<'a> IntoIterator for Tracks<'a> { 70 | type Item = Track<'a>; 71 | type IntoIter = TracksIter<'a>; 72 | 73 | #[inline] 74 | fn into_iter(self) -> Self::IntoIter { 75 | TracksIter { 76 | tracks: self, 77 | index: 0, 78 | } 79 | } 80 | } 81 | 82 | /// An iterator over [`Tracks`]. 83 | #[allow(missing_debug_implementations)] 84 | pub struct TracksIter<'a> { 85 | tracks: Tracks<'a>, 86 | index: u16, 87 | } 88 | 89 | impl<'a> Iterator for TracksIter<'a> { 90 | type Item = Track<'a>; 91 | 92 | fn next(&mut self) -> Option { 93 | if self.index < self.tracks.len() { 94 | self.index += 1; 95 | self.tracks.get(self.index - 1) 96 | } else { 97 | None 98 | } 99 | } 100 | } 101 | 102 | /// A track data. 103 | #[derive(Clone, Copy, Default, Debug)] 104 | pub struct TrackData<'a> { 105 | /// A list of tracks. 106 | pub tracks: Tracks<'a>, 107 | /// A list of sizes. 108 | pub sizes: LazyArray16<'a, Fixed>, 109 | } 110 | 111 | impl<'a> TrackData<'a> { 112 | fn parse(offset: usize, data: &'a [u8]) -> Option { 113 | let mut s = Stream::new_at(data, offset)?; 114 | let tracks_count = s.read::()?; 115 | let sizes_count = s.read::()?; 116 | let size_table_offset = s.read::()?; // Offset from start of the table. 117 | 118 | let tracks = Tracks { 119 | data, 120 | records: s.read_array16::(tracks_count)?, 121 | sizes_count, 122 | }; 123 | 124 | // TODO: Isn't the size table is directly after the tracks table?! 125 | // Why we need an offset then? 126 | let sizes = { 127 | let mut s = Stream::new_at(data, size_table_offset.to_usize())?; 128 | s.read_array16::(sizes_count)? 129 | }; 130 | 131 | Some(TrackData { tracks, sizes }) 132 | } 133 | } 134 | 135 | /// A [Tracking Table]( 136 | /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6trak.html). 137 | #[derive(Clone, Copy, Debug)] 138 | pub struct Table<'a> { 139 | /// Horizontal track data. 140 | pub horizontal: TrackData<'a>, 141 | /// Vertical track data. 142 | pub vertical: TrackData<'a>, 143 | } 144 | 145 | impl<'a> Table<'a> { 146 | /// Parses a table from raw data. 147 | pub fn parse(data: &'a [u8]) -> Option { 148 | let mut s = Stream::new(data); 149 | 150 | let version = s.read::()?; 151 | if version != 0x00010000 { 152 | return None; 153 | } 154 | 155 | let format = s.read::()?; 156 | if format != 0 { 157 | return None; 158 | } 159 | 160 | let hor_offset = s.read::>()?; 161 | let ver_offset = s.read::>()?; 162 | s.skip::(); // reserved 163 | 164 | let horizontal = if let Some(offset) = hor_offset { 165 | TrackData::parse(offset.to_usize(), data)? 166 | } else { 167 | TrackData::default() 168 | }; 169 | 170 | let vertical = if let Some(offset) = ver_offset { 171 | TrackData::parse(offset.to_usize(), data)? 172 | } else { 173 | TrackData::default() 174 | }; 175 | 176 | Some(Table { 177 | horizontal, 178 | vertical, 179 | }) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/tables/vhea.rs: -------------------------------------------------------------------------------- 1 | //! A [Vertical Header Table]( 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/vhea) implementation. 3 | 4 | use crate::parser::Stream; 5 | 6 | /// A [Vertical Header Table](https://docs.microsoft.com/en-us/typography/opentype/spec/vhea). 7 | #[derive(Clone, Copy, Default, Debug)] 8 | pub struct Table { 9 | /// Face ascender. 10 | pub ascender: i16, 11 | /// Face descender. 12 | pub descender: i16, 13 | /// Face line gap. 14 | pub line_gap: i16, 15 | /// Number of metrics in the `vmtx` table. 16 | pub number_of_metrics: u16, 17 | } 18 | 19 | impl Table { 20 | /// Parses a table from raw data. 21 | pub fn parse(data: &[u8]) -> Option { 22 | // Do not check the exact length, because some fonts include 23 | // padding in table's length in table records, which is incorrect. 24 | if data.len() < 36 { 25 | return None; 26 | } 27 | 28 | let mut s = Stream::new(data); 29 | s.skip::(); // version 30 | let ascender = s.read::()?; 31 | let descender = s.read::()?; 32 | let line_gap = s.read::()?; 33 | s.advance(24); 34 | let number_of_metrics = s.read::()?; 35 | 36 | Some(Table { 37 | ascender, 38 | descender, 39 | line_gap, 40 | number_of_metrics, 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/tables/vorg.rs: -------------------------------------------------------------------------------- 1 | //! A [Vertical Origin Table]( 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/vorg) implementation. 3 | 4 | use crate::parser::{FromData, LazyArray16, Stream}; 5 | use crate::GlyphId; 6 | 7 | /// Vertical origin metrics for the 8 | /// [Vertical Origin Table](https://docs.microsoft.com/en-us/typography/opentype/spec/vorg). 9 | #[derive(Clone, Copy, Debug)] 10 | pub struct VerticalOriginMetrics { 11 | /// Glyph ID. 12 | pub glyph_id: GlyphId, 13 | /// Y coordinate, in the font's design coordinate system, of the vertical origin. 14 | pub y: i16, 15 | } 16 | 17 | impl FromData for VerticalOriginMetrics { 18 | const SIZE: usize = 4; 19 | 20 | #[inline] 21 | fn parse(data: &[u8]) -> Option { 22 | let mut s = Stream::new(data); 23 | Some(VerticalOriginMetrics { 24 | glyph_id: s.read::()?, 25 | y: s.read::()?, 26 | }) 27 | } 28 | } 29 | 30 | /// A [Vertical Origin Table](https://docs.microsoft.com/en-us/typography/opentype/spec/vorg). 31 | #[derive(Clone, Copy, Debug)] 32 | pub struct Table<'a> { 33 | /// Default origin. 34 | pub default_y: i16, 35 | /// A list of metrics for each glyph. 36 | /// 37 | /// Ordered by `glyph_id`. 38 | pub metrics: LazyArray16<'a, VerticalOriginMetrics>, 39 | } 40 | 41 | impl<'a> Table<'a> { 42 | /// Parses a table from raw data. 43 | pub fn parse(data: &'a [u8]) -> Option { 44 | let mut s = Stream::new(data); 45 | 46 | let version = s.read::()?; 47 | if version != 0x00010000 { 48 | return None; 49 | } 50 | 51 | let default_y = s.read::()?; 52 | let count = s.read::()?; 53 | let metrics = s.read_array16::(count)?; 54 | 55 | Some(Table { default_y, metrics }) 56 | } 57 | 58 | /// Returns glyph's Y origin. 59 | pub fn glyph_y_origin(&self, glyph_id: GlyphId) -> i16 { 60 | self.metrics 61 | .binary_search_by(|m| m.glyph_id.cmp(&glyph_id)) 62 | .map(|(_, m)| m.y) 63 | .unwrap_or(self.default_y) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/tables/vvar.rs: -------------------------------------------------------------------------------- 1 | //! A [Vertical Metrics Variations Table]( 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/hvar) implementation. 3 | 4 | use crate::delta_set::DeltaSetIndexMap; 5 | use crate::parser::{Offset, Offset32, Stream}; 6 | use crate::var_store::ItemVariationStore; 7 | use crate::{GlyphId, NormalizedCoordinate}; 8 | 9 | /// A [Vertical Metrics Variations Table]( 10 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/hvar). 11 | #[derive(Clone, Copy)] 12 | pub struct Table<'a> { 13 | data: &'a [u8], 14 | variation_store: ItemVariationStore<'a>, 15 | advance_height_mapping_offset: Option, 16 | tsb_mapping_offset: Option, 17 | bsb_mapping_offset: Option, 18 | vorg_mapping_offset: Option, 19 | } 20 | 21 | impl<'a> Table<'a> { 22 | /// Parses a table from raw data. 23 | pub fn parse(data: &'a [u8]) -> Option { 24 | let mut s = Stream::new(data); 25 | 26 | let version = s.read::()?; 27 | if version != 0x00010000 { 28 | return None; 29 | } 30 | 31 | let variation_store_offset = s.read::()?; 32 | let var_store_s = Stream::new_at(data, variation_store_offset.to_usize())?; 33 | let variation_store = ItemVariationStore::parse(var_store_s)?; 34 | 35 | Some(Table { 36 | data, 37 | variation_store, 38 | advance_height_mapping_offset: s.read::>()?, 39 | tsb_mapping_offset: s.read::>()?, 40 | bsb_mapping_offset: s.read::>()?, 41 | vorg_mapping_offset: s.read::>()?, 42 | }) 43 | } 44 | 45 | /// Returns the advance height offset for a glyph. 46 | #[inline] 47 | pub fn advance_offset( 48 | &self, 49 | glyph_id: GlyphId, 50 | coordinates: &[NormalizedCoordinate], 51 | ) -> Option { 52 | let (outer_idx, inner_idx) = if let Some(offset) = self.advance_height_mapping_offset { 53 | DeltaSetIndexMap::new(self.data.get(offset.to_usize()..)?).map(glyph_id.0 as u32)? 54 | } else { 55 | // 'If there is no delta-set index mapping table for advance widths, 56 | // then glyph IDs implicitly provide the indices: 57 | // for a given glyph ID, the delta-set outer-level index is zero, 58 | // and the glyph ID is the delta-set inner-level index.' 59 | (0, glyph_id.0) 60 | }; 61 | 62 | self.variation_store 63 | .parse_delta(outer_idx, inner_idx, coordinates) 64 | } 65 | 66 | /// Returns the top side bearing offset for a glyph. 67 | #[inline] 68 | pub fn top_side_bearing_offset( 69 | &self, 70 | glyph_id: GlyphId, 71 | coordinates: &[NormalizedCoordinate], 72 | ) -> Option { 73 | let set_data = self.data.get(self.tsb_mapping_offset?.to_usize()..)?; 74 | self.side_bearing_offset(glyph_id, coordinates, set_data) 75 | } 76 | 77 | /// Returns the bottom side bearing offset for a glyph. 78 | #[inline] 79 | pub fn bottom_side_bearing_offset( 80 | &self, 81 | glyph_id: GlyphId, 82 | coordinates: &[NormalizedCoordinate], 83 | ) -> Option { 84 | let set_data = self.data.get(self.bsb_mapping_offset?.to_usize()..)?; 85 | self.side_bearing_offset(glyph_id, coordinates, set_data) 86 | } 87 | 88 | /// Returns the vertical origin offset for a glyph. 89 | #[inline] 90 | pub fn vertical_origin_offset( 91 | &self, 92 | glyph_id: GlyphId, 93 | coordinates: &[NormalizedCoordinate], 94 | ) -> Option { 95 | let set_data = self.data.get(self.vorg_mapping_offset?.to_usize()..)?; 96 | self.side_bearing_offset(glyph_id, coordinates, set_data) 97 | } 98 | 99 | fn side_bearing_offset( 100 | &self, 101 | glyph_id: GlyphId, 102 | coordinates: &[NormalizedCoordinate], 103 | set_data: &[u8], 104 | ) -> Option { 105 | let (outer_idx, inner_idx) = DeltaSetIndexMap::new(set_data).map(glyph_id.0 as u32)?; 106 | self.variation_store 107 | .parse_delta(outer_idx, inner_idx, coordinates) 108 | } 109 | } 110 | 111 | impl core::fmt::Debug for Table<'_> { 112 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 113 | write!(f, "Table {{ ... }}") 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /testing-tools/font-view/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | moc_*.cpp 3 | moc_*.h 4 | ui_*.h 5 | font-view.app 6 | Makefile 7 | .qmake.stash 8 | -------------------------------------------------------------------------------- /testing-tools/font-view/README.md: -------------------------------------------------------------------------------- 1 | # font-view 2 | 3 | A simple tool to preview all glyphs in the font using `ttf-parser`, `freetype` and `harfbuzz`. 4 | 5 | ## Build 6 | 7 | ```sh 8 | # build ttf-parser C API first 9 | cargo build --release --manifest-path ../../c-api/Cargo.toml 10 | 11 | # build only with ttf-parser support 12 | qmake 13 | make 14 | 15 | # or build with freetype support 16 | qmake DEFINES+=WITH_FREETYPE 17 | make 18 | 19 | # or build with harfbuzz support 20 | # note that harfbuzz should be built from sources using meson, 21 | # because we're using an unstable API 22 | # 23 | # build harfbuzz first 24 | meson builddir -Dexperimental_api=true --buildtype release 25 | ninja -C builddir 26 | # build font-view 27 | qmake DEFINES+=WITH_HARFBUZZ HARFBUZZ_SRC=/path/to/harfbuzz-master/ 28 | make 29 | 30 | # or with all 31 | qmake DEFINES+=WITH_FREETYPE DEFINES+=WITH_HARFBUZZ HARFBUZZ_SRC=/path/to/harfbuzz-master/ 32 | make 33 | ``` 34 | -------------------------------------------------------------------------------- /testing-tools/font-view/font-view.pro: -------------------------------------------------------------------------------- 1 | QT += widgets 2 | 3 | CONFIG += c++14 4 | 5 | CONFIG(release, debug|release): LIBS += -L$$PWD/../../c-api/target/release/ -lttfparser 6 | else:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../c-api/target/debug/ -lttfparser 7 | 8 | INCLUDEPATH += $$PWD/../../c-api 9 | DEPENDPATH += $$PWD/../../c-api 10 | 11 | SOURCES += \ 12 | glyphsview.cpp \ 13 | main.cpp \ 14 | mainwindow.cpp \ 15 | ttfparserfont.cpp 16 | 17 | HEADERS += \ 18 | glyph.h \ 19 | glyphsview.h \ 20 | mainwindow.h \ 21 | ttfparserfont.h 22 | 23 | FORMS += \ 24 | mainwindow.ui 25 | 26 | macx { 27 | QT_CONFIG -= no-pkg-config 28 | PKG_CONFIG = /opt/homebrew/bin/pkg-config 29 | } 30 | 31 | # qmake DEFINES+=WITH_FREETYPE 32 | contains(DEFINES, WITH_FREETYPE) { 33 | SOURCES += freetypefont.cpp 34 | HEADERS += freetypefont.h 35 | 36 | CONFIG += link_pkgconfig 37 | PKGCONFIG += freetype2 38 | } 39 | 40 | # qmake DEFINES+=WITH_HARFBUZZ HARFBUZZ_SRC=/path/to/harfbuzz-master/ 41 | contains(DEFINES, WITH_HARFBUZZ) { 42 | DEFINES += HB_EXPERIMENTAL_API 43 | 44 | SOURCES += harfbuzzfont.cpp 45 | HEADERS += harfbuzzfont.h 46 | 47 | # harfbuzz should be built with meson 48 | LIBS += -L$$HARFBUZZ_SRC/builddir/src/ -lharfbuzz 49 | INCLUDEPATH += $$HARFBUZZ_SRC/src 50 | } 51 | -------------------------------------------------------------------------------- /testing-tools/font-view/freetypefont.cpp: -------------------------------------------------------------------------------- 1 | // Based on https://www.freetype.org/freetype2/docs/tutorial/example5.cpp 2 | 3 | #include 4 | 5 | #include "freetypefont.h" 6 | 7 | const FT_Fixed MULTIPLIER_FT = 65536L; 8 | 9 | const char* getErrorMessage(FT_Error err) 10 | { 11 | #undef __FTERRORS_H__ 12 | #define FT_ERRORDEF( e, v, s ) case e: return s; 13 | #define FT_ERROR_START_LIST switch (err) { 14 | #define FT_ERROR_END_LIST } 15 | #include FT_ERRORS_H 16 | return "(Unknown error)"; 17 | } 18 | 19 | struct Outliner 20 | { 21 | static int moveToFn(const FT_Vector *to, void *user) 22 | { 23 | auto self = static_cast(user); 24 | self->path.moveTo(to->x, to->y); 25 | return 0; 26 | } 27 | 28 | static int lineToFn(const FT_Vector *to, void *user) 29 | { 30 | auto self = static_cast(user); 31 | self->path.lineTo(to->x, to->y); 32 | return 0; 33 | } 34 | 35 | static int quadToFn(const FT_Vector *control, const FT_Vector *to, void *user) 36 | { 37 | auto self = static_cast(user); 38 | self->path.quadTo(control->x, control->y, to->x, to->y); 39 | return 0; 40 | } 41 | 42 | static int cubicToFn(const FT_Vector *controlOne, 43 | const FT_Vector *controlTwo, 44 | const FT_Vector *to, 45 | void *user) 46 | { 47 | auto self = static_cast(user); 48 | self->path.cubicTo(controlOne->x, controlOne->y, controlTwo->x, controlTwo->y, to->x, to->y); 49 | return 0; 50 | } 51 | 52 | QPainterPath path; 53 | }; 54 | 55 | FreeTypeFont::FreeTypeFont() 56 | { 57 | const auto error = FT_Init_FreeType(&m_ftLibrary); 58 | if (error) { 59 | throw tr("Failed to init FreeType.\n%1").arg(getErrorMessage(error)); 60 | } 61 | } 62 | 63 | FreeTypeFont::~FreeTypeFont() 64 | { 65 | if (m_ftFace) { 66 | FT_Done_Face(m_ftFace); 67 | } 68 | 69 | FT_Done_FreeType(m_ftLibrary); 70 | } 71 | 72 | void FreeTypeFont::open(const QString &path, const quint32 index) 73 | { 74 | if (isOpen()) { 75 | FT_Done_Face(m_ftFace); 76 | m_ftFace = nullptr; 77 | } 78 | 79 | const auto utf8Path = path.toUtf8(); 80 | const auto error = FT_New_Face(m_ftLibrary, utf8Path.constData(), index, &m_ftFace); 81 | if (error) { 82 | throw tr("Failed to open a font.\n%1").arg(getErrorMessage(error)); 83 | } 84 | } 85 | 86 | bool FreeTypeFont::isOpen() const 87 | { 88 | return m_ftFace != nullptr; 89 | } 90 | 91 | FontInfo FreeTypeFont::fontInfo() const 92 | { 93 | if (!isOpen()) { 94 | throw tr("Font is not loaded."); 95 | } 96 | 97 | return FontInfo { 98 | m_ftFace->ascender, 99 | m_ftFace->height, 100 | (quint16)m_ftFace->num_glyphs, // TrueType allows only u16. 101 | }; 102 | } 103 | 104 | Glyph FreeTypeFont::outline(const quint16 gid) const 105 | { 106 | if (!isOpen()) { 107 | throw tr("Font is not loaded."); 108 | } 109 | 110 | auto error = FT_Load_Glyph(m_ftFace, gid, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP); 111 | if (error) { 112 | throw tr("Failed to load a glyph.\n%1").arg(getErrorMessage(error)); 113 | } 114 | 115 | Outliner outliner; 116 | 117 | FT_Outline_Funcs funcs; 118 | funcs.move_to = outliner.moveToFn; 119 | funcs.line_to = outliner.lineToFn; 120 | funcs.conic_to = outliner.quadToFn; 121 | funcs.cubic_to = outliner.cubicToFn; 122 | funcs.shift = 0; 123 | funcs.delta = 0; 124 | 125 | auto slot = m_ftFace->glyph; 126 | auto &outline = slot->outline; 127 | 128 | // Flip outline around x-axis. 129 | FT_Matrix matrix; 130 | matrix.xx = 1L * MULTIPLIER_FT; 131 | matrix.xy = 0L * MULTIPLIER_FT; 132 | matrix.yx = 0L * MULTIPLIER_FT; 133 | matrix.yy = -1L * MULTIPLIER_FT; 134 | FT_Outline_Transform(&outline, &matrix); 135 | 136 | FT_BBox bboxFt; 137 | FT_Outline_Get_BBox(&outline, &bboxFt); 138 | 139 | const QRect bbox( 140 | (int)bboxFt.xMin, 141 | (int)bboxFt.yMin, 142 | (int)bboxFt.xMax - (int)bboxFt.xMin, 143 | (int)bboxFt.yMax - (int)bboxFt.yMin 144 | ); 145 | 146 | error = FT_Outline_Decompose(&outline, &funcs, &outliner); 147 | if (error) { 148 | throw tr("Failed to outline a glyph.\n%1").arg(getErrorMessage(error)); 149 | } 150 | 151 | outliner.path.setFillRule(Qt::WindingFill); 152 | 153 | return Glyph { 154 | outliner.path, 155 | bbox, 156 | }; 157 | } 158 | 159 | void FreeTypeFont::setVariations(const QVector &variations) 160 | { 161 | if (!isOpen()) { 162 | throw tr("Font is not loaded."); 163 | } 164 | 165 | QVector ftCoords; 166 | 167 | for (const auto &var : variations) { 168 | ftCoords << var.value * MULTIPLIER_FT; 169 | } 170 | 171 | const auto error = FT_Set_Var_Design_Coordinates(m_ftFace, ftCoords.size(), ftCoords.data()); 172 | if (error) { 173 | throw tr("Failed to set variation.\n%1").arg(getErrorMessage(error)); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /testing-tools/font-view/freetypefont.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include FT_FREETYPE_H 5 | #include FT_OUTLINE_H 6 | #include FT_BBOX_H 7 | #include FT_MULTIPLE_MASTERS_H 8 | 9 | #include 10 | 11 | #include "glyph.h" 12 | 13 | class FreeTypeFont 14 | { 15 | Q_DECLARE_TR_FUNCTIONS(FreeTypeFont) 16 | 17 | public: 18 | FreeTypeFont(); 19 | ~FreeTypeFont(); 20 | 21 | void open(const QString &path, const quint32 index = 0); 22 | bool isOpen() const; 23 | 24 | FontInfo fontInfo() const; 25 | Glyph outline(const quint16 gid) const; 26 | 27 | void setVariations(const QVector &variations); 28 | 29 | private: 30 | FT_Library m_ftLibrary = nullptr; 31 | FT_Face m_ftFace = nullptr; 32 | }; 33 | -------------------------------------------------------------------------------- /testing-tools/font-view/glyph.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Tag 6 | { 7 | Tag(quint32 v) : value(v) {} 8 | 9 | QString toString() const 10 | { 11 | QString s; 12 | s.append(QChar(value >> 24 & 0xff)); 13 | s.append(QChar(value >> 16 & 0xff)); 14 | s.append(QChar(value >> 8 & 0xff)); 15 | s.append(QChar(value >> 0 & 0xff)); 16 | return s; 17 | } 18 | 19 | quint32 value; 20 | }; 21 | 22 | struct FontInfo 23 | { 24 | qint16 ascender = 0; 25 | qint16 height = 1000; 26 | quint16 numberOfGlyphs = 0; 27 | }; 28 | 29 | struct Glyph 30 | { 31 | QPainterPath outline; 32 | QRect bbox; 33 | }; 34 | 35 | struct VariationInfo 36 | { 37 | QString name; 38 | Tag tag; 39 | qint16 min = 0; 40 | qint16 def = 0; 41 | qint16 max = 0; 42 | }; 43 | 44 | struct Variation 45 | { 46 | Tag tag; 47 | int value; 48 | }; 49 | -------------------------------------------------------------------------------- /testing-tools/font-view/glyphsview.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "glyph.h" 7 | 8 | class GlyphsView : public QAbstractScrollArea 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | explicit GlyphsView(QWidget *parent = nullptr); 14 | 15 | void setFontInfo(const FontInfo &fi); 16 | void setGlyph(int idx, const Glyph &glyph); 17 | #ifdef WITH_FREETYPE 18 | void setFTGlyph(int idx, const Glyph &glyph); 19 | #endif 20 | #ifdef WITH_HARFBUZZ 21 | void setHBGlyph(int idx, const Glyph &glyph); 22 | #endif 23 | 24 | void setDrawBboxes(const bool flag); 25 | void setDrawGlyphs(const bool flag); 26 | void setDrawFTGlyphs(const bool flag); 27 | void setDrawHBGlyphs(const bool flag); 28 | 29 | private: 30 | void paintEvent(QPaintEvent *); 31 | void drawGrid(QPainter &p, const double cellHeight); 32 | 33 | void mousePressEvent(QMouseEvent *e); 34 | void mouseMoveEvent(QMouseEvent *e); 35 | void mouseReleaseEvent(QMouseEvent *e); 36 | void wheelEvent(QWheelEvent *e); 37 | 38 | void resizeEvent(QResizeEvent *); 39 | 40 | void updateScrollBars(); 41 | 42 | private: 43 | QPoint m_mousePressPos; 44 | QPoint m_origOffset; 45 | 46 | double m_scale = 0.05; 47 | bool m_drawBboxes = true; 48 | bool m_drawGlyphs = true; 49 | bool m_drawFTGlyphs = false; 50 | bool m_drawHBGlyphs = false; 51 | 52 | FontInfo m_fontInfo; 53 | QVector m_glyphs; 54 | #ifdef WITH_FREETYPE 55 | QVector m_ftGlyphs; 56 | #endif 57 | #ifdef WITH_HARFBUZZ 58 | QVector m_hbGlyphs; 59 | #endif 60 | QVector m_indexes; 61 | }; 62 | -------------------------------------------------------------------------------- /testing-tools/font-view/harfbuzzfont.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "harfbuzzfont.h" 7 | 8 | struct Outliner 9 | { 10 | static void moveToFn(hb_position_t to_x, hb_position_t to_y, Outliner &outliner) 11 | { 12 | outliner.path.moveTo(to_x, to_y); 13 | } 14 | 15 | static void lineToFn(hb_position_t to_x, hb_position_t to_y, Outliner &outliner) 16 | { 17 | outliner.path.lineTo(to_x, to_y); 18 | } 19 | 20 | static void quadToFn(hb_position_t control_x, hb_position_t control_y, 21 | hb_position_t to_x, hb_position_t to_y, 22 | Outliner &outliner) 23 | { 24 | outliner.path.quadTo(control_x, control_y, to_x, to_y); 25 | } 26 | 27 | static void cubicToFn(hb_position_t control1_x, hb_position_t control1_y, 28 | hb_position_t control2_x, hb_position_t control2_y, 29 | hb_position_t to_x, hb_position_t to_y, 30 | Outliner &outliner) 31 | { 32 | outliner.path.cubicTo(control1_x, control1_y, control2_x, control2_y, to_x, to_y); 33 | } 34 | 35 | static void closePathFn(Outliner &outliner) 36 | { 37 | outliner.path.closeSubpath(); 38 | } 39 | 40 | QPainterPath path; 41 | }; 42 | 43 | HarfBuzzFont::HarfBuzzFont() 44 | { 45 | 46 | } 47 | 48 | HarfBuzzFont::~HarfBuzzFont() 49 | { 50 | reset(); 51 | } 52 | 53 | void HarfBuzzFont::open(const QString &path, const quint32 index) 54 | { 55 | if (isOpen()) { 56 | reset(); 57 | } 58 | 59 | const auto utf8Path = path.toUtf8(); 60 | hb_blob_t *blob = hb_blob_create_from_file(utf8Path.constData()); 61 | if (!blob) { 62 | throw tr("Failed to open a font."); 63 | } 64 | 65 | hb_face_t *face = hb_face_create(blob, index); 66 | if (!face) { 67 | throw tr("Failed to open a font."); 68 | } 69 | 70 | hb_font_t *font = hb_font_create(face); 71 | if (!font) { 72 | throw tr("Failed to open a font."); 73 | } 74 | 75 | m_blob = blob; 76 | m_face = face; 77 | m_font = font; 78 | } 79 | 80 | bool HarfBuzzFont::isOpen() const 81 | { 82 | return m_font != nullptr; 83 | } 84 | 85 | Glyph HarfBuzzFont::outline(const quint16 gid) const 86 | { 87 | if (!isOpen()) { 88 | throw tr("Font is not loaded."); 89 | } 90 | 91 | Outliner outliner; 92 | 93 | hb_draw_funcs_t *funcs = hb_draw_funcs_create(); 94 | hb_draw_funcs_set_move_to_func(funcs, (hb_draw_move_to_func_t)outliner.moveToFn); 95 | hb_draw_funcs_set_line_to_func(funcs, (hb_draw_line_to_func_t)outliner.lineToFn); 96 | hb_draw_funcs_set_quadratic_to_func(funcs, (hb_draw_quadratic_to_func_t)outliner.quadToFn); 97 | hb_draw_funcs_set_cubic_to_func(funcs, (hb_draw_cubic_to_func_t)outliner.cubicToFn); 98 | hb_draw_funcs_set_close_path_func(funcs, (hb_draw_close_path_func_t)outliner.closePathFn); 99 | 100 | if (!hb_font_draw_glyph(m_font, gid, funcs, &outliner)) { 101 | throw tr("Failed to outline a glyph %1.").arg(gid); 102 | } 103 | 104 | hb_draw_funcs_destroy(funcs); 105 | 106 | hb_glyph_extents_t extents = {0, 0, 0, 0}; 107 | if (!hb_font_get_glyph_extents(m_font, gid, &extents)) { 108 | throw tr("Failed to query glyph extents."); 109 | } 110 | 111 | const QRect bbox( 112 | extents.x_bearing, 113 | -extents.y_bearing, 114 | extents.width, 115 | -extents.height 116 | ); 117 | 118 | // Flip outline around x-axis. 119 | QTransform ts(1, 0, 0, -1, 0, 0); 120 | outliner.path = ts.map(outliner.path); 121 | 122 | outliner.path.setFillRule(Qt::WindingFill); 123 | 124 | return Glyph { 125 | outliner.path, 126 | bbox, 127 | }; 128 | } 129 | 130 | void HarfBuzzFont::setVariations(const QVector &variations) 131 | { 132 | if (!isOpen()) { 133 | throw tr("Font is not loaded."); 134 | } 135 | 136 | QVector hbVariations; 137 | for (const auto &var : variations) { 138 | hbVariations.append({ var.tag.value, (float)var.value }); 139 | } 140 | 141 | hb_font_set_variations(m_font, hbVariations.constData(), hbVariations.size()); 142 | } 143 | 144 | void HarfBuzzFont::reset() 145 | { 146 | if (m_blob) { 147 | hb_blob_destroy(m_blob); 148 | m_blob = nullptr; 149 | } 150 | 151 | if (m_font) { 152 | hb_font_destroy(m_font); 153 | m_font = nullptr; 154 | } 155 | 156 | if (m_face) { 157 | hb_face_destroy(m_face); 158 | m_face = nullptr; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /testing-tools/font-view/harfbuzzfont.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "glyph.h" 6 | 7 | struct hb_blob_t; 8 | struct hb_face_t; 9 | struct hb_font_t; 10 | struct hb_draw_funcs_t; 11 | 12 | class HarfBuzzFont 13 | { 14 | Q_DECLARE_TR_FUNCTIONS(HarfBuzzFont) 15 | 16 | public: 17 | HarfBuzzFont(); 18 | ~HarfBuzzFont(); 19 | 20 | void open(const QString &path, const quint32 index = 0); 21 | bool isOpen() const; 22 | 23 | Glyph outline(const quint16 gid) const; 24 | 25 | void setVariations(const QVector &variations); 26 | 27 | private: 28 | void reset(); 29 | 30 | private: 31 | hb_blob_t *m_blob = nullptr; 32 | hb_face_t *m_face = nullptr; 33 | hb_font_t *m_font = nullptr; 34 | }; 35 | -------------------------------------------------------------------------------- /testing-tools/font-view/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "mainwindow.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | 9 | MainWindow w; 10 | w.show(); 11 | 12 | return a.exec(); 13 | } 14 | -------------------------------------------------------------------------------- /testing-tools/font-view/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "mainwindow.h" 8 | #include "ui_mainwindow.h" 9 | 10 | MainWindow::MainWindow(QWidget *parent) 11 | : QMainWindow(parent) 12 | , ui(new Ui::MainWindow) 13 | { 14 | ui->setupUi(this); 15 | 16 | #ifndef WITH_FREETYPE 17 | ui->chBoxDrawFreeType->hide(); 18 | #endif 19 | 20 | #ifndef WITH_HARFBUZZ 21 | ui->chBoxDrawHarfBuzz->hide(); 22 | #endif 23 | 24 | if (qApp->arguments().size() == 2) { 25 | QTimer::singleShot(1, this, [this](){ 26 | loadFont(qApp->arguments().at(1)); 27 | }); 28 | } 29 | } 30 | 31 | MainWindow::~MainWindow() 32 | { 33 | delete ui; 34 | } 35 | 36 | void MainWindow::loadFont(const QString &path) 37 | { 38 | try { 39 | m_ttfpFont.open(path); 40 | 41 | const auto variations = m_ttfpFont.loadVariations(); 42 | if (!variations.isEmpty()) { 43 | ui->widgetVariations->show(); 44 | 45 | // Clear layout. 46 | while (ui->layVariations->count()) { 47 | delete ui->layVariations->takeAt(0); 48 | } 49 | 50 | m_variationSliders.clear(); 51 | 52 | QVector newVariations; 53 | 54 | for (const auto &var : variations) { 55 | auto hlay = new QHBoxLayout(); 56 | hlay->setContentsMargins(0, 0, 0, 0); 57 | hlay->addWidget(new QLabel(var.name)); 58 | 59 | auto slider = new QSlider(Qt::Horizontal); 60 | slider->setMinimum(var.min); 61 | slider->setMaximum(var.max); 62 | slider->setValue(var.def); 63 | hlay->addWidget(slider); 64 | ui->layVariations->addLayout(hlay); 65 | 66 | m_variationSliders.append({ slider, var.tag }); 67 | 68 | connect(slider, &QSlider::valueChanged, this, &MainWindow::onVariationChanged); 69 | 70 | newVariations.append({ var.tag, var.def }); 71 | } 72 | 73 | m_ttfpFont.setVariations(newVariations); 74 | } else { 75 | ui->widgetVariations->hide(); 76 | } 77 | 78 | #ifdef WITH_FREETYPE 79 | m_ftFont.open(path); 80 | #endif 81 | 82 | #ifdef WITH_HARFBUZZ 83 | m_hbFont.open(path); 84 | #endif 85 | 86 | ui->glyphsView->setFontInfo(m_ttfpFont.fontInfo()); 87 | reloadGlyphs(); 88 | } catch (const QString &err) { 89 | QMessageBox::warning(this, tr("Error"), err); 90 | } 91 | } 92 | 93 | void MainWindow::reloadGlyphs() 94 | { 95 | const auto fi = m_ttfpFont.fontInfo(); 96 | for (quint16 i = 0; i < fi.numberOfGlyphs; ++i) { 97 | try { 98 | ui->glyphsView->setGlyph(i, m_ttfpFont.outline(i)); 99 | } catch (...) { 100 | } 101 | 102 | #ifdef WITH_FREETYPE 103 | try { 104 | ui->glyphsView->setFTGlyph(i, m_ftFont.outline(i)); 105 | } catch (...) { 106 | } 107 | #endif 108 | 109 | #ifdef WITH_HARFBUZZ 110 | try { 111 | ui->glyphsView->setHBGlyph(i, m_hbFont.outline(i)); 112 | } catch (...) { 113 | } 114 | #endif 115 | } 116 | 117 | ui->glyphsView->viewport()->update(); 118 | } 119 | 120 | void MainWindow::onVariationChanged() 121 | { 122 | try { 123 | QVector variations; 124 | 125 | for (auto var : m_variationSliders) { 126 | variations.append({ var.tag, var.slider->value() }); 127 | } 128 | 129 | #ifdef WITH_FREETYPE 130 | m_ftFont.setVariations(variations); 131 | #endif 132 | 133 | #ifdef WITH_HARFBUZZ 134 | m_hbFont.setVariations(variations); 135 | #endif 136 | m_ttfpFont.setVariations(variations); 137 | 138 | reloadGlyphs(); 139 | } catch (const QString &err) { 140 | QMessageBox::warning(this, tr("Error"), err); 141 | } 142 | } 143 | 144 | void MainWindow::on_chBoxDrawBboxes_stateChanged(int flag) 145 | { 146 | ui->glyphsView->setDrawBboxes(flag); 147 | } 148 | 149 | void MainWindow::on_chBoxDrawTtfParser_stateChanged(int flag) 150 | { 151 | ui->glyphsView->setDrawGlyphs(flag); 152 | } 153 | 154 | void MainWindow::on_chBoxDrawFreeType_stateChanged(int flag) 155 | { 156 | ui->glyphsView->setDrawFTGlyphs(flag); 157 | } 158 | 159 | void MainWindow::on_chBoxDrawHarfBuzz_stateChanged(int flag) 160 | { 161 | ui->glyphsView->setDrawHBGlyphs(flag); 162 | } 163 | -------------------------------------------------------------------------------- /testing-tools/font-view/mainwindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef WITH_FREETYPE 6 | #include "freetypefont.h" 7 | #endif 8 | 9 | #ifdef WITH_FREETYPE 10 | #include "harfbuzzfont.h" 11 | #endif 12 | 13 | #include "ttfparserfont.h" 14 | 15 | namespace Ui { class MainWindow; } 16 | 17 | class QSlider; 18 | 19 | class MainWindow : public QMainWindow 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | MainWindow(QWidget *parent = nullptr); 25 | ~MainWindow(); 26 | 27 | private: 28 | void loadFont(const QString &path); 29 | void reloadGlyphs(); 30 | void onVariationChanged(); 31 | 32 | private slots: 33 | void on_chBoxDrawBboxes_stateChanged(int flag); 34 | void on_chBoxDrawTtfParser_stateChanged(int flag); 35 | void on_chBoxDrawFreeType_stateChanged(int flag); 36 | void on_chBoxDrawHarfBuzz_stateChanged(int flag); 37 | 38 | private: 39 | struct VariationSlider 40 | { 41 | QSlider *slider; 42 | Tag tag; 43 | }; 44 | 45 | Ui::MainWindow * const ui; 46 | QVector m_variationSliders; 47 | TtfParserFont m_ttfpFont; 48 | #ifdef WITH_FREETYPE 49 | FreeTypeFont m_ftFont; 50 | #endif 51 | #ifdef WITH_HARFBUZZ 52 | HarfBuzzFont m_hbFont; 53 | #endif 54 | }; 55 | -------------------------------------------------------------------------------- /testing-tools/font-view/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 600 11 | 12 | 13 | 14 | FontView 15 | 16 | 17 | 18 | 19 | 3 20 | 21 | 22 | 3 23 | 24 | 25 | 3 26 | 27 | 28 | 3 29 | 30 | 31 | 32 | 33 | 34 | 0 35 | 0 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 0 45 | 0 46 | 47 | 48 | 49 | 50 | 200 51 | 0 52 | 53 | 54 | 55 | 56 | 57 | 58 | Draw ttf-parser 59 | 60 | 61 | true 62 | 63 | 64 | 65 | 66 | 67 | 68 | Draw FreeType 69 | 70 | 71 | 72 | 73 | 74 | 75 | Draw HarfBuzz 76 | 77 | 78 | 79 | 80 | 81 | 82 | Draw bboxes 83 | 84 | 85 | true 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 0 94 | 95 | 96 | 0 97 | 98 | 99 | 0 100 | 101 | 102 | 0 103 | 104 | 105 | 106 | 107 | Variations: 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | Qt::Vertical 121 | 122 | 123 | 124 | 20 125 | 40 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 0 139 | 0 140 | 800 141 | 32 142 | 143 | 144 | 145 | 146 | 147 | 148 | GlyphsView 149 | QWidget 150 |
glyphsview.h
151 | 1 152 |
153 |
154 | 155 | 156 |
157 | -------------------------------------------------------------------------------- /testing-tools/font-view/ttfparserfont.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "ttfparserfont.h" 6 | 7 | struct Outliner 8 | { 9 | static void moveToFn(float x, float y, void *user) 10 | { 11 | auto self = static_cast(user); 12 | self->path.moveTo(double(x), double(y)); 13 | } 14 | 15 | static void lineToFn(float x, float y, void *user) 16 | { 17 | auto self = static_cast(user); 18 | self->path.lineTo(double(x), double(y)); 19 | } 20 | 21 | static void quadToFn(float x1, float y1, float x, float y, void *user) 22 | { 23 | auto self = static_cast(user); 24 | self->path.quadTo(double(x1), double(y1), double(x), double(y)); 25 | } 26 | 27 | static void curveToFn(float x1, float y1, float x2, float y2, float x, float y, void *user) 28 | { 29 | auto self = static_cast(user); 30 | self->path.cubicTo(double(x1), double(y1), double(x2), double(y2), double(x), double(y)); 31 | } 32 | 33 | static void closePathFn(void *user) 34 | { 35 | auto self = static_cast(user); 36 | self->path.closeSubpath(); 37 | } 38 | 39 | QPainterPath path; 40 | }; 41 | 42 | TtfParserFont::TtfParserFont() 43 | { 44 | } 45 | 46 | void TtfParserFont::open(const QString &path, const quint32 index) 47 | { 48 | if (isOpen()) { 49 | m_face.reset(); 50 | } 51 | 52 | QFile file(path); 53 | file.open(QFile::ReadOnly); 54 | m_fontData = file.readAll(); 55 | 56 | m_face.reset((ttfp_face*)malloc(ttfp_face_size_of())); 57 | const auto res = ttfp_face_init(m_fontData.constData(), m_fontData.size(), index, m_face.get()); 58 | 59 | if (!res) { 60 | throw tr("Failed to open a font."); 61 | } 62 | } 63 | 64 | bool TtfParserFont::isOpen() const 65 | { 66 | return m_face != nullptr; 67 | } 68 | 69 | FontInfo TtfParserFont::fontInfo() const 70 | { 71 | if (!isOpen()) { 72 | throw tr("Font is not loaded."); 73 | } 74 | 75 | return FontInfo { 76 | ttfp_get_ascender(m_face.get()), 77 | ttfp_get_height(m_face.get()), 78 | ttfp_get_number_of_glyphs(m_face.get()), 79 | }; 80 | } 81 | 82 | Glyph TtfParserFont::outline(const quint16 gid) const 83 | { 84 | if (!isOpen()) { 85 | throw tr("Font is not loaded."); 86 | } 87 | 88 | Outliner outliner; 89 | ttfp_outline_builder builder; 90 | builder.move_to = outliner.moveToFn; 91 | builder.line_to = outliner.lineToFn; 92 | builder.quad_to = outliner.quadToFn; 93 | builder.curve_to = outliner.curveToFn; 94 | builder.close_path = outliner.closePathFn; 95 | 96 | ttfp_rect rawBbox; 97 | 98 | const bool ok = ttfp_outline_glyph( 99 | m_face.get(), 100 | builder, 101 | &outliner, 102 | gid, 103 | &rawBbox 104 | ); 105 | 106 | if (!ok) { 107 | return Glyph { 108 | QPainterPath(), 109 | QRect(), 110 | }; 111 | } 112 | 113 | const QRect bbox( 114 | rawBbox.x_min, 115 | -rawBbox.y_max, 116 | rawBbox.x_max - rawBbox.x_min, 117 | rawBbox.y_max - rawBbox.y_min 118 | ); 119 | 120 | // Flip outline around x-axis. 121 | QTransform ts(1, 0, 0, -1, 0, 0); 122 | outliner.path = ts.map(outliner.path); 123 | 124 | outliner.path.setFillRule(Qt::WindingFill); 125 | 126 | return Glyph { 127 | outliner.path, 128 | bbox, 129 | }; 130 | } 131 | 132 | QVector TtfParserFont::loadVariations() 133 | { 134 | if (!isOpen()) { 135 | throw tr("Font is not loaded."); 136 | } 137 | 138 | QVector variations; 139 | 140 | for (uint16_t i = 0; i < ttfp_get_variation_axes_count(m_face.get()); ++i) { 141 | ttfp_variation_axis axis; 142 | ttfp_get_variation_axis(m_face.get(), i, &axis); 143 | 144 | variations.append(VariationInfo { 145 | Tag(axis.tag).toString(), 146 | { static_cast(axis.tag) }, 147 | static_cast(axis.min_value), 148 | static_cast(axis.def_value), 149 | static_cast(axis.max_value), 150 | }); 151 | } 152 | 153 | return variations; 154 | } 155 | 156 | void TtfParserFont::setVariations(const QVector &variations) 157 | { 158 | if (!isOpen()) { 159 | throw tr("Font is not loaded."); 160 | } 161 | 162 | for (const auto &variation : variations) { 163 | ttfp_set_variation(m_face.get(), variation.tag.value, variation.value); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /testing-tools/font-view/ttfparserfont.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #define TTFP_VARIABLE_FONTS 9 | #include 10 | 11 | #include "glyph.h" 12 | 13 | class TtfParserFont 14 | { 15 | Q_DECLARE_TR_FUNCTIONS(TtfParserFont) 16 | 17 | public: 18 | TtfParserFont(); 19 | 20 | void open(const QString &path, const quint32 index = 0); 21 | bool isOpen() const; 22 | 23 | FontInfo fontInfo() const; 24 | Glyph outline(const quint16 gid) const; 25 | 26 | QVector loadVariations(); 27 | void setVariations(const QVector &variations); 28 | 29 | private: 30 | struct FreeCPtr 31 | { void operator()(void* x) { free(x); } }; 32 | 33 | QByteArray m_fontData; 34 | std::unique_ptr m_face; 35 | }; 36 | -------------------------------------------------------------------------------- /testing-tools/ttf-fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | in 2 | out 3 | -------------------------------------------------------------------------------- /testing-tools/ttf-fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ttf-fuzz" 3 | version = "0.1.0" 4 | authors = ["Yevhenii Reizner "] 5 | edition = "2018" 6 | 7 | # Prevent this from interfering with workspaces 8 | [workspace] 9 | members = ["."] 10 | 11 | [dependencies] 12 | afl = "0.7" 13 | ttf-parser = { path = "../.." } 14 | 15 | [[bin]] 16 | name = "fuzz-outline" 17 | path = "src/fuzz-outline.rs" 18 | 19 | [[bin]] 20 | name = "fuzz-glyph-index" 21 | path = "src/fuzz-glyph-index.rs" 22 | 23 | [[bin]] 24 | name = "fuzz-variable-outline" 25 | path = "src/fuzz-variable-outline.rs" 26 | -------------------------------------------------------------------------------- /testing-tools/ttf-fuzz/README.md: -------------------------------------------------------------------------------- 1 | ## Build 2 | 3 | Install AFL first: 4 | 5 | ``` 6 | cargo install afl 7 | ``` 8 | 9 | and then build via `cargo-afl`: 10 | 11 | ``` 12 | cargo afl build 13 | ``` 14 | 15 | ## Run 16 | 17 | Before running, we have to collect some test data. 18 | Using raw fonts is too wasteful, so we are using the `strip-tables.py` script 19 | to remove unneeded tables. 20 | 21 | Here is an example to test `cmap`/`Face::glyph_index`: 22 | 23 | ``` 24 | strip-tables.py glyph-index in /usr/share/fonts 25 | cargo afl fuzz -i in -o out target/debug/fuzz-glyph-index 26 | ``` 27 | -------------------------------------------------------------------------------- /testing-tools/ttf-fuzz/src/fuzz-glyph-index.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate afl; 3 | 4 | const CHARS: &[char] = &[ 5 | '\u{0}', 6 | 'A', 7 | 'Ф', 8 | '0', 9 | '\u{D7FF}', 10 | '\u{10FFFF}', 11 | ]; 12 | 13 | fn main() { 14 | afl::fuzz!(|data: &[u8]| { 15 | if let Some(face) = ttf_parser::Face::parse(data, 0) { 16 | for c in CHARS { 17 | let _ = face.glyph_index(*c); 18 | } 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /testing-tools/ttf-fuzz/src/fuzz-outline.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate afl; 3 | 4 | fn main() { 5 | afl::fuzz!(|data: &[u8]| { 6 | if let Some(face) = ttf_parser::Face::parse(data, 0) { 7 | for id in 0..face.number_of_glyphs() { 8 | let _ = face.outline_glyph(ttf_parser::GlyphId(id), &mut Builder(0)); 9 | } 10 | } 11 | }); 12 | } 13 | 14 | 15 | struct Builder(usize); 16 | 17 | impl ttf_parser::OutlineBuilder for Builder { 18 | #[inline] 19 | fn move_to(&mut self, _: f32, _: f32) { 20 | self.0 += 1; 21 | } 22 | 23 | #[inline] 24 | fn line_to(&mut self, _: f32, _: f32) { 25 | self.0 += 1; 26 | } 27 | 28 | #[inline] 29 | fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) { 30 | self.0 += 2; 31 | } 32 | 33 | #[inline] 34 | fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) { 35 | self.0 += 3; 36 | } 37 | 38 | #[inline] 39 | fn close(&mut self) { 40 | self.0 += 1; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /testing-tools/ttf-fuzz/src/fuzz-variable-outline.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate afl; 3 | 4 | fn main() { 5 | afl::fuzz!(|data: &[u8]| { 6 | if let Some(mut face) = ttf_parser::Face::parse(data, 0) { 7 | if face.set_variation(ttf_parser::Tag::from_bytes(b"wght"), 500.0).is_some() { 8 | for id in 0..face.number_of_glyphs() { 9 | let _ = face.outline_glyph(ttf_parser::GlyphId(id), &mut Builder(0)); 10 | } 11 | } 12 | } 13 | }); 14 | } 15 | 16 | 17 | struct Builder(usize); 18 | 19 | impl ttf_parser::OutlineBuilder for Builder { 20 | #[inline] 21 | fn move_to(&mut self, _: f32, _: f32) { 22 | self.0 += 1; 23 | } 24 | 25 | #[inline] 26 | fn line_to(&mut self, _: f32, _: f32) { 27 | self.0 += 1; 28 | } 29 | 30 | #[inline] 31 | fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) { 32 | self.0 += 2; 33 | } 34 | 35 | #[inline] 36 | fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) { 37 | self.0 += 3; 38 | } 39 | 40 | #[inline] 41 | fn close(&mut self) { 42 | self.0 += 1; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /testing-tools/ttf-fuzz/strip-tables.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # TODO: support ttc 4 | 5 | import argparse 6 | import io 7 | import os 8 | from pathlib import Path 9 | from uuid import uuid4 10 | 11 | parser = argparse.ArgumentParser(description='Strip TrueType tables.') 12 | parser.add_argument('target', metavar='TARGET', 13 | choices=[ 14 | 'glyf-outline', 15 | 'gvar-outline', 16 | 'cff-outline', 17 | 'cff2-outline', 18 | 'glyph-index', 19 | ], 20 | help='fuzz target') 21 | parser.add_argument('out_dir', metavar='DIR', type=Path, nargs='?', help='output dir') 22 | parser.add_argument('in_dirs', metavar='DIRS', type=Path, nargs='+', help='font dirs') 23 | args = parser.parse_args() 24 | 25 | if args.out_dir.exists(): 26 | print('Error: Output directory already exists.') 27 | exit(1) 28 | 29 | os.mkdir(args.out_dir) 30 | 31 | required_tables = ['head', 'hhea', 'maxp'] 32 | if args.target == 'glyf-outline': 33 | required_tables.extend(['loca', 'glyf']) 34 | elif args.target == 'gvar-outline': 35 | required_tables.extend(['loca', 'glyf', 'gvar', 'fvar']) 36 | elif args.target == 'cff-outline': 37 | required_tables.append('CFF ') 38 | elif args.target == 'cff2-outline': 39 | required_tables.extend(['CFF2', 'fvar']) 40 | elif args.target == 'glyph-index': 41 | required_tables.append('cmap') 42 | 43 | fonts = [] 44 | for dir in args.in_dirs: 45 | for root, _, files in os.walk(dir): 46 | for file in files: 47 | if file.endswith('ttf') or file.endswith('otf'): 48 | fonts.append(Path(root).joinpath(file)) 49 | 50 | for orig_font_path in fonts: 51 | with open(orig_font_path, 'rb') as in_file: 52 | font_data = in_file.read() 53 | 54 | # Parse header 55 | old_data = io.BytesIO(font_data) 56 | old_data.read(4) # sfnt_version 57 | num_tables = int.from_bytes(old_data.read(2), byteorder='big', signed=False) 58 | old_data.read(6) # search_range + entry_selector + range_shift 59 | 60 | tables = [] 61 | 62 | # Parse Table Records 63 | for _ in range(0, num_tables): 64 | tag = old_data.read(4) 65 | old_data.read(4) # check_sum 66 | offset = int.from_bytes(old_data.read(4), byteorder='big', signed=False) 67 | length = int.from_bytes(old_data.read(4), byteorder='big', signed=False) 68 | if tag.decode('ascii') in required_tables: 69 | tables.append((tag, font_data[offset:offset+length])) 70 | 71 | if len(tables) != len(required_tables): 72 | continue 73 | 74 | new_data = io.BytesIO() 75 | new_data.write(b'\x00\x01\x00\x00') # magic 76 | new_data.write(int.to_bytes(len(tables), 2, byteorder='big', signed=False)) # number of tables 77 | new_data.write(b'\x00\x00\x00\x00\x00\x00') # we don't care about those bytes 78 | # Write Table Records 79 | offset = 12 + len(tables) * 16 80 | for (table_tag, table_data) in tables: 81 | new_data.write(table_tag) 82 | new_data.write(b'\x00\x00\x00\x00') # CRC, ttf-parser ignores it anyway 83 | new_data.write(int.to_bytes(offset, 4, byteorder='big', signed=False)) 84 | new_data.write(int.to_bytes(len(table_data), 4, byteorder='big', signed=False)) 85 | offset += len(table_data) 86 | 87 | for (_, table_data) in tables: 88 | new_data.write(table_data) 89 | 90 | with open(args.out_dir.joinpath(str(uuid4()) + '.ttf'), 'wb') as out_file: 91 | out_file.write(new_data.getbuffer()) 92 | -------------------------------------------------------------------------------- /tests/bitmap.rs: -------------------------------------------------------------------------------- 1 | use ttf_parser::{RasterGlyphImage, RasterImageFormat}; 2 | 3 | // NOTE: Bitmap.otb is an incomplete example font that was created specifically for this test. 4 | // It is under the same license as the other source files in the project. 5 | static FONT_DATA: &[u8] = include_bytes!("fonts/bitmap.otb"); 6 | 7 | #[test] 8 | fn bitmap_font() { 9 | let face = ttf_parser::Face::parse(FONT_DATA, 0).unwrap(); 10 | assert_eq!(face.units_per_em(), 800); 11 | assert_eq!( 12 | face.glyph_hor_advance(face.glyph_index('a').unwrap()), 13 | Some(500) 14 | ); 15 | const W: u8 = 0; 16 | const B: u8 = 255; 17 | assert_eq!( 18 | face.glyph_raster_image(face.glyph_index('a').unwrap(), 1), 19 | Some(RasterGlyphImage { 20 | x: 0, 21 | y: 0, 22 | width: 4, 23 | height: 4, 24 | pixels_per_em: 8, 25 | format: RasterImageFormat::BitmapGray8, 26 | #[rustfmt::skip] 27 | data: &[ 28 | W, B, B, B, 29 | B, W, W, B, 30 | B, W, W, B, 31 | W, B, B, B 32 | ] 33 | }) 34 | ); 35 | assert_eq!( 36 | face.glyph_raster_image(face.glyph_index('d').unwrap(), 1), 37 | Some(RasterGlyphImage { 38 | x: 0, 39 | y: 0, 40 | width: 4, 41 | height: 6, 42 | pixels_per_em: 8, 43 | format: RasterImageFormat::BitmapGray8, 44 | #[rustfmt::skip] 45 | data: &[ 46 | W, W, W, B, 47 | W, W, W, B, 48 | W, B, B, B, 49 | B, W, W, B, 50 | B, W, W, B, 51 | W, B, B, B 52 | ] 53 | }) 54 | ); 55 | assert_eq!( 56 | face.glyph_raster_image(face.glyph_index('\"').unwrap(), 1), 57 | Some(RasterGlyphImage { 58 | x: 1, 59 | y: 4, 60 | width: 3, 61 | height: 2, 62 | pixels_per_em: 8, 63 | format: RasterImageFormat::BitmapGray8, 64 | #[rustfmt::skip] 65 | data: &[ 66 | B, W, B, 67 | B, W, B, 68 | ] 69 | }) 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /tests/fonts/bitmap.otb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harfbuzz/ttf-parser/a6813b4778f7513c207a389b4eab7e2369c20e70/tests/fonts/bitmap.otb -------------------------------------------------------------------------------- /tests/fonts/colr_1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harfbuzz/ttf-parser/a6813b4778f7513c207a389b4eab7e2369c20e70/tests/fonts/colr_1.ttf -------------------------------------------------------------------------------- /tests/fonts/colr_1_variable.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harfbuzz/ttf-parser/a6813b4778f7513c207a389b4eab7e2369c20e70/tests/fonts/colr_1_variable.ttf -------------------------------------------------------------------------------- /tests/fonts/demo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harfbuzz/ttf-parser/a6813b4778f7513c207a389b4eab7e2369c20e70/tests/fonts/demo.ttf -------------------------------------------------------------------------------- /tests/fonts/demo.ttx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /tests/tables/ankr.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroU16; 2 | use ttf_parser::GlyphId; 3 | use ttf_parser::ankr::{Table, Point}; 4 | use crate::{convert, Unit::*}; 5 | 6 | #[test] 7 | fn empty() { 8 | let data = convert(&[ 9 | UInt16(0), // version 10 | UInt16(0), // reserved 11 | UInt32(0), // offset to lookup table 12 | UInt32(0), // offset to glyphs data 13 | ]); 14 | 15 | let _ = Table::parse(NonZeroU16::new(1).unwrap(), &data).unwrap(); 16 | } 17 | 18 | #[test] 19 | fn single() { 20 | let data = convert(&[ 21 | UInt16(0), // version 22 | UInt16(0), // reserved 23 | UInt32(12), // offset to lookup table 24 | UInt32(12 + 16), // offset to glyphs data 25 | 26 | // Lookup Table 27 | UInt16(6), // format 28 | 29 | // Binary Search Table 30 | UInt16(4), // segment size 31 | UInt16(1), // number of segments 32 | UInt16(0), // search range: we don't use it 33 | UInt16(0), // entry selector: we don't use it 34 | UInt16(0), // range shift: we don't use it 35 | 36 | // Segment [0] 37 | UInt16(0), // glyph 38 | UInt16(0), // offset 39 | 40 | // Glyphs Data 41 | UInt32(1), // number of points 42 | // Point [0] 43 | Int16(-5), // x 44 | Int16(11), // y 45 | ]); 46 | 47 | let table = Table::parse(NonZeroU16::new(1).unwrap(), &data).unwrap(); 48 | let points = table.points(GlyphId(0)).unwrap(); 49 | assert_eq!(points.get(0).unwrap(), Point { x: -5, y: 11 }); 50 | } 51 | 52 | #[test] 53 | fn two_points() { 54 | let data = convert(&[ 55 | UInt16(0), // version 56 | UInt16(0), // reserved 57 | UInt32(12), // offset to lookup table 58 | UInt32(12 + 16), // offset to glyphs data 59 | 60 | // Lookup Table 61 | UInt16(6), // format 62 | 63 | // Binary Search Table 64 | UInt16(4), // segment size 65 | UInt16(1), // number of segments 66 | UInt16(0), // search range: we don't use it 67 | UInt16(0), // entry selector: we don't use it 68 | UInt16(0), // range shift: we don't use it 69 | 70 | // Segment [0] 71 | UInt16(0), // glyph 72 | UInt16(0), // offset 73 | 74 | // Glyphs Data 75 | // Glyph Data [0] 76 | UInt32(2), // number of points 77 | // Point [0] 78 | Int16(-5), // x 79 | Int16(11), // y 80 | // Point [1] 81 | Int16(10), // x 82 | Int16(-40), // y 83 | ]); 84 | 85 | let table = Table::parse(NonZeroU16::new(1).unwrap(), &data).unwrap(); 86 | let points = table.points(GlyphId(0)).unwrap(); 87 | assert_eq!(points.get(0).unwrap(), Point { x: -5, y: 11 }); 88 | assert_eq!(points.get(1).unwrap(), Point { x: 10, y: -40 }); 89 | } 90 | 91 | #[test] 92 | fn two_glyphs() { 93 | let data = convert(&[ 94 | UInt16(0), // version 95 | UInt16(0), // reserved 96 | UInt32(12), // offset to lookup table 97 | UInt32(12 + 20), // offset to glyphs data 98 | 99 | // Lookup Table 100 | UInt16(6), // format 101 | 102 | // Binary Search Table 103 | UInt16(4), // segment size 104 | UInt16(2), // number of segments 105 | UInt16(0), // search range: we don't use it 106 | UInt16(0), // entry selector: we don't use it 107 | UInt16(0), // range shift: we don't use it 108 | 109 | // Segment [0] 110 | UInt16(0), // glyph 111 | UInt16(0), // offset 112 | // Segment [1] 113 | UInt16(1), // glyph 114 | UInt16(8), // offset 115 | 116 | // Glyphs Data 117 | // Glyph Data [0] 118 | UInt32(1), // number of points 119 | // Point [0] 120 | Int16(-5), // x 121 | Int16(11), // y 122 | // Glyph Data [1] 123 | UInt32(1), // number of points 124 | // Point [0] 125 | Int16(40), // x 126 | Int16(10), // y 127 | ]); 128 | 129 | let table = Table::parse(NonZeroU16::new(1).unwrap(), &data).unwrap(); 130 | let points = table.points(GlyphId(0)).unwrap(); 131 | assert_eq!(points.get(0).unwrap(), Point { x: -5, y: 11 }); 132 | let points = table.points(GlyphId(1)).unwrap(); 133 | assert_eq!(points.get(0).unwrap(), Point { x: 40, y: 10 }); 134 | } 135 | -------------------------------------------------------------------------------- /tests/tables/feat.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::bool_assert_comparison)] 2 | 3 | use ttf_parser::feat::Table; 4 | use crate::{convert, Unit::*}; 5 | 6 | #[test] 7 | fn basic() { 8 | let data = convert(&[ 9 | Fixed(1.0), // version 10 | UInt16(4), // number of features 11 | UInt16(0), // reserved 12 | UInt32(0), // reserved 13 | 14 | // Feature Name [0] 15 | UInt16(0), // feature 16 | UInt16(1), // number of settings 17 | UInt32(60), // offset to settings table 18 | UInt16(0), // flags: none 19 | UInt16(260), // name index 20 | 21 | // Feature Name [1] 22 | UInt16(1), // feature 23 | UInt16(1), // number of settings 24 | UInt32(64), // offset to settings table 25 | UInt16(0), // flags: none 26 | UInt16(256), // name index 27 | 28 | // Feature Name [2] 29 | UInt16(3), // feature 30 | UInt16(3), // number of settings 31 | UInt32(68), // offset to settings table 32 | Raw(&[0x80, 0x00]), // flags: exclusive 33 | UInt16(262), // name index 34 | 35 | // Feature Name [3] 36 | UInt16(6), // feature 37 | UInt16(2), // number of settings 38 | UInt32(80), // offset to settings table 39 | Raw(&[0xC0, 0x01]), // flags: exclusive and other 40 | UInt16(258), // name index 41 | 42 | // Setting Name [0] 43 | UInt16(0), // setting 44 | UInt16(261), // name index 45 | 46 | // Setting Name [1] 47 | UInt16(2), // setting 48 | UInt16(257), // name index 49 | 50 | // Setting Name [2] 51 | UInt16(0), // setting 52 | UInt16(268), // name index 53 | UInt16(3), // setting 54 | UInt16(264), // name index 55 | UInt16(4), // setting 56 | UInt16(265), // name index 57 | 58 | // Setting Name [3] 59 | UInt16(0), // setting 60 | UInt16(259), // name index 61 | UInt16(1), // setting 62 | UInt16(260), // name index 63 | ]); 64 | 65 | let table = Table::parse(&data).unwrap(); 66 | assert_eq!(table.names.len(), 4); 67 | 68 | let feature0 = table.names.get(0).unwrap(); 69 | assert_eq!(feature0.feature, 0); 70 | assert_eq!(feature0.setting_names.len(), 1); 71 | assert_eq!(feature0.exclusive, false); 72 | assert_eq!(feature0.name_index, 260); 73 | 74 | let feature2 = table.names.get(2).unwrap(); 75 | assert_eq!(feature2.feature, 3); 76 | assert_eq!(feature2.setting_names.len(), 3); 77 | assert_eq!(feature2.exclusive, true); 78 | 79 | assert_eq!(feature2.setting_names.get(1).unwrap().setting, 3); 80 | assert_eq!(feature2.setting_names.get(1).unwrap().name_index, 264); 81 | 82 | let feature3 = table.names.get(3).unwrap(); 83 | assert_eq!(feature3.default_setting_index, 1); 84 | assert_eq!(feature3.exclusive, true); 85 | } 86 | -------------------------------------------------------------------------------- /tests/tables/glyf.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | 3 | struct Builder(String); 4 | 5 | impl ttf_parser::OutlineBuilder for Builder { 6 | fn move_to(&mut self, x: f32, y: f32) { 7 | write!(&mut self.0, "M {} {} ", x, y).unwrap(); 8 | } 9 | 10 | fn line_to(&mut self, x: f32, y: f32) { 11 | write!(&mut self.0, "L {} {} ", x, y).unwrap(); 12 | } 13 | 14 | fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { 15 | write!(&mut self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap(); 16 | } 17 | 18 | fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { 19 | write!(&mut self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap(); 20 | } 21 | 22 | fn close(&mut self) { 23 | write!(&mut self.0, "Z ").unwrap(); 24 | } 25 | } 26 | 27 | #[test] 28 | fn endless_loop() { 29 | let data = b"\x00\x01\x00\x00\x00\x0f\x00\x10\x00PTT-W\x002h\xd7\x81x\x00\ 30 | \x00\x00?L\xbaN\x00c\x9a\x9e\x8f\x96\xe3\xfeu\xff\x00\xb2\x00@\x03\x00\xb8\ 31 | cvt 5:\x00\x00\x00\xb5\xf8\x01\x00\x03\x9ckEr\x92\xd7\xe6\x98M\xdc\x00\x00\ 32 | \x03\xe0\x00\x00\x00dglyf\"\t\x15`\x00\x00\x03\xe0\x00\x00\x00dglyf\"\t\x15\ 33 | `\x00\x00\x00 \x00\x00\x00\xfc\x97\x9fmx\x87\xc9\xc8\xfe\x00\x00\xbad\xff\ 34 | \xff\xf1\xc8head\xc7\x17\xce[\x00\x00\x00\xfc\x00\x00\x006hhea\x03\xc6\x05\ 35 | \xe4\x00\x00\x014\x00\x00\x00$hmtx\xc9\xfdq\xed\x00\x00\xb5\xf8\x01\x00\x03\ 36 | \x9ckEr\x92\xd7\xe6\xdch\x00\x00\xc9d\x00\x00\x04 loca\x00M\x82\x11\x00\x00\ 37 | \x00\x06\x00\x00\x00\xa0maxp\x17\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 name\ 38 | \xf4\xd6\xfe\xad\x00OTTO\x00\x02gpost5;5\xe1\x00\x00\xb0P\x00\x00\x01\xf0perp%\ 39 | \xb0{\x04\x93D\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\x00\x00\xe1!yf%1\ 40 | \x08\x95\x00\x00\x00\x00\x00\xaa\x06\x80fmtx\x02\x00\x00\x00\x00\x00\x00\x00\ 41 | \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ 42 | \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00a\xcc\xff\ 43 | \xce\x03CCCCCCCCC\x00\x00\x00\x00\x00C\x00\x00\x00\x00\xb5\xf8\x01\x00\x00\x9c"; 44 | 45 | let face = ttf_parser::Face::parse(data, 0).unwrap(); 46 | let _ = face.outline_glyph(ttf_parser::GlyphId(0), &mut Builder(String::new())); 47 | } 48 | -------------------------------------------------------------------------------- /tests/tables/hmtx.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroU16; 2 | use ttf_parser::GlyphId; 3 | use ttf_parser::hmtx::Table; 4 | use crate::{convert, Unit::*}; 5 | 6 | macro_rules! nzu16 { 7 | ($n:expr) => { NonZeroU16::new($n).unwrap() }; 8 | } 9 | 10 | #[test] 11 | fn simple_case() { 12 | let data = convert(&[ 13 | UInt16(1), // advance width [0] 14 | Int16(2), // side bearing [0] 15 | ]); 16 | 17 | let table = Table::parse(1, nzu16!(1), &data).unwrap(); 18 | assert_eq!(table.advance(GlyphId(0)), Some(1)); 19 | assert_eq!(table.side_bearing(GlyphId(0)), Some(2)); 20 | } 21 | 22 | #[test] 23 | fn empty() { 24 | assert!(Table::parse(1, nzu16!(1), &[]).is_none()); 25 | } 26 | 27 | #[test] 28 | fn zero_metrics() { 29 | let data = convert(&[ 30 | UInt16(1), // advance width [0] 31 | Int16(2), // side bearing [0] 32 | ]); 33 | 34 | assert!(Table::parse(0, nzu16!(1), &data).is_none()); 35 | } 36 | 37 | #[test] 38 | fn smaller_than_glyphs_count() { 39 | let data = convert(&[ 40 | UInt16(1), // advance width [0] 41 | Int16(2), // side bearing [0] 42 | 43 | Int16(3), // side bearing [1] 44 | ]); 45 | 46 | let table = Table::parse(1, nzu16!(2), &data).unwrap(); 47 | assert_eq!(table.advance(GlyphId(0)), Some(1)); 48 | assert_eq!(table.side_bearing(GlyphId(0)), Some(2)); 49 | assert_eq!(table.advance(GlyphId(1)), Some(1)); 50 | assert_eq!(table.side_bearing(GlyphId(1)), Some(3)); 51 | } 52 | 53 | #[test] 54 | fn no_additional_side_bearings() { 55 | let data = convert(&[ 56 | UInt16(1), // advance width [0] 57 | Int16(2), // side bearing [0] 58 | 59 | // A single side bearing should be present here. 60 | // We should simply ignore it and not return None during Table parsing. 61 | ]); 62 | 63 | let table = Table::parse(1, nzu16!(2), &data).unwrap(); 64 | assert_eq!(table.advance(GlyphId(0)), Some(1)); 65 | assert_eq!(table.side_bearing(GlyphId(0)), Some(2)); 66 | } 67 | 68 | #[test] 69 | fn less_metrics_than_glyphs() { 70 | let data = convert(&[ 71 | UInt16(1), // advance width [0] 72 | Int16(2), // side bearing [0] 73 | 74 | UInt16(3), // advance width [1] 75 | Int16(4), // side bearing [1] 76 | 77 | Int16(5), // side bearing [2] 78 | ]); 79 | 80 | let table = Table::parse(2, nzu16!(1), &data).unwrap(); 81 | assert_eq!(table.side_bearing(GlyphId(0)), Some(2)); 82 | assert_eq!(table.side_bearing(GlyphId(1)), Some(4)); 83 | assert_eq!(table.side_bearing(GlyphId(2)), None); 84 | } 85 | 86 | #[test] 87 | fn glyph_out_of_bounds_0() { 88 | let data = convert(&[ 89 | UInt16(1), // advance width [0] 90 | Int16(2), // side bearing [0] 91 | ]); 92 | 93 | let table = Table::parse(1, nzu16!(1), &data).unwrap(); 94 | assert_eq!(table.advance(GlyphId(0)), Some(1)); 95 | assert_eq!(table.side_bearing(GlyphId(0)), Some(2)); 96 | assert_eq!(table.advance(GlyphId(1)), None); 97 | assert_eq!(table.side_bearing(GlyphId(1)), None); 98 | } 99 | 100 | #[test] 101 | fn glyph_out_of_bounds_1() { 102 | let data = convert(&[ 103 | UInt16(1), // advance width [0] 104 | Int16(2), // side bearing [0] 105 | 106 | Int16(3), // side bearing [1] 107 | ]); 108 | 109 | let table = Table::parse(1, nzu16!(2), &data).unwrap(); 110 | assert_eq!(table.advance(GlyphId(1)), Some(1)); 111 | assert_eq!(table.side_bearing(GlyphId(1)), Some(3)); 112 | assert_eq!(table.advance(GlyphId(2)), None); 113 | assert_eq!(table.side_bearing(GlyphId(2)), None); 114 | } 115 | -------------------------------------------------------------------------------- /tests/tables/main.rs: -------------------------------------------------------------------------------- 1 | #[rustfmt::skip] mod aat; 2 | #[rustfmt::skip] mod ankr; 3 | #[rustfmt::skip] mod cff1; 4 | #[rustfmt::skip] mod cmap; 5 | #[rustfmt::skip] mod colr; 6 | #[rustfmt::skip] mod feat; 7 | #[rustfmt::skip] mod glyf; 8 | #[rustfmt::skip] mod hmtx; 9 | #[rustfmt::skip] mod maxp; 10 | #[rustfmt::skip] mod sbix; 11 | #[rustfmt::skip] mod trak; 12 | 13 | use ttf_parser::{fonts_in_collection, Face, FaceParsingError}; 14 | 15 | #[allow(dead_code)] 16 | #[derive(Clone, Copy)] 17 | pub enum Unit { 18 | Raw(&'static [u8]), 19 | Int8(i8), 20 | UInt8(u8), 21 | Int16(i16), 22 | UInt16(u16), 23 | Int32(i32), 24 | UInt32(u32), 25 | Fixed(f32), 26 | } 27 | 28 | pub fn convert(units: &[Unit]) -> Vec { 29 | let mut data = Vec::with_capacity(256); 30 | for v in units { 31 | convert_unit(*v, &mut data); 32 | } 33 | 34 | data 35 | } 36 | 37 | fn convert_unit(unit: Unit, data: &mut Vec) { 38 | match unit { 39 | Unit::Raw(bytes) => { 40 | data.extend_from_slice(bytes); 41 | } 42 | Unit::Int8(n) => { 43 | data.extend_from_slice(&i8::to_be_bytes(n)); 44 | } 45 | Unit::UInt8(n) => { 46 | data.extend_from_slice(&u8::to_be_bytes(n)); 47 | } 48 | Unit::Int16(n) => { 49 | data.extend_from_slice(&i16::to_be_bytes(n)); 50 | } 51 | Unit::UInt16(n) => { 52 | data.extend_from_slice(&u16::to_be_bytes(n)); 53 | } 54 | Unit::Int32(n) => { 55 | data.extend_from_slice(&i32::to_be_bytes(n)); 56 | } 57 | Unit::UInt32(n) => { 58 | data.extend_from_slice(&u32::to_be_bytes(n)); 59 | } 60 | Unit::Fixed(n) => { 61 | data.extend_from_slice(&i32::to_be_bytes((n * 65536.0) as i32)); 62 | } 63 | } 64 | } 65 | 66 | #[test] 67 | fn empty_font() { 68 | assert_eq!( 69 | Face::parse(&[], 0).unwrap_err(), 70 | FaceParsingError::UnknownMagic 71 | ); 72 | } 73 | 74 | #[test] 75 | fn zero_tables() { 76 | use Unit::*; 77 | let data = convert(&[ 78 | Raw(&[0x00, 0x01, 0x00, 0x00]), // magic 79 | UInt16(0), // numTables 80 | UInt16(0), // searchRange 81 | UInt16(0), // entrySelector 82 | UInt16(0), // rangeShift 83 | ]); 84 | 85 | assert_eq!( 86 | Face::parse(&data, 0).unwrap_err(), 87 | FaceParsingError::NoHeadTable 88 | ); 89 | } 90 | 91 | #[test] 92 | fn tables_count_overflow() { 93 | use Unit::*; 94 | let data = convert(&[ 95 | Raw(&[0x00, 0x01, 0x00, 0x00]), // magic 96 | UInt16(u16::MAX), // numTables 97 | UInt16(0), // searchRange 98 | UInt16(0), // entrySelector 99 | UInt16(0), // rangeShift 100 | ]); 101 | 102 | assert_eq!( 103 | Face::parse(&data, 0).unwrap_err(), 104 | FaceParsingError::MalformedFont 105 | ); 106 | } 107 | 108 | #[test] 109 | fn empty_font_collection() { 110 | use Unit::*; 111 | let data = convert(&[ 112 | Raw(&[0x74, 0x74, 0x63, 0x66]), // magic 113 | UInt16(0), // majorVersion 114 | UInt16(0), // minorVersion 115 | UInt32(0), // numFonts 116 | ]); 117 | 118 | assert_eq!(fonts_in_collection(&data), Some(0)); 119 | assert_eq!( 120 | Face::parse(&data, 0).unwrap_err(), 121 | FaceParsingError::FaceIndexOutOfBounds 122 | ); 123 | } 124 | 125 | #[test] 126 | fn font_collection_num_fonts_overflow_1() { 127 | use Unit::*; 128 | let data = convert(&[ 129 | Raw(&[0x74, 0x74, 0x63, 0x66]), // magic 130 | UInt16(0), // majorVersion 131 | UInt16(0), // minorVersion 132 | UInt32(u32::MAX), // numFonts 133 | ]); 134 | 135 | assert_eq!(fonts_in_collection(&data), Some(u32::MAX)); 136 | } 137 | 138 | #[test] 139 | #[should_panic] 140 | fn font_collection_num_fonts_overflow_2() { 141 | use Unit::*; 142 | let data = convert(&[ 143 | Raw(&[0x74, 0x74, 0x63, 0x66]), // magic 144 | UInt16(0), // majorVersion 145 | UInt16(0), // minorVersion 146 | UInt32(u32::MAX), // numFonts 147 | ]); 148 | 149 | assert_eq!( 150 | Face::parse(&data, 0).unwrap_err(), 151 | FaceParsingError::MalformedFont 152 | ); 153 | } 154 | 155 | #[test] 156 | fn font_index_overflow() { 157 | use Unit::*; 158 | let data = convert(&[ 159 | Raw(&[0x74, 0x74, 0x63, 0x66]), // magic 160 | UInt16(0), // majorVersion 161 | UInt16(0), // minorVersion 162 | UInt32(1), // numFonts 163 | UInt32(12), // offset [0] 164 | ]); 165 | 166 | assert_eq!(fonts_in_collection(&data), Some(1)); 167 | assert_eq!( 168 | Face::parse(&data, u32::MAX).unwrap_err(), 169 | FaceParsingError::FaceIndexOutOfBounds 170 | ); 171 | } 172 | 173 | #[test] 174 | fn font_index_overflow_on_regular_font() { 175 | use Unit::*; 176 | let data = convert(&[ 177 | Raw(&[0x00, 0x01, 0x00, 0x00]), // magic 178 | UInt16(0), // numTables 179 | UInt16(0), // searchRange 180 | UInt16(0), // entrySelector 181 | UInt16(0), // rangeShift 182 | ]); 183 | 184 | assert_eq!(fonts_in_collection(&data), None); 185 | assert_eq!( 186 | Face::parse(&data, 1).unwrap_err(), 187 | FaceParsingError::FaceIndexOutOfBounds 188 | ); 189 | } 190 | -------------------------------------------------------------------------------- /tests/tables/maxp.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroU16; 2 | use ttf_parser::maxp::Table; 3 | use crate::{convert, Unit::*}; 4 | 5 | #[test] 6 | fn version_05() { 7 | let table = Table::parse(&convert(&[ 8 | Fixed(0.3125), // version 9 | UInt16(1), // number of glyphs 10 | ])).unwrap(); 11 | assert_eq!(table.number_of_glyphs, NonZeroU16::new(1).unwrap()); 12 | } 13 | 14 | #[test] 15 | fn version_1_full() { 16 | let table = Table::parse(&convert(&[ 17 | Fixed(1.0), // version 18 | UInt16(1), // number of glyphs 19 | UInt16(0), // maximum points in a non-composite glyph 20 | UInt16(0), // maximum contours in a non-composite glyph 21 | UInt16(0), // maximum points in a composite glyph 22 | UInt16(0), // maximum contours in a composite glyph 23 | UInt16(0), // maximum zones 24 | UInt16(0), // maximum twilight points 25 | UInt16(0), // number of Storage Area locations 26 | UInt16(0), // number of FDEFs 27 | UInt16(0), // number of IDEFs 28 | UInt16(0), // maximum stack depth 29 | UInt16(0), // maximum byte count for glyph instructions 30 | UInt16(0), // maximum number of components 31 | UInt16(0), // maximum levels of recursion 32 | ])).unwrap(); 33 | assert_eq!(table.number_of_glyphs, NonZeroU16::new(1).unwrap()); 34 | } 35 | 36 | #[test] 37 | fn version_1_trimmed() { 38 | // We don't really care about the data after the number of glyphs. 39 | let table = Table::parse(&convert(&[ 40 | Fixed(1.0), // version 41 | UInt16(1), // number of glyphs 42 | ])).unwrap(); 43 | assert_eq!(table.number_of_glyphs, NonZeroU16::new(1).unwrap()); 44 | } 45 | 46 | #[test] 47 | fn unknown_version() { 48 | let table = Table::parse(&convert(&[ 49 | Fixed(0.0), // version 50 | UInt16(1), // number of glyphs 51 | ])); 52 | assert!(table.is_none()); 53 | } 54 | 55 | #[test] 56 | fn zero_glyphs() { 57 | let table = Table::parse(&convert(&[ 58 | Fixed(0.3125), // version 59 | UInt16(0), // number of glyphs 60 | ])); 61 | assert!(table.is_none()); 62 | } 63 | 64 | // TODO: what to do when the number of glyphs is 0xFFFF? 65 | // we're actually checking this in loca 66 | -------------------------------------------------------------------------------- /tests/tables/sbix.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroU16; 2 | use ttf_parser::{GlyphId, RasterImageFormat}; 3 | use ttf_parser::sbix::Table; 4 | use crate::{convert, Unit::*}; 5 | 6 | #[test] 7 | fn single_glyph() { 8 | let data = convert(&[ 9 | UInt16(1), // version 10 | UInt16(0), // flags 11 | UInt32(1), // number of strikes 12 | UInt32(12), // strike offset [0] 13 | 14 | // Strike [0] 15 | UInt16(20), // pixels_per_em 16 | UInt16(72), // ppi 17 | UInt32(12), // glyph data offset [0] 18 | UInt32(44), // glyph data offset [1] 19 | 20 | // Glyph Data [0] 21 | UInt16(1), // x 22 | UInt16(2), // y 23 | Raw(b"png "), // type tag 24 | // PNG data, just the part we need 25 | Raw(&[0x89, 0x50, 0x4E, 0x47]), 26 | Raw(&[0x0D, 0x0A, 0x1A, 0x0A]), 27 | Raw(&[0x00, 0x00, 0x00, 0x0D]), 28 | Raw(&[0x49, 0x48, 0x44, 0x52]), 29 | UInt32(20), // width 30 | UInt32(30), // height 31 | ]); 32 | 33 | let table = Table::parse(NonZeroU16::new(1).unwrap(), &data).unwrap(); 34 | assert_eq!(table.strikes.len(), 1); 35 | 36 | let strike = table.strikes.get(0).unwrap(); 37 | assert_eq!(strike.pixels_per_em, 20); 38 | assert_eq!(strike.ppi, 72); 39 | assert_eq!(strike.len(), 1); 40 | 41 | let glyph_data = strike.get(GlyphId(0)).unwrap(); 42 | assert_eq!(glyph_data.x, 1); 43 | assert_eq!(glyph_data.y, 2); 44 | assert_eq!(glyph_data.width, 20); 45 | assert_eq!(glyph_data.height, 30); 46 | assert_eq!(glyph_data.pixels_per_em, 20); 47 | assert_eq!(glyph_data.format, RasterImageFormat::PNG); 48 | assert_eq!(glyph_data.data.len(), 24); 49 | } 50 | 51 | #[test] 52 | fn duplicate_glyph() { 53 | let data = convert(&[ 54 | UInt16(1), // version 55 | UInt16(0), // flags 56 | UInt32(1), // number of strikes 57 | UInt32(12), // strike offset [0] 58 | 59 | // Strike [0] 60 | UInt16(20), // pixels_per_em 61 | UInt16(72), // ppi 62 | UInt32(16), // glyph data offset [0] 63 | UInt32(48), // glyph data offset [1] 64 | UInt32(58), // glyph data offset [2] 65 | 66 | // Glyph Data [0] 67 | UInt16(1), // x 68 | UInt16(2), // y 69 | Raw(b"png "), // type tag 70 | // PNG data, just the part we need 71 | Raw(&[0x89, 0x50, 0x4E, 0x47]), 72 | Raw(&[0x0D, 0x0A, 0x1A, 0x0A]), 73 | Raw(&[0x00, 0x00, 0x00, 0x0D]), 74 | Raw(&[0x49, 0x48, 0x44, 0x52]), 75 | UInt32(20), // width 76 | UInt32(30), // height 77 | 78 | // Glyph Data [1] 79 | UInt16(3), // x 80 | UInt16(4), // y 81 | Raw(b"dupe"), // type tag 82 | UInt16(0), // glyph id 83 | ]); 84 | 85 | let table = Table::parse(NonZeroU16::new(2).unwrap(), &data).unwrap(); 86 | assert_eq!(table.strikes.len(), 1); 87 | 88 | let strike = table.strikes.get(0).unwrap(); 89 | assert_eq!(strike.pixels_per_em, 20); 90 | assert_eq!(strike.ppi, 72); 91 | assert_eq!(strike.len(), 2); 92 | 93 | let glyph_data = strike.get(GlyphId(1)).unwrap(); 94 | assert_eq!(glyph_data.x, 1); 95 | assert_eq!(glyph_data.y, 2); 96 | assert_eq!(glyph_data.width, 20); 97 | assert_eq!(glyph_data.height, 30); 98 | assert_eq!(glyph_data.pixels_per_em, 20); 99 | assert_eq!(glyph_data.format, RasterImageFormat::PNG); 100 | assert_eq!(glyph_data.data.len(), 24); 101 | } 102 | 103 | #[test] 104 | fn recursive() { 105 | let data = convert(&[ 106 | UInt16(1), // version 107 | UInt16(0), // flags 108 | UInt32(1), // number of strikes 109 | UInt32(12), // strike offset [0] 110 | 111 | // Strike [0] 112 | UInt16(20), // pixels_per_em 113 | UInt16(72), // ppi 114 | UInt32(16), // glyph data offset [0] 115 | UInt32(26), // glyph data offset [1] 116 | UInt32(36), // glyph data offset [2] 117 | 118 | // Glyph Data [0] 119 | UInt16(1), // x 120 | UInt16(2), // y 121 | Raw(b"dupe"), // type tag 122 | UInt16(0), // glyph id 123 | 124 | // Glyph Data [1] 125 | UInt16(1), // x 126 | UInt16(2), // y 127 | Raw(b"dupe"), // type tag 128 | UInt16(0), // glyph id 129 | ]); 130 | 131 | let table = Table::parse(NonZeroU16::new(2).unwrap(), &data).unwrap(); 132 | let strike = table.strikes.get(0).unwrap(); 133 | assert!(strike.get(GlyphId(0)).is_none()); 134 | assert!(strike.get(GlyphId(1)).is_none()); 135 | } 136 | -------------------------------------------------------------------------------- /tests/tables/trak.rs: -------------------------------------------------------------------------------- 1 | use ttf_parser::trak::Table; 2 | use crate::{convert, Unit::*}; 3 | 4 | #[test] 5 | fn empty() { 6 | let data = convert(&[ 7 | Fixed(1.0), // version 8 | UInt16(0), // format 9 | UInt16(0), // horizontal data offset 10 | UInt16(0), // vertical data offset 11 | UInt16(0), // padding 12 | ]); 13 | 14 | let table = Table::parse(&data).unwrap(); 15 | assert_eq!(table.horizontal.tracks.len(), 0); 16 | assert_eq!(table.horizontal.sizes.len(), 0); 17 | assert_eq!(table.vertical.tracks.len(), 0); 18 | assert_eq!(table.vertical.sizes.len(), 0); 19 | } 20 | 21 | #[test] 22 | fn basic() { 23 | let data = convert(&[ 24 | Fixed(1.0), // version 25 | UInt16(0), // format 26 | UInt16(12), // horizontal data offset 27 | UInt16(0), // vertical data offset 28 | UInt16(0), // padding 29 | 30 | // TrackData 31 | UInt16(3), // number of tracks 32 | UInt16(2), // number of sizes 33 | UInt32(44), // offset to size table 34 | 35 | // TrackTableEntry [0] 36 | Fixed(-1.0), // track 37 | UInt16(256), // name index 38 | UInt16(52), // offset of the two per-size tracking values 39 | 40 | // TrackTableEntry [1] 41 | Fixed(0.0), // track 42 | UInt16(258), // name index 43 | UInt16(60), // offset of the two per-size tracking values 44 | 45 | // TrackTableEntry [2] 46 | Fixed(1.0), // track 47 | UInt16(257), // name index 48 | UInt16(56), // offset of the two per-size tracking values 49 | 50 | // Size [0] 51 | Fixed(12.0), // points 52 | // Size [1] 53 | Fixed(24.0), // points 54 | 55 | // Per-size tracking values. 56 | Int16(-15), 57 | Int16(-7), 58 | Int16(50), 59 | Int16(20), 60 | Int16(0), 61 | Int16(0), 62 | ]); 63 | 64 | let table = Table::parse(&data).unwrap(); 65 | 66 | assert_eq!(table.horizontal.tracks.len(), 3); 67 | assert_eq!(table.horizontal.tracks.get(0).unwrap().value, -1.0); 68 | assert_eq!(table.horizontal.tracks.get(1).unwrap().value, 0.0); 69 | assert_eq!(table.horizontal.tracks.get(2).unwrap().value, 1.0); 70 | assert_eq!(table.horizontal.tracks.get(0).unwrap().name_index, 256); 71 | assert_eq!(table.horizontal.tracks.get(1).unwrap().name_index, 258); 72 | assert_eq!(table.horizontal.tracks.get(2).unwrap().name_index, 257); 73 | assert_eq!(table.horizontal.tracks.get(0).unwrap().values.len(), 2); 74 | assert_eq!(table.horizontal.tracks.get(0).unwrap().values.get(0).unwrap(), -15); 75 | assert_eq!(table.horizontal.tracks.get(0).unwrap().values.get(1).unwrap(), -7); 76 | assert_eq!(table.horizontal.tracks.get(1).unwrap().values.len(), 2); 77 | assert_eq!(table.horizontal.tracks.get(1).unwrap().values.get(0).unwrap(), 0); 78 | assert_eq!(table.horizontal.tracks.get(1).unwrap().values.get(1).unwrap(), 0); 79 | assert_eq!(table.horizontal.tracks.get(2).unwrap().values.len(), 2); 80 | assert_eq!(table.horizontal.tracks.get(2).unwrap().values.get(0).unwrap(), 50); 81 | assert_eq!(table.horizontal.tracks.get(2).unwrap().values.get(1).unwrap(), 20); 82 | assert_eq!(table.horizontal.sizes.len(), 2); 83 | assert_eq!(table.horizontal.sizes.get(0).unwrap().0, 12.0); 84 | assert_eq!(table.horizontal.sizes.get(1).unwrap().0, 24.0); 85 | 86 | assert_eq!(table.vertical.tracks.len(), 0); 87 | assert_eq!(table.vertical.sizes.len(), 0); 88 | } 89 | --------------------------------------------------------------------------------