├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── c ├── Cargo.toml ├── build.rs ├── cbindgen.toml └── src │ └── lib.rs ├── examples ├── fallback.rs ├── list-fonts.rs ├── match-font.rs └── render-glyph.rs ├── resources └── tests │ ├── eb-garamond │ ├── COPYING │ ├── EBGaramond12-Italic.otf │ ├── EBGaramond12-Regular.otf │ ├── EBGaramond12-Regular.ttf │ └── EBGaramond12.otc │ ├── inconsolata │ ├── Inconsolata-Regular.ttf │ └── OFL.txt │ └── times-roman-pcf │ ├── COPYING │ └── timR12.pcf ├── src ├── canvas.rs ├── error.rs ├── family.rs ├── family_handle.rs ├── family_name.rs ├── file_type.rs ├── font.rs ├── handle.rs ├── hinting.rs ├── lib.rs ├── loader.rs ├── loaders │ ├── core_text.rs │ ├── directwrite.rs │ ├── freetype.rs │ └── mod.rs ├── matching.rs ├── metrics.rs ├── outline.rs ├── properties.rs ├── source.rs ├── sources │ ├── core_text.rs │ ├── directwrite.rs │ ├── fontconfig.rs │ ├── fs.rs │ ├── mem.rs │ ├── mod.rs │ └── multi.rs └── utils.rs └── tests ├── select_font.rs └── tests.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Run CI 2 | on: 3 | push: 4 | branches: ["main"] 5 | pull_request: 6 | branches: ["**"] 7 | merge_group: 8 | types: [checks_requested] 9 | 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | env: 15 | RUST_BACKTRACE: 1 16 | SHELL: /bin/bash 17 | 18 | jobs: 19 | ci-linux: 20 | name: Linux 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 2 26 | - name: Setup Rust 27 | uses: dtolnay/rust-toolchain@stable 28 | - name: Install 29 | run: | 30 | echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | sudo debconf-set-selections; 31 | sudo apt-get install ttf-mscorefonts-installer libfontconfig-dev; 32 | - name: Build 33 | run: cargo build 34 | - name: Tests 35 | run: cargo test 36 | - name: Format 37 | run: cargo fmt --all -- --check 38 | 39 | ci-macos: 40 | name: macOS 41 | runs-on: macos-14 42 | steps: 43 | - uses: actions/checkout@v4 44 | with: 45 | fetch-depth: 2 46 | - name: Setup Rust 47 | uses: dtolnay/rust-toolchain@stable 48 | - name: Build 49 | run: cargo build 50 | - name: Tests 51 | run: cargo test 52 | 53 | ci-win: 54 | name: Windows 55 | runs-on: windows-2022 56 | steps: 57 | - uses: actions/checkout@v4 58 | with: 59 | fetch-depth: 2 60 | - name: Setup Rust 61 | uses: dtolnay/rust-toolchain@stable 62 | - name: Build 63 | run: cargo build 64 | - name: Tests 65 | run: cargo test 66 | 67 | build_result: 68 | name: Result 69 | runs-on: ubuntu-latest 70 | needs: 71 | - "ci-linux" 72 | - "ci-macos" 73 | - "ci-win" 74 | 75 | steps: 76 | - name: Mark the job as successful 77 | run: exit 0 78 | if: success() 79 | - name: Mark the job as unsuccessful 80 | run: exit 1 81 | if: "!success()" 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | /c/build 5 | /c/target 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "font-kit" 3 | version = "0.14.3" 4 | authors = ["Patrick Walton "] 5 | description = "A cross-platform font loading library" 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | repository = "https://github.com/servo/font-kit" 9 | homepage = "https://github.com/servo/font-kit" 10 | exclude = ["resources/**"] 11 | edition = "2018" 12 | rust-version = "1.77" 13 | 14 | [features] 15 | default = ["source"] 16 | loader-freetype = ["freetype-sys"] 17 | loader-freetype-default = ["loader-freetype"] 18 | source-fontconfig = ["yeslogic-fontconfig-sys"] 19 | source-fontconfig-dlopen = ["yeslogic-fontconfig-sys/dlopen"] 20 | source-fontconfig-default = ["source-fontconfig"] 21 | source = [] 22 | 23 | [dependencies] 24 | bitflags = "2.4" 25 | byteorder = "1.2" 26 | float-ord = "0.3" 27 | libc = "0.2" 28 | log = "0.4.4" 29 | pathfinder_geometry = "0.5" 30 | pathfinder_simd = "0.5.5" 31 | freetype-sys = {version = "0.23", optional = true} 32 | 33 | [dependencies.yeslogic-fontconfig-sys] 34 | version = "6.0" 35 | optional = true 36 | 37 | [dev-dependencies] 38 | clap = "4" 39 | colored = "2" 40 | pbr = "1.0" 41 | prettytable-rs = "0.10" 42 | 43 | [target.'cfg(target_family = "windows")'.dependencies] 44 | dwrote = { version = "^0.11.3", default-features = false } 45 | 46 | [target.'cfg(target_family = "windows")'.dependencies.winapi] 47 | version = "0.3" 48 | features = ["dwrite", "minwindef", "sysinfoapi", "winbase", "winnt"] 49 | 50 | [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] 51 | core-foundation = "0.9" 52 | core-graphics = "0.23" 53 | core-text = "20.1.0" 54 | 55 | [target.'cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))'.dependencies] 56 | freetype-sys = "0.23" 57 | 58 | [target.'cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios", target_arch = "wasm32", target_env = "ohos")))'.dependencies] 59 | yeslogic-fontconfig-sys = "6.0" 60 | 61 | [target.'cfg(not(any(target_arch = "wasm32", target_family = "windows", target_os = "android", target_env = "ohos")))'.dependencies] 62 | dirs = "6.0" 63 | 64 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 65 | walkdir = "2.1" 66 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # font-kit 2 | 3 | [![Build Status](https://github.com/servo/font-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/servo/font-kit/actions) 4 | [![crates.io](https://img.shields.io/crates/v/font-kit.svg)](https://crates.io/crates/font-kit) 5 | [![Documentation](https://docs.rs/font-kit/badge.svg)](https://docs.rs/font-kit) 6 | 7 | `font-kit` provides a common interface to the various system font libraries and provides 8 | services such as finding fonts on the system, performing nearest-font matching, and rasterizing 9 | glyphs. 10 | 11 | ## Synopsis 12 | 13 | ```rust 14 | let font = SystemSource::new() 15 | .select_by_postscript_name("ArialMT") 16 | .unwrap() 17 | .load() 18 | .unwrap(); 19 | 20 | let glyph_id = font.glyph_for_char('A').unwrap(); 21 | let mut canvas = Canvas::new(&Size2D::new(32, 32), Format::A8); 22 | 23 | font.rasterize_glyph( 24 | &mut canvas, 25 | glyph_id, 26 | 32.0, 27 | &Point2D::new(0.0, 32.0), 28 | HintingOptions::None, 29 | RasterizationOptions::GrayscaleAa, 30 | ) 31 | .unwrap(); 32 | ``` 33 | 34 | ## Backends 35 | 36 | `font-kit` delegates to system libraries to perform tasks. It has two types of backends: a *source* 37 | and a *loader*. Sources are platform font databases; they allow lookup of installed fonts by name 38 | or attributes. Loaders are font loading libraries; they allow font files (TTF, OTF, etc.) to be 39 | loaded from a file on disk or from bytes in memory. Sources and loaders can be freely intermixed at 40 | runtime; fonts can be looked up via DirectWrite and rendered via FreeType, for example. 41 | 42 | Available loaders: 43 | 44 | * Core Text (macOS): The system font loader on macOS. Does not do hinting except when bilevel 45 | rendering is in use. 46 | 47 | * DirectWrite (Windows): The newer system framework for text rendering on Windows. Does vertical 48 | hinting but not full hinting. 49 | 50 | * FreeType (cross-platform): A full-featured font rendering framework. 51 | 52 | Available sources: 53 | 54 | * Core Text (macOS): The system font database on macOS. 55 | 56 | * DirectWrite (Windows): The newer API to query the system font database on Windows. 57 | 58 | * Fontconfig (cross-platform): A technically platform-neutral, but in practice Unix-specific, API 59 | to query and match fonts. 60 | 61 | * Filesystem (cross-platform): A simple source that reads fonts from a path on disk. This is the 62 | default on Android. 63 | 64 | * Memory (cross-platform): A source that reads from a fixed set of fonts in memory. 65 | 66 | * Multi (cross-platform): A source that allows multiple sources to be queried at once. 67 | 68 | On Windows and macOS, the FreeType loader and the Fontconfig source are not built by default. 69 | To build them, use the `loader-freetype` and `source-fontconfig` Cargo features respectively. If 70 | you want them to be the default, instead use the `loader-freetype-default` and 71 | `source-fontconfig-default` Cargo features respectively. Beware that `source-fontconfig-default` is 72 | rarely what you want on those two platforms! 73 | 74 | If you don't need to locate fonts on the system at all—for example, if all your fonts are stored 75 | with your app—then you can omit the default `source` feature and none of that code will be 76 | included. 77 | 78 | ## Features 79 | 80 | `font-kit` is capable of doing the following: 81 | 82 | * Loading fonts from files or memory. 83 | 84 | * Determining whether files on disk or in memory represent fonts. 85 | 86 | * Interoperating with native font APIs. 87 | 88 | * Querying various metadata about fonts. 89 | 90 | * Doing simple glyph-to-character mapping. (For more complex use cases, a shaper is required; 91 | proper shaping is beyond the scope of `font-kit`.) 92 | 93 | * Reading unhinted or hinted vector outlines from glyphs. 94 | 95 | * Calculating glyph and font metrics. 96 | 97 | * Looking up glyph advances and origins. 98 | 99 | * Rasterizing glyphs using the native rasterizer, optionally using hinting. (Custom rasterizers, 100 | such as Pathfinder, can be used in conjunction with the outline API.) 101 | 102 | * Looking up all fonts on the system. 103 | 104 | * Searching for specific fonts by family or PostScript name. 105 | 106 | * Performing font matching according to the [CSS Fonts Module Level 3] specification. 107 | 108 | ## Dependencies 109 | 110 | **Ubuntu** 111 | 112 | `sudo apt install pkg-config libfreetype6-dev libfontconfig1-dev` 113 | 114 | ## License 115 | 116 | `font-kit` is licensed under the same terms as Rust itself. 117 | 118 | [CSS Fonts Module Level 3]: https://drafts.csswg.org/css-fonts-3/#font-matching-algorithm 119 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-env-changed=RUST_FONTCONFIG_DLOPEN"); 3 | let dlopen = std::env::var("RUST_FONTCONFIG_DLOPEN").is_ok(); 4 | if dlopen { 5 | println!("cargo:rustc-cfg=feature=\"source-fontconfig-dlopen\""); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /c/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "font-kit-c" 3 | version = "0.1.0" 4 | authors = ["Patrick Walton "] 5 | edition = "2018" 6 | build = "build.rs" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [dependencies] 12 | 13 | [dependencies.font-kit] 14 | path = ".." 15 | 16 | [build-dependencies] 17 | cbindgen = "0.8" 18 | -------------------------------------------------------------------------------- /c/build.rs: -------------------------------------------------------------------------------- 1 | // font-kit/c/build.rs 2 | // 3 | // Copyright © 2019 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | use cbindgen; 12 | use std::env; 13 | use std::fs; 14 | 15 | fn main() { 16 | fs::create_dir_all("build/include/font-kit").expect("Failed to create directories!"); 17 | let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 18 | cbindgen::generate(crate_dir).expect("cbindgen failed!") 19 | .write_to_file("build/include/font-kit/font-kit.h"); 20 | } 21 | -------------------------------------------------------------------------------- /c/cbindgen.toml: -------------------------------------------------------------------------------- 1 | language = "C" 2 | header = """\ 3 | /* Generated code. Do not edit; instead run `cargo build` in `font-kit-c`. */ 4 | 5 | extern \"C\" { 6 | """ 7 | trailer = "}" 8 | include_guard = "FK_FONT_KIT_H" 9 | include_version = true 10 | 11 | [parse] 12 | parse_deps = true 13 | include = ["font-kit"] 14 | -------------------------------------------------------------------------------- /c/src/lib.rs: -------------------------------------------------------------------------------- 1 | // font-kit/c/src/lib.rs 2 | // 3 | // Copyright © 2019 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | use font_kit::handle::Handle; 12 | use std::mem; 13 | use std::slice; 14 | use std::sync::Arc; 15 | 16 | pub type FKDataRef = *const Vec; 17 | pub type FKHandleRef = *mut Handle; 18 | 19 | #[no_mangle] 20 | pub unsafe extern "C" fn FKDataCreate(bytes: *const u8, len: usize) -> FKDataRef { 21 | Arc::into_raw(Arc::new(slice::from_raw_parts(bytes, len).to_vec())) 22 | } 23 | 24 | #[no_mangle] 25 | pub unsafe extern "C" fn FKDataDestroy(data: FKDataRef) { 26 | drop(Arc::from_raw(data)) 27 | } 28 | 29 | /// Does not take ownership of `bytes`. 30 | #[no_mangle] 31 | pub unsafe extern "C" fn FKHandleCreateWithMemory(bytes: FKDataRef, font_index: u32) 32 | -> FKHandleRef { 33 | let bytes = Arc::from_raw(bytes); 34 | mem::forget(bytes.clone()); 35 | Box::into_raw(Box::new(Handle::from_memory(bytes, font_index))) 36 | } 37 | 38 | #[no_mangle] 39 | pub unsafe extern "C" fn FKHandleDestroy(handle: FKHandleRef) { 40 | drop(Box::from_raw(handle)) 41 | } 42 | -------------------------------------------------------------------------------- /examples/fallback.rs: -------------------------------------------------------------------------------- 1 | // font-kit/examples/fallback.rs 2 | // 3 | // Copyright © 2019 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | extern crate clap; 12 | extern crate font_kit; 13 | 14 | use clap::{Arg, ArgMatches, Command}; 15 | 16 | use font_kit::loader::Loader; 17 | use font_kit::source::SystemSource; 18 | 19 | #[cfg(any(target_family = "windows", target_os = "macos"))] 20 | static SANS_SERIF_FONT_REGULAR_POSTSCRIPT_NAME: &'static str = "ArialMT"; 21 | #[cfg(not(any(target_family = "windows", target_os = "macos")))] 22 | static SANS_SERIF_FONT_REGULAR_POSTSCRIPT_NAME: &str = "DejaVuSans"; 23 | 24 | fn get_args() -> ArgMatches { 25 | let postscript_name_arg = Arg::new("POSTSCRIPT-NAME") 26 | .help("PostScript name of the font") 27 | .default_value(SANS_SERIF_FONT_REGULAR_POSTSCRIPT_NAME) 28 | .index(1); 29 | let text_arg = Arg::new("TEXT") 30 | .help("Text to query") 31 | .default_value("A") 32 | .index(2); 33 | let locale_arg = Arg::new("LOCALE") 34 | .help("Locale for fallback query") 35 | .default_value("en-US") 36 | .index(3); 37 | Command::new("fallback") 38 | .version("0.1") 39 | .arg(postscript_name_arg) 40 | .arg(text_arg) 41 | .arg(locale_arg) 42 | .get_matches() 43 | } 44 | 45 | fn main() { 46 | let matches = get_args(); 47 | let postscript_name = matches 48 | .get_one::("POSTSCRIPT-NAME") 49 | .map(|s| s.as_str()) 50 | .unwrap(); 51 | let text = matches 52 | .get_one::("TEXT") 53 | .map(|s| s.as_str()) 54 | .unwrap(); 55 | let locale = matches 56 | .get_one::("LOCALE") 57 | .map(|s| s.as_str()) 58 | .unwrap(); 59 | let font = SystemSource::new() 60 | .select_by_postscript_name(postscript_name) 61 | .expect("Font not found") 62 | .load() 63 | .unwrap(); 64 | println!("{}: text: {:?}", postscript_name, text); 65 | let fallback_result = font.get_fallbacks(text, locale); 66 | println!( 67 | "fallback valid substring length: {}", 68 | fallback_result.valid_len 69 | ); 70 | for font in &fallback_result.fonts { 71 | println!("font: {}", font.font.full_name()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/list-fonts.rs: -------------------------------------------------------------------------------- 1 | // font-kit/examples/list-fonts.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Lists all fonts on the system. 12 | 13 | extern crate font_kit; 14 | extern crate pbr; 15 | extern crate prettytable; 16 | 17 | use font_kit::source::SystemSource; 18 | use pbr::ProgressBar; 19 | use prettytable::{Attr, Cell, Row, Table}; 20 | 21 | fn main() { 22 | let mut table = Table::new(); 23 | table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); 24 | table.set_titles(Row::new(vec![ 25 | Cell::new("PostScript Name").with_style(Attr::Bold), 26 | Cell::new("Name").with_style(Attr::Bold), 27 | Cell::new("Family").with_style(Attr::Bold), 28 | Cell::new("Style").with_style(Attr::Bold), 29 | Cell::new("Weight").with_style(Attr::Bold), 30 | Cell::new("Stretch").with_style(Attr::Bold), 31 | ])); 32 | 33 | let source = SystemSource::new(); 34 | let fonts = source.all_fonts().unwrap(); 35 | let mut progress_bar = ProgressBar::new(fonts.len() as u64); 36 | progress_bar.message("Loading fonts… "); 37 | 38 | for font in fonts { 39 | if let Ok(font) = font.load() { 40 | let properties = font.properties(); 41 | table.add_row(Row::new(vec![ 42 | Cell::new(&font.postscript_name().unwrap_or_else(|| "".to_owned())), 43 | Cell::new(&font.full_name()), 44 | Cell::new(&font.family_name()), 45 | Cell::new(&properties.style.to_string()), 46 | Cell::new(&properties.weight.0.to_string()), 47 | Cell::new(&properties.stretch.0.to_string()), 48 | ])); 49 | } 50 | 51 | progress_bar.inc(); 52 | } 53 | 54 | progress_bar.finish_print(""); 55 | table.printstd(); 56 | } 57 | -------------------------------------------------------------------------------- /examples/match-font.rs: -------------------------------------------------------------------------------- 1 | // font-kit/examples/match-font.rs 2 | // 3 | // Copyright © 2020 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Looks up fonts by name. 12 | 13 | extern crate font_kit; 14 | 15 | use font_kit::family_name::FamilyName; 16 | use font_kit::handle::Handle; 17 | use font_kit::properties::Properties; 18 | use font_kit::source::SystemSource; 19 | use std::env; 20 | 21 | fn main() -> Result<(), Box> { 22 | let args: Vec<_> = env::args().collect(); 23 | if args.len() != 2 { 24 | println!("Usage:\n\tmatch-font \"Times New Roman, Arial, serif\""); 25 | std::process::exit(1); 26 | } 27 | 28 | let mut families = Vec::new(); 29 | for family in args[1].split(',') { 30 | let family = family.replace('\'', ""); 31 | let family = family.trim(); 32 | families.push(match family { 33 | "serif" => FamilyName::Serif, 34 | "sans-serif" => FamilyName::SansSerif, 35 | "monospace" => FamilyName::Monospace, 36 | "cursive" => FamilyName::Cursive, 37 | "fantasy" => FamilyName::Fantasy, 38 | _ => FamilyName::Title(family.to_string()), 39 | }); 40 | } 41 | 42 | let properties = Properties::default(); 43 | let handle = SystemSource::new().select_best_match(&families, &properties)?; 44 | 45 | if let Handle::Path { 46 | ref path, 47 | font_index, 48 | } = handle 49 | { 50 | println!("Path: {}", path.display()); 51 | println!("Index: {}", font_index); 52 | } 53 | 54 | let font = handle.load()?; 55 | 56 | println!("Family name: {}", font.family_name()); 57 | println!( 58 | "PostScript name: {}", 59 | font.postscript_name().unwrap_or("?".to_string()) 60 | ); 61 | println!("Style: {:?}", font.properties().style); 62 | println!("Weight: {:?}", font.properties().weight); 63 | println!("Stretch: {:?}", font.properties().stretch); 64 | 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /examples/render-glyph.rs: -------------------------------------------------------------------------------- 1 | // font-kit/examples/render-glyph.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | extern crate clap; 12 | extern crate colored; 13 | extern crate font_kit; 14 | extern crate pathfinder_geometry; 15 | 16 | use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command}; 17 | use colored::Colorize; 18 | use font_kit::canvas::{Canvas, Format, RasterizationOptions}; 19 | use font_kit::hinting::HintingOptions; 20 | use font_kit::source::SystemSource; 21 | use pathfinder_geometry::transform2d::Transform2F; 22 | use std::fmt::Write; 23 | 24 | #[cfg(any(target_family = "windows", target_os = "macos"))] 25 | static SANS_SERIF_FONT_REGULAR_POSTSCRIPT_NAME: &'static str = "ArialMT"; 26 | #[cfg(not(any(target_family = "windows", target_os = "macos")))] 27 | static SANS_SERIF_FONT_REGULAR_POSTSCRIPT_NAME: &str = "DejaVuSans"; 28 | 29 | fn get_args() -> ArgMatches { 30 | let postscript_name_arg = Arg::new("POSTSCRIPT-NAME") 31 | .help("PostScript name of the font") 32 | .default_value(SANS_SERIF_FONT_REGULAR_POSTSCRIPT_NAME) 33 | .index(1); 34 | let glyph_arg = Arg::new("GLYPH") 35 | .help("Character to render") 36 | .default_value("A") 37 | .index(2); 38 | let size_arg = Arg::new("SIZE") 39 | .help("Font size in blocks") 40 | .default_value("32") 41 | .index(3); 42 | let grayscale_arg = Arg::new("grayscale") 43 | .long("grayscale") 44 | .help("Use grayscale antialiasing (default)"); 45 | let bilevel_arg = Arg::new("bilevel") 46 | .help("Use bilevel (black & white) rasterization") 47 | .short('b') 48 | .long("bilevel") 49 | .action(ArgAction::SetTrue); 50 | let subpixel_arg = Arg::new("subpixel") 51 | .help("Use subpixel (LCD) rasterization") 52 | .short('s') 53 | .long("subpixel") 54 | .action(ArgAction::SetTrue); 55 | let hinting_value_parser = 56 | clap::builder::PossibleValuesParser::new(["none", "vertical", "full"]); 57 | let hinting_arg = Arg::new("hinting") 58 | .help("Select hinting type") 59 | .short('H') 60 | .long("hinting") 61 | .value_parser(hinting_value_parser) 62 | .value_names(&["TYPE"]); 63 | let transform_arg = Arg::new("transform") 64 | .help("Transform to apply to glyph when rendering") 65 | .long("transform") 66 | .num_args(4); 67 | let rasterization_mode_group = 68 | ArgGroup::new("rasterization-mode").args(&["grayscale", "bilevel", "subpixel"]); 69 | Command::new("render-glyph") 70 | .version("0.1") 71 | .author("The Pathfinder Project Developers") 72 | .about("Simple example tool to render glyphs with `font-kit`") 73 | .arg(postscript_name_arg) 74 | .arg(glyph_arg) 75 | .arg(size_arg) 76 | .arg(grayscale_arg) 77 | .arg(bilevel_arg) 78 | .arg(subpixel_arg) 79 | .group(rasterization_mode_group) 80 | .arg(hinting_arg) 81 | .arg(transform_arg) 82 | .get_matches() 83 | } 84 | 85 | fn main() { 86 | let matches = get_args(); 87 | 88 | let postscript_name = matches 89 | .get_one::("POSTSCRIPT-NAME") 90 | .map(|s| s.as_str()) 91 | .unwrap(); 92 | let character = matches 93 | .get_one::("GLYPH") 94 | .map(|s| s.as_str()) 95 | .unwrap() 96 | .chars() 97 | .next() 98 | .unwrap(); 99 | let size: f32 = matches 100 | .get_one::("SIZE") 101 | .map(|s| s.as_str()) 102 | .unwrap() 103 | .parse() 104 | .unwrap(); 105 | 106 | let (canvas_format, rasterization_options) = if matches.get_flag("bilevel") { 107 | (Format::A8, RasterizationOptions::Bilevel) 108 | } else if matches.get_flag("subpixel") { 109 | (Format::Rgb24, RasterizationOptions::SubpixelAa) 110 | } else { 111 | (Format::A8, RasterizationOptions::GrayscaleAa) 112 | }; 113 | 114 | let mut transform = Transform2F::default(); 115 | if let Some(values) = matches.get_many::("transform") { 116 | if let [Ok(a), Ok(b), Ok(c), Ok(d)] = values.map(|x| x.parse()).collect::>()[..] { 117 | transform = Transform2F::row_major(a, b, c, d, 0.0, 0.0) 118 | } 119 | } 120 | 121 | let hinting_options = match matches.get_one::("hinting").map(|s| s.as_str()) { 122 | Some("vertical") => HintingOptions::Vertical(size), 123 | Some("full") => HintingOptions::Full(size), 124 | _ => HintingOptions::None, 125 | }; 126 | 127 | let font = SystemSource::new() 128 | .select_by_postscript_name(postscript_name) 129 | .unwrap() 130 | .load() 131 | .unwrap(); 132 | let glyph_id = font.glyph_for_char(character).unwrap(); 133 | 134 | let raster_rect = font 135 | .raster_bounds( 136 | glyph_id, 137 | size, 138 | transform, 139 | hinting_options, 140 | rasterization_options, 141 | ) 142 | .unwrap(); 143 | 144 | let mut canvas = Canvas::new(raster_rect.size(), canvas_format); 145 | font.rasterize_glyph( 146 | &mut canvas, 147 | glyph_id, 148 | size, 149 | Transform2F::from_translation(-raster_rect.origin().to_f32()) * transform, 150 | hinting_options, 151 | rasterization_options, 152 | ) 153 | .unwrap(); 154 | 155 | println!("glyph {}:", glyph_id); 156 | for y in 0..raster_rect.height() { 157 | let mut line = String::new(); 158 | let (row_start, row_end) = (y as usize * canvas.stride, (y + 1) as usize * canvas.stride); 159 | let row = &canvas.pixels[row_start..row_end]; 160 | for x in 0..raster_rect.width() { 161 | match canvas.format { 162 | Format::Rgba32 => unimplemented!(), 163 | Format::Rgb24 => { 164 | write!( 165 | &mut line, 166 | "{}{}{}", 167 | shade(row[x as usize * 3]).to_string().red(), 168 | shade(row[x as usize * 3 + 1]).to_string().green(), 169 | shade(row[x as usize * 3 + 2]).to_string().blue() 170 | ) 171 | .unwrap(); 172 | } 173 | Format::A8 => { 174 | let shade = shade(row[x as usize]); 175 | line.push(shade); 176 | line.push(shade); 177 | } 178 | } 179 | } 180 | println!("{}", line); 181 | } 182 | } 183 | 184 | fn shade(value: u8) -> char { 185 | match value { 186 | 0 => ' ', 187 | 1..=84 => '░', 188 | 85..=169 => '▒', 189 | 170..=254 => '▓', 190 | _ => '█', 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /resources/tests/eb-garamond/COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2013 Georg Duffner (http://www.georgduffner.at) 2 | 3 | All "EB Garamond" Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /resources/tests/eb-garamond/EBGaramond12-Italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/servo/font-kit/868f28a2c60d36092be66e4d83db001267c9d6b4/resources/tests/eb-garamond/EBGaramond12-Italic.otf -------------------------------------------------------------------------------- /resources/tests/eb-garamond/EBGaramond12-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/servo/font-kit/868f28a2c60d36092be66e4d83db001267c9d6b4/resources/tests/eb-garamond/EBGaramond12-Regular.otf -------------------------------------------------------------------------------- /resources/tests/eb-garamond/EBGaramond12-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/servo/font-kit/868f28a2c60d36092be66e4d83db001267c9d6b4/resources/tests/eb-garamond/EBGaramond12-Regular.ttf -------------------------------------------------------------------------------- /resources/tests/eb-garamond/EBGaramond12.otc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/servo/font-kit/868f28a2c60d36092be66e4d83db001267c9d6b4/resources/tests/eb-garamond/EBGaramond12.otc -------------------------------------------------------------------------------- /resources/tests/inconsolata/Inconsolata-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/servo/font-kit/868f28a2c60d36092be66e4d83db001267c9d6b4/resources/tests/inconsolata/Inconsolata-Regular.ttf -------------------------------------------------------------------------------- /resources/tests/inconsolata/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2006 The Inconsolata Project Authors 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /resources/tests/times-roman-pcf/COPYING: -------------------------------------------------------------------------------- 1 | The X11 Fonts are governed by the following license: 2 | 3 | Copyright (C) 1994 X Consortium 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to 7 | deal in the Software without restriction, including without limitation the 8 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | sell copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 19 | AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- 20 | TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | Except as contained in this notice, the name of the X Consortium shall not 23 | be used in advertising or otherwise to promote the sale, use or other deal- 24 | ings in this Software without prior written authorization from the X Consor- 25 | tium. 26 | 27 | X Window System is a trademark of X Consortium, Inc. 28 | 29 | 30 | -------------------------------------------------------------------------------- /resources/tests/times-roman-pcf/timR12.pcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/servo/font-kit/868f28a2c60d36092be66e4d83db001267c9d6b4/resources/tests/times-roman-pcf/timR12.pcf -------------------------------------------------------------------------------- /src/canvas.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/canvas.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! An in-memory bitmap surface for glyph rasterization. 12 | 13 | use pathfinder_geometry::rect::RectI; 14 | use pathfinder_geometry::vector::Vector2I; 15 | use std::cmp; 16 | use std::fmt; 17 | 18 | use crate::utils; 19 | 20 | static BITMAP_1BPP_TO_8BPP_LUT: [[u8; 8]; 256] = { 21 | let mut lut = [[0; 8]; 256]; 22 | 23 | let mut byte = 0; 24 | while byte < 0x100 { 25 | let mut value = [0; 8]; 26 | 27 | let mut bit = 0; 28 | while bit < 8 { 29 | if (byte & (0x80 >> bit)) != 0 { 30 | value[bit] = 0xff; 31 | } 32 | bit += 1; 33 | } 34 | 35 | lut[byte] = value; 36 | byte += 1; 37 | } 38 | lut 39 | }; 40 | 41 | /// An in-memory bitmap surface for glyph rasterization. 42 | pub struct Canvas { 43 | /// The raw pixel data. 44 | pub pixels: Vec, 45 | /// The size of the buffer, in pixels. 46 | pub size: Vector2I, 47 | /// The number of *bytes* between successive rows. 48 | pub stride: usize, 49 | /// The image format of the canvas. 50 | pub format: Format, 51 | } 52 | 53 | impl Canvas { 54 | /// Creates a new blank canvas with the given pixel size and format. 55 | /// 56 | /// Stride is automatically calculated from width. 57 | /// 58 | /// The canvas is initialized with transparent black (all values 0). 59 | #[inline] 60 | pub fn new(size: Vector2I, format: Format) -> Canvas { 61 | Canvas::with_stride( 62 | size, 63 | size.x() as usize * format.bytes_per_pixel() as usize, 64 | format, 65 | ) 66 | } 67 | 68 | /// Creates a new blank canvas with the given pixel size, stride (number of bytes between 69 | /// successive rows), and format. 70 | /// 71 | /// The canvas is initialized with transparent black (all values 0). 72 | pub fn with_stride(size: Vector2I, stride: usize, format: Format) -> Canvas { 73 | Canvas { 74 | pixels: vec![0; stride * size.y() as usize], 75 | size, 76 | stride, 77 | format, 78 | } 79 | } 80 | 81 | #[allow(dead_code)] 82 | pub(crate) fn blit_from_canvas(&mut self, src: &Canvas) { 83 | self.blit_from( 84 | Vector2I::default(), 85 | &src.pixels, 86 | src.size, 87 | src.stride, 88 | src.format, 89 | ) 90 | } 91 | 92 | /// Blits to a rectangle with origin at `dst_point` and size according to `src_size`. 93 | /// If the target area overlaps the boundaries of the canvas, only the drawable region is blitted. 94 | /// `dst_point` and `src_size` are specified in pixels. `src_stride` is specified in bytes. 95 | /// `src_stride` must be equal or larger than the actual data length. 96 | #[allow(dead_code)] 97 | pub(crate) fn blit_from( 98 | &mut self, 99 | dst_point: Vector2I, 100 | src_bytes: &[u8], 101 | src_size: Vector2I, 102 | src_stride: usize, 103 | src_format: Format, 104 | ) { 105 | assert_eq!( 106 | src_stride * src_size.y() as usize, 107 | src_bytes.len(), 108 | "Number of pixels in src_bytes does not match stride and size." 109 | ); 110 | assert!( 111 | src_stride >= src_size.x() as usize * src_format.bytes_per_pixel() as usize, 112 | "src_stride must be >= than src_size.x()" 113 | ); 114 | 115 | let dst_rect = RectI::new(dst_point, src_size); 116 | let dst_rect = dst_rect.intersection(RectI::new(Vector2I::default(), self.size)); 117 | let dst_rect = match dst_rect { 118 | Some(dst_rect) => dst_rect, 119 | None => return, 120 | }; 121 | 122 | match (self.format, src_format) { 123 | (Format::A8, Format::A8) 124 | | (Format::Rgb24, Format::Rgb24) 125 | | (Format::Rgba32, Format::Rgba32) => { 126 | self.blit_from_with::(dst_rect, src_bytes, src_stride, src_format) 127 | } 128 | (Format::A8, Format::Rgb24) => { 129 | self.blit_from_with::(dst_rect, src_bytes, src_stride, src_format) 130 | } 131 | (Format::Rgb24, Format::A8) => { 132 | self.blit_from_with::(dst_rect, src_bytes, src_stride, src_format) 133 | } 134 | (Format::Rgb24, Format::Rgba32) => self 135 | .blit_from_with::(dst_rect, src_bytes, src_stride, src_format), 136 | (Format::Rgba32, Format::Rgb24) => self 137 | .blit_from_with::(dst_rect, src_bytes, src_stride, src_format), 138 | (Format::Rgba32, Format::A8) | (Format::A8, Format::Rgba32) => unimplemented!(), 139 | } 140 | } 141 | 142 | #[allow(dead_code)] 143 | pub(crate) fn blit_from_bitmap_1bpp( 144 | &mut self, 145 | dst_point: Vector2I, 146 | src_bytes: &[u8], 147 | src_size: Vector2I, 148 | src_stride: usize, 149 | ) { 150 | if self.format != Format::A8 { 151 | unimplemented!() 152 | } 153 | 154 | let dst_rect = RectI::new(dst_point, src_size); 155 | let dst_rect = dst_rect.intersection(RectI::new(Vector2I::default(), self.size)); 156 | let dst_rect = match dst_rect { 157 | Some(dst_rect) => dst_rect, 158 | None => return, 159 | }; 160 | 161 | let size = dst_rect.size(); 162 | 163 | let dest_bytes_per_pixel = self.format.bytes_per_pixel() as usize; 164 | let dest_row_stride = size.x() as usize * dest_bytes_per_pixel; 165 | let src_row_stride = utils::div_round_up(size.x() as usize, 8); 166 | 167 | for y in 0..size.y() { 168 | let (dest_row_start, src_row_start) = ( 169 | (y + dst_rect.origin_y()) as usize * self.stride 170 | + dst_rect.origin_x() as usize * dest_bytes_per_pixel, 171 | y as usize * src_stride, 172 | ); 173 | let dest_row_end = dest_row_start + dest_row_stride; 174 | let src_row_end = src_row_start + src_row_stride; 175 | let dest_row_pixels = &mut self.pixels[dest_row_start..dest_row_end]; 176 | let src_row_pixels = &src_bytes[src_row_start..src_row_end]; 177 | for x in 0..src_row_stride { 178 | let pattern = &BITMAP_1BPP_TO_8BPP_LUT[src_row_pixels[x] as usize]; 179 | let dest_start = x * 8; 180 | let dest_end = cmp::min(dest_start + 8, dest_row_stride); 181 | let src = &pattern[0..(dest_end - dest_start)]; 182 | dest_row_pixels[dest_start..dest_end].clone_from_slice(src); 183 | } 184 | } 185 | } 186 | 187 | /// Blits to area `rect` using the data given in the buffer `src_bytes`. 188 | /// `src_stride` must be specified in bytes. 189 | /// The dimensions of `rect` must be in pixels. 190 | fn blit_from_with( 191 | &mut self, 192 | rect: RectI, 193 | src_bytes: &[u8], 194 | src_stride: usize, 195 | src_format: Format, 196 | ) { 197 | let src_bytes_per_pixel = src_format.bytes_per_pixel() as usize; 198 | let dest_bytes_per_pixel = self.format.bytes_per_pixel() as usize; 199 | 200 | for y in 0..rect.height() { 201 | let (dest_row_start, src_row_start) = ( 202 | (y + rect.origin_y()) as usize * self.stride 203 | + rect.origin_x() as usize * dest_bytes_per_pixel, 204 | y as usize * src_stride, 205 | ); 206 | let dest_row_end = dest_row_start + rect.width() as usize * dest_bytes_per_pixel; 207 | let src_row_end = src_row_start + rect.width() as usize * src_bytes_per_pixel; 208 | let dest_row_pixels = &mut self.pixels[dest_row_start..dest_row_end]; 209 | let src_row_pixels = &src_bytes[src_row_start..src_row_end]; 210 | B::blit(dest_row_pixels, src_row_pixels) 211 | } 212 | } 213 | } 214 | 215 | impl fmt::Debug for Canvas { 216 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 217 | f.debug_struct("Canvas") 218 | .field("pixels", &self.pixels.len()) // Do not dump a vector content. 219 | .field("size", &self.size) 220 | .field("stride", &self.stride) 221 | .field("format", &self.format) 222 | .finish() 223 | } 224 | } 225 | 226 | /// The image format for the canvas. 227 | #[derive(Clone, Copy, Debug, PartialEq)] 228 | pub enum Format { 229 | /// Premultiplied R8G8B8A8, little-endian. 230 | Rgba32, 231 | /// R8G8B8, little-endian. 232 | Rgb24, 233 | /// A8. 234 | A8, 235 | } 236 | 237 | impl Format { 238 | /// Returns the number of bits per pixel that this image format corresponds to. 239 | #[inline] 240 | pub fn bits_per_pixel(self) -> u8 { 241 | match self { 242 | Format::Rgba32 => 32, 243 | Format::Rgb24 => 24, 244 | Format::A8 => 8, 245 | } 246 | } 247 | 248 | /// Returns the number of color channels per pixel that this image format corresponds to. 249 | #[inline] 250 | pub fn components_per_pixel(self) -> u8 { 251 | match self { 252 | Format::Rgba32 => 4, 253 | Format::Rgb24 => 3, 254 | Format::A8 => 1, 255 | } 256 | } 257 | 258 | /// Returns the number of bits per color channel that this image format contains. 259 | #[inline] 260 | pub fn bits_per_component(self) -> u8 { 261 | self.bits_per_pixel() / self.components_per_pixel() 262 | } 263 | 264 | /// Returns the number of bytes per pixel that this image format corresponds to. 265 | #[inline] 266 | pub fn bytes_per_pixel(self) -> u8 { 267 | self.bits_per_pixel() / 8 268 | } 269 | } 270 | 271 | /// The antialiasing strategy that should be used when rasterizing glyphs. 272 | #[derive(Clone, Copy, Debug, PartialEq)] 273 | pub enum RasterizationOptions { 274 | /// "Black-and-white" rendering. Each pixel is either entirely on or off. 275 | Bilevel, 276 | /// Grayscale antialiasing. Only one channel is used. 277 | GrayscaleAa, 278 | /// Subpixel RGB antialiasing, for LCD screens. 279 | SubpixelAa, 280 | } 281 | 282 | trait Blit { 283 | fn blit(dest: &mut [u8], src: &[u8]); 284 | } 285 | 286 | struct BlitMemcpy; 287 | 288 | impl Blit for BlitMemcpy { 289 | #[inline] 290 | fn blit(dest: &mut [u8], src: &[u8]) { 291 | dest.clone_from_slice(src) 292 | } 293 | } 294 | 295 | struct BlitRgb24ToA8; 296 | 297 | impl Blit for BlitRgb24ToA8 { 298 | #[inline] 299 | fn blit(dest: &mut [u8], src: &[u8]) { 300 | // TODO(pcwalton): SIMD. 301 | for (dest, src) in dest.iter_mut().zip(src.chunks(3)) { 302 | *dest = src[1] 303 | } 304 | } 305 | } 306 | 307 | struct BlitA8ToRgb24; 308 | 309 | impl Blit for BlitA8ToRgb24 { 310 | #[inline] 311 | fn blit(dest: &mut [u8], src: &[u8]) { 312 | for (dest, src) in dest.chunks_mut(3).zip(src.iter()) { 313 | dest[0] = *src; 314 | dest[1] = *src; 315 | dest[2] = *src; 316 | } 317 | } 318 | } 319 | 320 | struct BlitRgba32ToRgb24; 321 | 322 | impl Blit for BlitRgba32ToRgb24 { 323 | #[inline] 324 | fn blit(dest: &mut [u8], src: &[u8]) { 325 | // TODO(pcwalton): SIMD. 326 | for (dest, src) in dest.chunks_mut(3).zip(src.chunks(4)) { 327 | dest.copy_from_slice(&src[0..3]) 328 | } 329 | } 330 | } 331 | 332 | struct BlitRgb24ToRgba32; 333 | 334 | impl Blit for BlitRgb24ToRgba32 { 335 | fn blit(dest: &mut [u8], src: &[u8]) { 336 | for (dest, src) in dest.chunks_mut(4).zip(src.chunks(3)) { 337 | dest[0] = src[0]; 338 | dest[1] = src[1]; 339 | dest[2] = src[2]; 340 | dest[3] = 255; 341 | } 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/error.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Various types of errors that `font-kit` can return. 12 | 13 | use std::borrow::Cow; 14 | use std::convert::From; 15 | use std::error::Error; 16 | use std::io; 17 | 18 | macro_rules! impl_display { 19 | ($enum:ident, {$($variant:pat => $fmt_string:expr),+$(,)* }) => { 20 | 21 | impl ::std::fmt::Display for $enum { 22 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 23 | use self::$enum::*; 24 | match &self { 25 | $( 26 | $variant => write!(f, "{}", $fmt_string), 27 | )+ 28 | } 29 | } 30 | } 31 | }; 32 | } 33 | 34 | /// Reasons why a loader might fail to load a font. 35 | #[derive(Debug)] 36 | pub enum FontLoadingError { 37 | /// The data was of a format the loader didn't recognize. 38 | UnknownFormat, 39 | /// Attempted to load an invalid index in a TrueType or OpenType font collection. 40 | /// 41 | /// For example, if a `.ttc` file has 2 fonts in it, and you ask for the 5th one, you'll get 42 | /// this error. 43 | NoSuchFontInCollection, 44 | /// Attempted to load a malformed or corrupted font. 45 | Parse, 46 | /// Attempted to load a font from the filesystem, but there is no filesystem (e.g. in 47 | /// WebAssembly). 48 | NoFilesystem, 49 | /// A disk or similar I/O error occurred while attempting to load the font. 50 | Io(io::Error), 51 | } 52 | 53 | impl Error for FontLoadingError {} 54 | 55 | impl_display! { FontLoadingError, { 56 | UnknownFormat => "unknown format", 57 | NoSuchFontInCollection => "no such font in the collection", 58 | Parse => "parse error", 59 | NoFilesystem => "no filesystem present", 60 | Io(e) => format!("I/O error: {}", e), 61 | } 62 | } 63 | 64 | impl From for FontLoadingError { 65 | fn from(error: io::Error) -> FontLoadingError { 66 | FontLoadingError::Io(error) 67 | } 68 | } 69 | 70 | /// Reasons why a font might fail to load a glyph. 71 | #[derive(Clone, Copy, PartialEq, Debug)] 72 | pub enum GlyphLoadingError { 73 | /// The font didn't contain a glyph with that ID. 74 | NoSuchGlyph, 75 | /// A platform function returned an error. 76 | PlatformError, 77 | } 78 | 79 | impl Error for GlyphLoadingError {} 80 | 81 | impl_display! { GlyphLoadingError, { 82 | NoSuchGlyph => "no such glyph", 83 | PlatformError => "platform error", 84 | } 85 | } 86 | 87 | #[cfg(target_family = "windows")] 88 | impl From for GlyphLoadingError { 89 | fn from(_err: winapi::um::winnt::HRESULT) -> GlyphLoadingError { 90 | GlyphLoadingError::PlatformError 91 | } 92 | } 93 | 94 | /// Reasons why a source might fail to look up a font or fonts. 95 | #[derive(Clone, PartialEq, Debug)] 96 | pub enum SelectionError { 97 | /// No font matching the given query was found. 98 | NotFound, 99 | /// The source was inaccessible because of an I/O or similar error. 100 | CannotAccessSource { 101 | /// Additional diagnostic information may include file name 102 | reason: Option>, 103 | }, 104 | } 105 | 106 | impl Error for SelectionError {} 107 | 108 | impl_display! { SelectionError, { 109 | NotFound => "no font found", 110 | CannotAccessSource { reason: ref maybe_cow } => maybe_cow.as_deref().unwrap_or("failed to access source") 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/family.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/family.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Defines a set of faces that vary in weight, width or slope. 12 | 13 | use crate::error::FontLoadingError; 14 | use crate::family_handle::FamilyHandle; 15 | use crate::font::Font; 16 | use crate::handle::Handle; 17 | use crate::loader::Loader; 18 | 19 | /// Defines a set of faces that vary in weight, width or slope. 20 | #[derive(Debug)] 21 | pub struct Family 22 | where 23 | F: Loader, 24 | { 25 | fonts: Vec, 26 | } 27 | 28 | impl Family 29 | where 30 | F: Loader, 31 | { 32 | pub(crate) fn from_font_handles<'a, I>(font_handles: I) -> Result, FontLoadingError> 33 | where 34 | I: Iterator, 35 | { 36 | let mut fonts = vec![]; 37 | for font_handle in font_handles { 38 | fonts.push(F::from_handle(font_handle)?) 39 | } 40 | Ok(Family { fonts }) 41 | } 42 | 43 | #[inline] 44 | pub(crate) fn from_handle(family_handle: &FamilyHandle) -> Result, FontLoadingError> { 45 | Family::from_font_handles(family_handle.fonts.iter()) 46 | } 47 | 48 | /// Returns the individual fonts in this family. 49 | #[inline] 50 | pub fn fonts(&self) -> &[F] { 51 | &self.fonts 52 | } 53 | 54 | /// Returns true if and only if this family is empty. 55 | #[inline] 56 | pub fn is_empty(&self) -> bool { 57 | self.fonts.is_empty() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/family_handle.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/family_handle.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Encapsulates the information needed to locate and open the fonts in a family. 12 | 13 | use crate::handle::Handle; 14 | 15 | /// Encapsulates the information needed to locate and open the fonts in a family. 16 | #[derive(Debug)] 17 | pub struct FamilyHandle { 18 | pub(crate) fonts: Vec, 19 | } 20 | 21 | impl Default for FamilyHandle { 22 | fn default() -> Self { 23 | Self::new() 24 | } 25 | } 26 | 27 | impl FamilyHandle { 28 | /// Creates an empty set of family handles. 29 | #[inline] 30 | pub fn new() -> FamilyHandle { 31 | FamilyHandle { fonts: vec![] } 32 | } 33 | 34 | /// Creates a set of font family handles. 35 | #[inline] 36 | pub fn from_font_handles(fonts: I) -> FamilyHandle 37 | where 38 | I: Iterator, 39 | { 40 | FamilyHandle { 41 | fonts: fonts.collect::>(), 42 | } 43 | } 44 | 45 | /// Adds a new handle to this set. 46 | #[inline] 47 | pub fn push(&mut self, font: Handle) { 48 | self.fonts.push(font) 49 | } 50 | 51 | /// Returns true if and only if this set has no fonts in it. 52 | #[inline] 53 | pub fn is_empty(&self) -> bool { 54 | self.fonts.is_empty() 55 | } 56 | 57 | /// Returns all the handles in this set. 58 | #[inline] 59 | pub fn fonts(&self) -> &[Handle] { 60 | &self.fonts 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/family_name.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/descriptor.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! A possible value for the `font-family` CSS property. 12 | 13 | /// A possible value for the `font-family` CSS property. 14 | /// 15 | /// These descriptions are taken from CSS Fonts Level 3 § 3.1: 16 | /// . 17 | /// 18 | /// TODO(pcwalton): `system-ui`, `emoji`, `math`, `fangsong` 19 | #[derive(Clone, Debug, PartialEq, Hash)] 20 | pub enum FamilyName { 21 | /// A specific font family, specified by name: e.g. "Arial", "times". 22 | Title(String), 23 | /// Serif fonts represent the formal text style for a script. 24 | Serif, 25 | /// Glyphs in sans-serif fonts, as the term is used in CSS, are generally low contrast 26 | /// (vertical and horizontal stems have the close to the same thickness) and have stroke 27 | /// endings that are plain — without any flaring, cross stroke, or other ornamentation. 28 | SansSerif, 29 | /// The sole criterion of a monospace font is that all glyphs have the same fixed width. 30 | Monospace, 31 | /// Glyphs in cursive fonts generally use a more informal script style, and the result looks 32 | /// more like handwritten pen or brush writing than printed letterwork. 33 | Cursive, 34 | /// Fantasy fonts are primarily decorative or expressive fonts that contain decorative or 35 | /// expressive representations of characters. 36 | Fantasy, 37 | } 38 | -------------------------------------------------------------------------------- /src/file_type.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/file_type.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! The type of a font file: either a single font or a TrueType/OpenType collection. 12 | 13 | /// The type of a font file: either a single font or a TrueType/OpenType collection. 14 | #[derive(Clone, Copy, Debug, PartialEq)] 15 | pub enum FileType { 16 | /// The font file represents a single font (`.ttf`, `.otf`, `.woff`, etc.) 17 | Single, 18 | /// The font file represents a collection of fonts (`.ttc`, `.otc`, etc.) 19 | Collection(u32), 20 | } 21 | -------------------------------------------------------------------------------- /src/font.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/font.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! A font face loaded into memory. 12 | //! 13 | //! The Font type in this crate represents the default loader. 14 | 15 | pub use crate::loaders::default::Font; 16 | -------------------------------------------------------------------------------- /src/handle.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/handle.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Encapsulates the information needed to locate and open a font. 12 | //! 13 | //! This is either the path to the font or the raw in-memory font data. 14 | //! 15 | //! To open the font referenced by a handle, use a loader. 16 | 17 | use std::path::PathBuf; 18 | use std::sync::Arc; 19 | 20 | use crate::error::FontLoadingError; 21 | use crate::font::Font; 22 | 23 | /// Encapsulates the information needed to locate and open a font. 24 | /// 25 | /// This is either the path to the font or the raw in-memory font data. 26 | /// 27 | /// To open the font referenced by a handle, use a loader. 28 | #[derive(Debug, Clone)] 29 | pub enum Handle { 30 | /// A font on disk referenced by a path. 31 | Path { 32 | /// The path to the font. 33 | path: PathBuf, 34 | /// The index of the font, if the path refers to a collection. 35 | /// 36 | /// If the path refers to a single font, this value will be 0. 37 | font_index: u32, 38 | }, 39 | /// A font in memory. 40 | Memory { 41 | /// The raw TrueType/OpenType/etc. data that makes up this font. 42 | bytes: Arc>, 43 | /// The index of the font, if the memory consists of a collection. 44 | /// 45 | /// If the memory consists of a single font, this value will be 0. 46 | font_index: u32, 47 | }, 48 | } 49 | 50 | impl Handle { 51 | /// Creates a new handle from a path. 52 | /// 53 | /// `font_index` specifies the index of the font to choose if the path points to a font 54 | /// collection. If the path points to a single font file, pass 0. 55 | #[inline] 56 | pub fn from_path(path: PathBuf, font_index: u32) -> Handle { 57 | Handle::Path { path, font_index } 58 | } 59 | 60 | /// Creates a new handle from raw TTF/OTF/etc. data in memory. 61 | /// 62 | /// `font_index` specifies the index of the font to choose if the memory represents a font 63 | /// collection. If the memory represents a single font file, pass 0. 64 | #[inline] 65 | pub fn from_memory(bytes: Arc>, font_index: u32) -> Handle { 66 | Handle::Memory { bytes, font_index } 67 | } 68 | 69 | /// A convenience method to load this handle with the default loader, producing a Font. 70 | #[inline] 71 | pub fn load(&self) -> Result { 72 | Font::from_handle(self) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/hinting.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/hinting.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Specifies how hinting (grid fitting) is to be performed (or not performed) for a glyph. 12 | //! 13 | //! This affects both outlines and rasterization. 14 | 15 | /// Specifies how hinting (grid fitting) is to be performed (or not performed) for a glyph. 16 | /// 17 | /// This affects both outlines and rasterization. 18 | #[derive(Clone, Copy, Debug, PartialEq)] 19 | pub enum HintingOptions { 20 | /// No hinting is performed unless absolutely necessary to assemble the glyph. 21 | /// 22 | /// This corresponds to what macOS and FreeType in its "no hinting" mode do. 23 | None, 24 | 25 | /// Hinting is performed only in the vertical direction. The specified point size is used for 26 | /// grid fitting. 27 | /// 28 | /// This corresponds to what DirectWrite and FreeType in its light hinting mode do. 29 | Vertical(f32), 30 | 31 | /// Hinting is performed only in the vertical direction, and further tweaks are applied to make 32 | /// subpixel antialiasing look better. The specified point size is used for grid fitting. 33 | /// 34 | /// This matches DirectWrite, GDI in its ClearType mode, and FreeType in its LCD hinting mode. 35 | VerticalSubpixel(f32), 36 | 37 | /// Hinting is performed in both horizontal and vertical directions. The specified point size 38 | /// is used for grid fitting. 39 | /// 40 | /// This corresponds to what GDI in non-ClearType modes and FreeType in its normal hinting mode 41 | /// do. 42 | Full(f32), 43 | } 44 | 45 | impl HintingOptions { 46 | /// Returns the point size that will be used for grid fitting, if any. 47 | #[inline] 48 | pub fn grid_fitting_size(&self) -> Option { 49 | match *self { 50 | HintingOptions::None => None, 51 | HintingOptions::Vertical(size) 52 | | HintingOptions::VerticalSubpixel(size) 53 | | HintingOptions::Full(size) => Some(size), 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/lib.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! `font-kit` provides a common interface to the various system font libraries and provides 12 | //! services such as finding fonts on the system, performing nearest-font matching, and rasterizing 13 | //! glyphs. 14 | //! 15 | //! ## Synopsis 16 | //! 17 | //! # extern crate font_kit; 18 | //! # extern crate pathfinder_geometry; 19 | //! # 20 | //! use font_kit::canvas::{Canvas, Format, RasterizationOptions}; 21 | //! use font_kit::family_name::FamilyName; 22 | //! use font_kit::hinting::HintingOptions; 23 | //! use font_kit::properties::Properties; 24 | //! use font_kit::source::SystemSource; 25 | //! use pathfinder_geometry::transform2d::Transform2F; 26 | //! use pathfinder_geometry::vector::{Vector2F, Vector2I}; 27 | //! 28 | //! let font = SystemSource::new().select_best_match(&[FamilyName::SansSerif], 29 | //! &Properties::new()) 30 | //! .unwrap() 31 | //! .load() 32 | //! .unwrap(); 33 | //! let glyph_id = font.glyph_for_char('A').unwrap(); 34 | //! let mut canvas = Canvas::new(Vector2I::splat(32), Format::A8); 35 | //! font.rasterize_glyph(&mut canvas, 36 | //! glyph_id, 37 | //! 32.0, 38 | //! Transform2F::from_translation(Vector2F::new(0.0, 32.0)), 39 | //! HintingOptions::None, 40 | //! RasterizationOptions::GrayscaleAa) 41 | //! .unwrap(); 42 | //! 43 | //! ## Backends 44 | //! 45 | //! `font-kit` delegates to system libraries to perform tasks. It has two types of backends: a 46 | //! *source* and a *loader*. Sources are platform font databases; they allow lookup of installed 47 | //! fonts by name or attributes. Loaders are font loading libraries; they allow font files (TTF, 48 | //! OTF, etc.) to be loaded from a file on disk or from bytes in memory. Sources and loaders can be 49 | //! freely intermixed at runtime; fonts can be looked up via DirectWrite and rendered via FreeType, 50 | //! for example. 51 | //! 52 | //! Available loaders: 53 | //! 54 | //! * Core Text (macOS): The system font loader on macOS. Does not do hinting except when bilevel 55 | //! rendering is in use. 56 | //! 57 | //! * DirectWrite (Windows): The newer system framework for text rendering on Windows. Does 58 | //! vertical hinting but not full hinting. 59 | //! 60 | //! * FreeType (cross-platform): A full-featured font rendering framework. 61 | //! 62 | //! Available sources: 63 | //! 64 | //! * Core Text (macOS): The system font database on macOS. 65 | //! 66 | //! * DirectWrite (Windows): The newer API to query the system font database on Windows. 67 | //! 68 | //! * Fontconfig (cross-platform): A technically platform-neutral, but in practice Unix-specific, 69 | //! API to query and match fonts. 70 | //! 71 | //! * Filesystem (cross-platform): A simple source that reads fonts from a path on disk. This is 72 | //! the default on Android and OpenHarmony. 73 | //! 74 | //! * Memory (cross-platform): A source that reads from a fixed set of fonts in memory. 75 | //! 76 | //! * Multi (cross-platform): A source that allows multiple sources to be queried at once. 77 | //! 78 | //! On Windows and macOS, the FreeType loader and the Fontconfig source are not built by default. 79 | //! To build them, use the `loader-freetype` and `source-fontconfig` Cargo features respectively. 80 | //! If you want them to be the default, instead use the `loader-freetype-default` and 81 | //! `source-fontconfig-default` Cargo features respectively. Beware that 82 | //! `source-fontconfig-default` is rarely what you want on those two platforms! 83 | //! 84 | //! ## Features 85 | //! 86 | //! `font-kit` is capable of doing the following: 87 | //! 88 | //! * Loading fonts from files or memory. 89 | //! 90 | //! * Determining whether files on disk or in memory represent fonts. 91 | //! 92 | //! * Interoperating with native font APIs. 93 | //! 94 | //! * Querying various metadata about fonts. 95 | //! 96 | //! * Doing simple glyph-to-character mapping. (For more complex use cases, a shaper is required; 97 | //! proper shaping is beyond the scope of `font-kit`.) 98 | //! 99 | //! * Reading unhinted or hinted vector outlines from glyphs. 100 | //! 101 | //! * Calculating glyph and font metrics. 102 | //! 103 | //! * Looking up glyph advances and origins. 104 | //! 105 | //! * Rasterizing glyphs using the native rasterizer, optionally using hinting. (Custom 106 | //! rasterizers, such as Pathfinder, can be used in conjunction with the outline API.) 107 | //! 108 | //! * Looking up all fonts on the system. 109 | //! 110 | //! * Searching for specific fonts by family or PostScript name. 111 | //! 112 | //! * Performing font matching according to the [CSS Fonts Module Level 3] specification. 113 | //! 114 | //! ## License 115 | //! 116 | //! `font-kit` is licensed under the same terms as Rust itself. 117 | //! 118 | //! [CSS Fonts Module Level 3]: https://drafts.csswg.org/css-fonts-3/#font-matching-algorithm 119 | 120 | #![warn(missing_docs)] 121 | #![warn(missing_debug_implementations)] 122 | #![warn(missing_copy_implementations)] 123 | 124 | #[macro_use] 125 | extern crate bitflags; 126 | 127 | pub mod canvas; 128 | pub mod error; 129 | pub mod family; 130 | pub mod family_handle; 131 | pub mod family_name; 132 | pub mod file_type; 133 | pub mod font; 134 | pub mod handle; 135 | pub mod hinting; 136 | pub mod loader; 137 | pub mod loaders; 138 | pub mod metrics; 139 | pub mod outline; 140 | pub mod properties; 141 | 142 | #[cfg(feature = "source")] 143 | pub mod source; 144 | #[cfg(feature = "source")] 145 | pub mod sources; 146 | 147 | mod matching; 148 | mod utils; 149 | -------------------------------------------------------------------------------- /src/loader.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/loader.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Provides a common interface to the platform-specific API that loads, parses, and rasterizes 12 | //! fonts. 13 | 14 | use log::warn; 15 | use pathfinder_geometry::rect::{RectF, RectI}; 16 | use pathfinder_geometry::transform2d::Transform2F; 17 | use pathfinder_geometry::vector::Vector2F; 18 | use std::sync::Arc; 19 | 20 | use crate::canvas::{Canvas, RasterizationOptions}; 21 | use crate::error::{FontLoadingError, GlyphLoadingError}; 22 | use crate::file_type::FileType; 23 | use crate::handle::Handle; 24 | use crate::hinting::HintingOptions; 25 | use crate::metrics::Metrics; 26 | use crate::outline::OutlineSink; 27 | use crate::properties::Properties; 28 | 29 | #[cfg(not(target_arch = "wasm32"))] 30 | use std::fs::File; 31 | #[cfg(not(target_arch = "wasm32"))] 32 | use std::path::Path; 33 | 34 | /// Provides a common interface to the platform-specific API that loads, parses, and rasterizes 35 | /// fonts. 36 | pub trait Loader: Clone + Sized { 37 | /// The handle that the API natively uses to represent a font. 38 | type NativeFont; 39 | 40 | /// Loads a font from raw font data (the contents of a `.ttf`/`.otf`/etc. file). 41 | /// 42 | /// If the data represents a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index 43 | /// of the font to load from it. If the data represents a single font, pass 0 for `font_index`. 44 | fn from_bytes(font_data: Arc>, font_index: u32) -> Result; 45 | 46 | /// Loads a font from a `.ttf`/`.otf`/etc. file. 47 | /// 48 | /// If the file is a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index of the 49 | /// font to load from it. If the file represents a single font, pass 0 for `font_index`. 50 | #[cfg(not(target_arch = "wasm32"))] 51 | fn from_file(file: &mut File, font_index: u32) -> Result; 52 | 53 | /// Loads a font from the path to a `.ttf`/`.otf`/etc. file. 54 | /// 55 | /// If the file is a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index of the 56 | /// font to load from it. If the file represents a single font, pass 0 for `font_index`. 57 | #[cfg(not(target_arch = "wasm32"))] 58 | fn from_path

(path: P, font_index: u32) -> Result 59 | where 60 | P: AsRef, 61 | { 62 | Loader::from_file(&mut File::open(path)?, font_index) 63 | } 64 | 65 | /// Creates a font from a native API handle. 66 | unsafe fn from_native_font(native_font: Self::NativeFont) -> Self; 67 | 68 | /// Loads the font pointed to by a handle. 69 | fn from_handle(handle: &Handle) -> Result { 70 | match *handle { 71 | Handle::Memory { 72 | ref bytes, 73 | font_index, 74 | } => Self::from_bytes((*bytes).clone(), font_index), 75 | #[cfg(not(target_arch = "wasm32"))] 76 | Handle::Path { 77 | ref path, 78 | font_index, 79 | } => Self::from_path(path, font_index), 80 | #[cfg(target_arch = "wasm32")] 81 | Handle::Path { .. } => Err(FontLoadingError::NoFilesystem), 82 | } 83 | } 84 | 85 | /// Determines whether a blob of raw font data represents a supported font, and, if so, what 86 | /// type of font it is. 87 | fn analyze_bytes(font_data: Arc>) -> Result; 88 | 89 | /// Determines whether a file represents a supported font, and, if so, what type of font it is. 90 | #[cfg(not(target_arch = "wasm32"))] 91 | fn analyze_file(file: &mut File) -> Result; 92 | 93 | /// Determines whether a path points to a supported font, and, if so, what type of font it is. 94 | #[inline] 95 | #[cfg(not(target_arch = "wasm32"))] 96 | fn analyze_path

(path: P) -> Result 97 | where 98 | P: AsRef, 99 | { 100 | ::analyze_file(&mut File::open(path)?) 101 | } 102 | 103 | /// Returns the wrapped native font handle. 104 | fn native_font(&self) -> Self::NativeFont; 105 | 106 | /// Returns the PostScript name of the font. This should be globally unique. 107 | fn postscript_name(&self) -> Option; 108 | 109 | /// Returns the full name of the font (also known as "display name" on macOS). 110 | fn full_name(&self) -> String; 111 | 112 | /// Returns the name of the font family. 113 | fn family_name(&self) -> String; 114 | 115 | /// Returns true if and only if the font is monospace (fixed-width). 116 | fn is_monospace(&self) -> bool; 117 | 118 | /// Returns the values of various font properties, corresponding to those defined in CSS. 119 | fn properties(&self) -> Properties; 120 | 121 | /// Returns the number of glyphs in the font. 122 | /// 123 | /// Glyph IDs range from 0 inclusive to this value exclusive. 124 | fn glyph_count(&self) -> u32; 125 | 126 | /// Returns the usual glyph ID for a Unicode character. 127 | /// 128 | /// Be careful with this function; typographically correct character-to-glyph mapping must be 129 | /// done using a *shaper* such as HarfBuzz. This function is only useful for best-effort simple 130 | /// use cases like "what does character X look like on its own". 131 | fn glyph_for_char(&self, character: char) -> Option; 132 | 133 | /// Returns the glyph ID for the specified glyph name. 134 | #[inline] 135 | fn glyph_by_name(&self, _name: &str) -> Option { 136 | warn!("unimplemented"); 137 | None 138 | } 139 | 140 | /// Sends the vector path for a glyph to a sink. 141 | /// 142 | /// If `hinting_mode` is not None, this function performs grid-fitting as requested before 143 | /// sending the hinding outlines to the builder. 144 | /// 145 | /// TODO(pcwalton): What should we do for bitmap glyphs? 146 | fn outline( 147 | &self, 148 | glyph_id: u32, 149 | hinting_mode: HintingOptions, 150 | sink: &mut S, 151 | ) -> Result<(), GlyphLoadingError> 152 | where 153 | S: OutlineSink; 154 | 155 | /// Returns the boundaries of a glyph in font units. The origin of the coordinate 156 | /// space is at the bottom left. 157 | fn typographic_bounds(&self, glyph_id: u32) -> Result; 158 | 159 | /// Returns the distance from the origin of the glyph with the given ID to the next, in font 160 | /// units. 161 | fn advance(&self, glyph_id: u32) -> Result; 162 | 163 | /// Returns the amount that the given glyph should be displaced from the origin. 164 | fn origin(&self, glyph_id: u32) -> Result; 165 | 166 | /// Retrieves various metrics that apply to the entire font. 167 | fn metrics(&self) -> Metrics; 168 | 169 | /// Returns a handle to this font, if possible. 170 | /// 171 | /// This is useful if you want to open the font with a different loader. 172 | fn handle(&self) -> Option { 173 | // FIXME(pcwalton): This doesn't handle font collections! 174 | self.copy_font_data() 175 | .map(|font_data| Handle::from_memory(font_data, 0)) 176 | } 177 | 178 | /// Attempts to return the raw font data (contents of the font file). 179 | /// 180 | /// If this font is a member of a collection, this function returns the data for the entire 181 | /// collection. 182 | fn copy_font_data(&self) -> Option>>; 183 | 184 | /// Returns true if and only if the font loader can perform hinting in the requested way. 185 | /// 186 | /// Some APIs support only rasterizing glyphs with hinting, not retrieving hinted outlines. If 187 | /// `for_rasterization` is false, this function returns true if and only if the loader supports 188 | /// retrieval of hinted *outlines*. If `for_rasterization` is true, this function returns true 189 | /// if and only if the loader supports *rasterizing* hinted glyphs. 190 | fn supports_hinting_options( 191 | &self, 192 | hinting_options: HintingOptions, 193 | for_rasterization: bool, 194 | ) -> bool; 195 | 196 | /// Returns the pixel boundaries that the glyph will take up when rendered using this loader's 197 | /// rasterizer at the given `point_size` and `transform`. The origin of the coordinate space is 198 | /// at the top left. 199 | fn raster_bounds( 200 | &self, 201 | glyph_id: u32, 202 | point_size: f32, 203 | transform: Transform2F, 204 | _: HintingOptions, 205 | _: RasterizationOptions, 206 | ) -> Result { 207 | let typographic_bounds = self.typographic_bounds(glyph_id)?; 208 | let typographic_raster_bounds = 209 | typographic_bounds * (point_size / self.metrics().units_per_em as f32); 210 | 211 | // Translate the origin to "origin is top left" coordinate system. 212 | let new_origin = Vector2F::new( 213 | typographic_raster_bounds.origin_x(), 214 | -typographic_raster_bounds.origin_y() - typographic_raster_bounds.height(), 215 | ); 216 | let typographic_raster_bounds = RectF::new(new_origin, typographic_raster_bounds.size()); 217 | Ok((transform * typographic_raster_bounds).round_out().to_i32()) 218 | } 219 | 220 | /// Rasterizes a glyph to a canvas with the given size and transform. 221 | /// 222 | /// Format conversion will be performed if the canvas format does not match the rasterization 223 | /// options. For example, if bilevel (black and white) rendering is requested to an RGBA 224 | /// surface, this function will automatically convert the 1-bit raster image to the 32-bit 225 | /// format of the canvas. Note that this may result in a performance penalty, depending on the 226 | /// loader. 227 | /// 228 | /// If `hinting_options` is not None, the requested grid fitting is performed. 229 | fn rasterize_glyph( 230 | &self, 231 | canvas: &mut Canvas, 232 | glyph_id: u32, 233 | point_size: f32, 234 | transform: Transform2F, 235 | hinting_options: HintingOptions, 236 | rasterization_options: RasterizationOptions, 237 | ) -> Result<(), GlyphLoadingError>; 238 | 239 | /// Get font fallback results for the given text and locale. 240 | /// 241 | /// The `locale` argument is a language tag such as `"en-US"` or `"zh-Hans-CN"`. 242 | fn get_fallbacks(&self, text: &str, locale: &str) -> FallbackResult; 243 | 244 | /// Returns the OpenType font table with the given tag, if the table exists. 245 | fn load_font_table(&self, table_tag: u32) -> Option>; 246 | } 247 | 248 | /// The result of a fallback query. 249 | #[derive(Debug)] 250 | pub struct FallbackResult { 251 | /// A list of fallback fonts. 252 | pub fonts: Vec>, 253 | /// The fallback list is valid for this slice of the given text. 254 | pub valid_len: usize, 255 | } 256 | 257 | /// A single font record for a fallback query result. 258 | #[derive(Debug)] 259 | pub struct FallbackFont { 260 | /// The font. 261 | pub font: Font, 262 | /// A scale factor that should be applied to the fallback font. 263 | pub scale: f32, 264 | // TODO: add font simulation data 265 | } 266 | -------------------------------------------------------------------------------- /src/loaders/mod.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/loaders/mod.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! The different system services that can load and rasterize fonts. 12 | 13 | #[cfg(all( 14 | any(target_os = "macos", target_os = "ios"), 15 | not(feature = "loader-freetype-default") 16 | ))] 17 | pub use crate::loaders::core_text as default; 18 | 19 | #[cfg(all(target_family = "windows", not(feature = "loader-freetype-default")))] 20 | pub use crate::loaders::directwrite as default; 21 | 22 | #[cfg(any( 23 | not(any(target_os = "macos", target_os = "ios", target_family = "windows")), 24 | feature = "loader-freetype-default" 25 | ))] 26 | pub use crate::loaders::freetype as default; 27 | 28 | #[cfg(any(target_os = "macos", target_os = "ios"))] 29 | pub mod core_text; 30 | 31 | #[cfg(target_family = "windows")] 32 | pub mod directwrite; 33 | 34 | #[cfg(any( 35 | not(any(target_os = "macos", target_os = "ios", target_family = "windows")), 36 | feature = "loader-freetype" 37 | ))] 38 | pub mod freetype; 39 | -------------------------------------------------------------------------------- /src/matching.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/matching.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Determines the closest font matching a description per the CSS Fonts Level 3 specification. 12 | 13 | use float_ord::FloatOrd; 14 | 15 | use crate::error::SelectionError; 16 | use crate::properties::{Properties, Stretch, Style, Weight}; 17 | 18 | /// This follows CSS Fonts Level 3 § 5.2 [1]. 19 | /// 20 | /// https://drafts.csswg.org/css-fonts-3/#font-style-matching 21 | pub fn find_best_match( 22 | candidates: &[Properties], 23 | query: &Properties, 24 | ) -> Result { 25 | // Step 4. 26 | let mut matching_set: Vec = (0..candidates.len()).collect(); 27 | if matching_set.is_empty() { 28 | return Err(SelectionError::NotFound); 29 | } 30 | 31 | // Step 4a (`font-stretch`). 32 | let matching_stretch = if matching_set 33 | .iter() 34 | .any(|&index| candidates[index].stretch == query.stretch) 35 | { 36 | // Exact match. 37 | query.stretch 38 | } else if query.stretch <= Stretch::NORMAL { 39 | // Closest width, first checking narrower values and then wider values. 40 | match matching_set 41 | .iter() 42 | .filter(|&&index| candidates[index].stretch < query.stretch) 43 | .min_by_key(|&&index| FloatOrd(query.stretch.0 - candidates[index].stretch.0)) 44 | { 45 | Some(&matching_index) => candidates[matching_index].stretch, 46 | None => { 47 | let matching_index = *matching_set 48 | .iter() 49 | .min_by_key(|&&index| FloatOrd(candidates[index].stretch.0 - query.stretch.0)) 50 | .unwrap(); 51 | candidates[matching_index].stretch 52 | } 53 | } 54 | } else { 55 | // Closest width, first checking wider values and then narrower values. 56 | match matching_set 57 | .iter() 58 | .filter(|&&index| candidates[index].stretch > query.stretch) 59 | .min_by_key(|&&index| FloatOrd(candidates[index].stretch.0 - query.stretch.0)) 60 | { 61 | Some(&matching_index) => candidates[matching_index].stretch, 62 | None => { 63 | let matching_index = *matching_set 64 | .iter() 65 | .min_by_key(|&&index| FloatOrd(query.stretch.0 - candidates[index].stretch.0)) 66 | .unwrap(); 67 | candidates[matching_index].stretch 68 | } 69 | } 70 | }; 71 | matching_set.retain(|&index| candidates[index].stretch == matching_stretch); 72 | 73 | // Step 4b (`font-style`). 74 | let style_preference = match query.style { 75 | Style::Italic => [Style::Italic, Style::Oblique, Style::Normal], 76 | Style::Oblique => [Style::Oblique, Style::Italic, Style::Normal], 77 | Style::Normal => [Style::Normal, Style::Oblique, Style::Italic], 78 | }; 79 | let matching_style = *style_preference 80 | .iter() 81 | .find(|&query_style| { 82 | matching_set 83 | .iter() 84 | .any(|&index| candidates[index].style == *query_style) 85 | }) 86 | .unwrap(); 87 | matching_set.retain(|&index| candidates[index].style == matching_style); 88 | 89 | // Step 4c (`font-weight`). 90 | // 91 | // The spec doesn't say what to do if the weight is between 400 and 500 exclusive, so we 92 | // just use 450 as the cutoff. 93 | let matching_weight = if matching_set 94 | .iter() 95 | .any(|&index| candidates[index].weight == query.weight) 96 | { 97 | query.weight 98 | } else if query.weight >= Weight(400.0) 99 | && query.weight < Weight(450.0) 100 | && matching_set 101 | .iter() 102 | .any(|&index| candidates[index].weight == Weight(500.0)) 103 | { 104 | // Check 500 first. 105 | Weight(500.0) 106 | } else if query.weight >= Weight(450.0) 107 | && query.weight <= Weight(500.0) 108 | && matching_set 109 | .iter() 110 | .any(|&index| candidates[index].weight == Weight(400.0)) 111 | { 112 | // Check 400 first. 113 | Weight(400.0) 114 | } else if query.weight <= Weight(500.0) { 115 | // Closest weight, first checking thinner values and then fatter ones. 116 | match matching_set 117 | .iter() 118 | .filter(|&&index| candidates[index].weight <= query.weight) 119 | .min_by_key(|&&index| FloatOrd(query.weight.0 - candidates[index].weight.0)) 120 | { 121 | Some(&matching_index) => candidates[matching_index].weight, 122 | None => { 123 | let matching_index = *matching_set 124 | .iter() 125 | .min_by_key(|&&index| FloatOrd(candidates[index].weight.0 - query.weight.0)) 126 | .unwrap(); 127 | candidates[matching_index].weight 128 | } 129 | } 130 | } else { 131 | // Closest weight, first checking fatter values and then thinner ones. 132 | match matching_set 133 | .iter() 134 | .filter(|&&index| candidates[index].weight >= query.weight) 135 | .min_by_key(|&&index| FloatOrd(candidates[index].weight.0 - query.weight.0)) 136 | { 137 | Some(&matching_index) => candidates[matching_index].weight, 138 | None => { 139 | let matching_index = *matching_set 140 | .iter() 141 | .min_by_key(|&&index| FloatOrd(query.weight.0 - candidates[index].weight.0)) 142 | .unwrap(); 143 | candidates[matching_index].weight 144 | } 145 | } 146 | }; 147 | matching_set.retain(|&index| candidates[index].weight == matching_weight); 148 | 149 | // Step 4d concerns `font-size`, but fonts in `font-kit` are unsized, so we ignore that. 150 | 151 | // Return the result. 152 | matching_set 153 | .into_iter() 154 | .next() 155 | .ok_or(SelectionError::NotFound) 156 | } 157 | -------------------------------------------------------------------------------- /src/metrics.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/metrics.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Various metrics that apply to the entire font. 12 | //! 13 | //! For OpenType fonts, these mostly come from the `OS/2` table. 14 | 15 | use pathfinder_geometry::rect::RectF; 16 | 17 | /// Various metrics that apply to the entire font. 18 | /// 19 | /// For OpenType fonts, these mostly come from the `OS/2` table. 20 | #[derive(Clone, Copy, Debug)] 21 | pub struct Metrics { 22 | /// The number of font units per em. 23 | /// 24 | /// Font sizes are usually expressed in pixels per em; e.g. `12px` means 12 pixels per em. 25 | pub units_per_em: u32, 26 | 27 | /// The maximum amount the font rises above the baseline, in font units. 28 | pub ascent: f32, 29 | 30 | /// The maximum amount the font descends below the baseline, in font units. 31 | /// 32 | /// NB: This is typically a negative value to match the definition of `sTypoDescender` in the 33 | /// `OS/2` table in the OpenType specification. If you are used to using Windows or Mac APIs, 34 | /// beware, as the sign is reversed from what those APIs return. 35 | pub descent: f32, 36 | 37 | /// Distance between baselines, in font units. 38 | pub line_gap: f32, 39 | 40 | /// The suggested distance of the top of the underline from the baseline (negative values 41 | /// indicate below baseline), in font units. 42 | pub underline_position: f32, 43 | 44 | /// A suggested value for the underline thickness, in font units. 45 | pub underline_thickness: f32, 46 | 47 | /// The approximate amount that uppercase letters rise above the baseline, in font units. 48 | pub cap_height: f32, 49 | 50 | /// The approximate amount that non-ascending lowercase letters rise above the baseline, in 51 | /// font units. 52 | pub x_height: f32, 53 | 54 | /// A rectangle that surrounds all bounding boxes of all glyphs, in font units. 55 | /// 56 | /// This corresponds to the `xMin`/`xMax`/`yMin`/`yMax` values in the OpenType `head` table. 57 | pub bounding_box: RectF, 58 | } 59 | -------------------------------------------------------------------------------- /src/outline.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/outline.rs 2 | // 3 | // Copyright © 2020 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Bézier paths. 12 | 13 | use pathfinder_geometry::line_segment::LineSegment2F; 14 | use pathfinder_geometry::vector::Vector2F; 15 | use std::mem; 16 | 17 | /// Receives Bézier path rendering commands. 18 | pub trait OutlineSink { 19 | /// Moves the pen to a point. 20 | fn move_to(&mut self, to: Vector2F); 21 | /// Draws a line to a point. 22 | fn line_to(&mut self, to: Vector2F); 23 | /// Draws a quadratic Bézier curve to a point. 24 | fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F); 25 | /// Draws a cubic Bézier curve to a point. 26 | fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F); 27 | /// Closes the path, returning to the first point in it. 28 | fn close(&mut self); 29 | } 30 | 31 | /// A glyph vector outline or path. 32 | #[derive(Clone, PartialEq, Debug)] 33 | pub struct Outline { 34 | /// The individual subpaths that make up this outline. 35 | pub contours: Vec, 36 | } 37 | 38 | /// A single curve or subpath within a glyph outline. 39 | #[derive(Clone, PartialEq, Debug)] 40 | pub struct Contour { 41 | /// Positions of each point. 42 | /// 43 | /// This must have the same length as the `flags` field. 44 | pub positions: Vec, 45 | /// Flags that specify what type of point the corresponding position represents. 46 | /// 47 | /// This must have the same length as the `positions` field. 48 | pub flags: Vec, 49 | } 50 | 51 | bitflags! { 52 | /// Flags that specify what type of point the corresponding position represents. 53 | #[derive(Clone, Debug, PartialEq)] 54 | pub struct PointFlags: u8 { 55 | /// This point is the control point of a quadratic Bézier curve or the first control point 56 | /// of a cubic Bézier curve. 57 | /// 58 | /// This flag is mutually exclusive with `CONTROL_POINT_1`. 59 | const CONTROL_POINT_0 = 0x01; 60 | /// This point is the second control point of a cubic Bézier curve. 61 | /// 62 | /// This flag is mutually exclusive with `CONTROL_POINT_0`. 63 | const CONTROL_POINT_1 = 0x02; 64 | } 65 | } 66 | 67 | /// Accumulates Bézier path rendering commands into an `Outline` structure. 68 | #[derive(Clone, Debug)] 69 | pub struct OutlineBuilder { 70 | outline: Outline, 71 | current_contour: Contour, 72 | } 73 | 74 | impl Default for Outline { 75 | fn default() -> Self { 76 | Self::new() 77 | } 78 | } 79 | 80 | impl Outline { 81 | /// Creates a new empty outline. 82 | #[inline] 83 | pub fn new() -> Outline { 84 | Outline { contours: vec![] } 85 | } 86 | 87 | /// Sends this outline to an `OutlineSink`. 88 | pub fn copy_to(&self, sink: &mut S) 89 | where 90 | S: OutlineSink, 91 | { 92 | for contour in &self.contours { 93 | contour.copy_to(sink); 94 | } 95 | } 96 | } 97 | 98 | impl Default for Contour { 99 | fn default() -> Self { 100 | Self::new() 101 | } 102 | } 103 | 104 | impl Contour { 105 | /// Creates a new empty contour. 106 | #[inline] 107 | pub fn new() -> Contour { 108 | Contour { 109 | positions: vec![], 110 | flags: vec![], 111 | } 112 | } 113 | 114 | /// Adds a new point with the given flags to the contour. 115 | #[inline] 116 | pub fn push(&mut self, position: Vector2F, flags: PointFlags) { 117 | self.positions.push(position); 118 | self.flags.push(flags); 119 | } 120 | 121 | /// Sends this contour to an `OutlineSink`. 122 | pub fn copy_to(&self, sink: &mut S) 123 | where 124 | S: OutlineSink, 125 | { 126 | debug_assert_eq!(self.positions.len(), self.flags.len()); 127 | if self.positions.is_empty() { 128 | return; 129 | } 130 | sink.move_to(self.positions[0]); 131 | 132 | let mut iter = self.positions[1..].iter().zip(self.flags[1..].iter()); 133 | while let Some((&position_0, flags_0)) = iter.next() { 134 | if flags_0.is_empty() { 135 | sink.line_to(position_0); 136 | continue; 137 | } 138 | 139 | let (&position_1, flags_1) = iter.next().expect("Invalid outline!"); 140 | if flags_1.is_empty() { 141 | sink.quadratic_curve_to(position_0, position_1); 142 | continue; 143 | } 144 | 145 | let (&position_2, flags_2) = iter.next().expect("Invalid outline!"); 146 | debug_assert!(flags_2.is_empty()); 147 | sink.cubic_curve_to(LineSegment2F::new(position_0, position_1), position_2); 148 | } 149 | 150 | sink.close(); 151 | } 152 | } 153 | 154 | impl Default for OutlineBuilder { 155 | fn default() -> Self { 156 | Self::new() 157 | } 158 | } 159 | 160 | impl OutlineBuilder { 161 | /// Creates a new empty `OutlineBuilder`. 162 | #[inline] 163 | pub fn new() -> OutlineBuilder { 164 | OutlineBuilder { 165 | outline: Outline::new(), 166 | current_contour: Contour::new(), 167 | } 168 | } 169 | 170 | /// Consumes this outline builder and returns the resulting outline. 171 | #[inline] 172 | pub fn into_outline(self) -> Outline { 173 | self.outline 174 | } 175 | 176 | /// Resets the outline builder and returns the old outline. 177 | #[inline] 178 | pub fn take_outline(&mut self) -> Outline { 179 | assert!(self.current_contour.positions.is_empty()); 180 | self.current_contour = Contour::new(); 181 | mem::replace(&mut self.outline, Outline::new()) 182 | } 183 | } 184 | 185 | impl OutlineSink for OutlineBuilder { 186 | #[inline] 187 | fn move_to(&mut self, to: Vector2F) { 188 | self.current_contour.push(to, PointFlags::empty()); 189 | } 190 | 191 | #[inline] 192 | fn line_to(&mut self, to: Vector2F) { 193 | self.current_contour.push(to, PointFlags::empty()); 194 | } 195 | 196 | #[inline] 197 | fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F) { 198 | self.current_contour.push(ctrl, PointFlags::CONTROL_POINT_0); 199 | self.current_contour.push(to, PointFlags::empty()); 200 | } 201 | 202 | #[inline] 203 | fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F) { 204 | self.current_contour 205 | .push(ctrl.from(), PointFlags::CONTROL_POINT_0); 206 | self.current_contour 207 | .push(ctrl.to(), PointFlags::CONTROL_POINT_1); 208 | self.current_contour.push(to, PointFlags::empty()); 209 | } 210 | 211 | #[inline] 212 | fn close(&mut self) { 213 | self.outline 214 | .contours 215 | .push(mem::replace(&mut self.current_contour, Contour::new())); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/properties.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/properties.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Properties that specify which font in a family to use: e.g. style, weight, and stretchiness. 12 | //! 13 | //! Much of the documentation in this modules comes from the CSS 3 Fonts specification: 14 | //! 15 | 16 | use std::fmt::{self, Debug, Display, Formatter}; 17 | 18 | /// Properties that specify which font in a family to use: e.g. style, weight, and stretchiness. 19 | /// 20 | /// This object supports a method chaining style for idiomatic initialization; e.g. 21 | /// 22 | /// # use font_kit::properties::{Properties, Style}; 23 | /// println!("{:?}", Properties::new().style(Style::Italic)); 24 | #[derive(Clone, Copy, Debug, Default, PartialEq)] 25 | pub struct Properties { 26 | /// The font style, as defined in CSS. 27 | pub style: Style, 28 | /// The font weight, as defined in CSS. 29 | pub weight: Weight, 30 | /// The font stretchiness, as defined in CSS. 31 | pub stretch: Stretch, 32 | } 33 | 34 | impl Properties { 35 | /// Initializes a property set to its default values: normal style, normal weight, and normal 36 | /// stretchiness. 37 | #[inline] 38 | pub fn new() -> Properties { 39 | Properties::default() 40 | } 41 | 42 | /// Sets the value of the style property and returns this property set for method chaining. 43 | #[inline] 44 | pub fn style(&mut self, style: Style) -> &mut Properties { 45 | self.style = style; 46 | self 47 | } 48 | 49 | /// Sets the value of the weight property and returns this property set for method chaining. 50 | #[inline] 51 | pub fn weight(&mut self, weight: Weight) -> &mut Properties { 52 | self.weight = weight; 53 | self 54 | } 55 | 56 | /// Sets the value of the stretch property and returns this property set for method chaining. 57 | #[inline] 58 | pub fn stretch(&mut self, stretch: Stretch) -> &mut Properties { 59 | self.stretch = stretch; 60 | self 61 | } 62 | } 63 | 64 | /// Allows italic or oblique faces to be selected. 65 | #[derive(Clone, Copy, PartialEq, Debug, Hash, Default)] 66 | pub enum Style { 67 | /// A face that is neither italic not obliqued. 68 | #[default] 69 | Normal, 70 | /// A form that is generally cursive in nature. 71 | Italic, 72 | /// A typically-sloped version of the regular face. 73 | Oblique, 74 | } 75 | 76 | impl Display for Style { 77 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 78 | Debug::fmt(self, f) 79 | } 80 | } 81 | 82 | /// The degree of blackness or stroke thickness of a font. This value ranges from 100.0 to 900.0, 83 | /// with 400.0 as normal. 84 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] 85 | pub struct Weight(pub f32); 86 | 87 | impl Default for Weight { 88 | #[inline] 89 | fn default() -> Weight { 90 | Weight::NORMAL 91 | } 92 | } 93 | 94 | impl Weight { 95 | /// Thin weight (100), the thinnest value. 96 | pub const THIN: Weight = Weight(100.0); 97 | /// Extra light weight (200). 98 | pub const EXTRA_LIGHT: Weight = Weight(200.0); 99 | /// Light weight (300). 100 | pub const LIGHT: Weight = Weight(300.0); 101 | /// Normal (400). 102 | pub const NORMAL: Weight = Weight(400.0); 103 | /// Medium weight (500, higher than normal). 104 | pub const MEDIUM: Weight = Weight(500.0); 105 | /// Semibold weight (600). 106 | pub const SEMIBOLD: Weight = Weight(600.0); 107 | /// Bold weight (700). 108 | pub const BOLD: Weight = Weight(700.0); 109 | /// Extra-bold weight (800). 110 | pub const EXTRA_BOLD: Weight = Weight(800.0); 111 | /// Black weight (900), the thickest value. 112 | pub const BLACK: Weight = Weight(900.0); 113 | } 114 | 115 | /// The width of a font as an approximate fraction of the normal width. 116 | /// 117 | /// Widths range from 0.5 to 2.0 inclusive, with 1.0 as the normal width. 118 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] 119 | pub struct Stretch(pub f32); 120 | 121 | impl Default for Stretch { 122 | #[inline] 123 | fn default() -> Stretch { 124 | Stretch::NORMAL 125 | } 126 | } 127 | 128 | impl Stretch { 129 | /// Ultra-condensed width (50%), the narrowest possible. 130 | pub const ULTRA_CONDENSED: Stretch = Stretch(0.5); 131 | /// Extra-condensed width (62.5%). 132 | pub const EXTRA_CONDENSED: Stretch = Stretch(0.625); 133 | /// Condensed width (75%). 134 | pub const CONDENSED: Stretch = Stretch(0.75); 135 | /// Semi-condensed width (87.5%). 136 | pub const SEMI_CONDENSED: Stretch = Stretch(0.875); 137 | /// Normal width (100%). 138 | pub const NORMAL: Stretch = Stretch(1.0); 139 | /// Semi-expanded width (112.5%). 140 | pub const SEMI_EXPANDED: Stretch = Stretch(1.125); 141 | /// Expanded width (125%). 142 | pub const EXPANDED: Stretch = Stretch(1.25); 143 | /// Extra-expanded width (150%). 144 | pub const EXTRA_EXPANDED: Stretch = Stretch(1.5); 145 | /// Ultra-expanded width (200%), the widest possible. 146 | pub const ULTRA_EXPANDED: Stretch = Stretch(2.0); 147 | 148 | // Mapping from `usWidthClass` values to CSS `font-stretch` values. 149 | pub(crate) const MAPPING: [f32; 9] = [ 150 | Stretch::ULTRA_CONDENSED.0, 151 | Stretch::EXTRA_CONDENSED.0, 152 | Stretch::CONDENSED.0, 153 | Stretch::SEMI_CONDENSED.0, 154 | Stretch::NORMAL.0, 155 | Stretch::SEMI_EXPANDED.0, 156 | Stretch::EXPANDED.0, 157 | Stretch::EXTRA_EXPANDED.0, 158 | Stretch::ULTRA_EXPANDED.0, 159 | ]; 160 | } 161 | -------------------------------------------------------------------------------- /src/source.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/source.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! A database of installed fonts that can be queried. 12 | 13 | use crate::error::SelectionError; 14 | use crate::family::Family; 15 | use crate::family_handle::FamilyHandle; 16 | use crate::family_name::FamilyName; 17 | use crate::font::Font; 18 | use crate::handle::Handle; 19 | use crate::matching; 20 | use crate::properties::Properties; 21 | use std::any::Any; 22 | 23 | #[cfg(all( 24 | any(target_os = "macos", target_os = "ios"), 25 | not(feature = "loader-freetype-default") 26 | ))] 27 | pub use crate::sources::core_text::CoreTextSource as SystemSource; 28 | #[cfg(all(target_family = "windows", not(feature = "source-fontconfig-default")))] 29 | pub use crate::sources::directwrite::DirectWriteSource as SystemSource; 30 | #[cfg(all( 31 | any( 32 | not(any( 33 | target_os = "android", 34 | target_os = "macos", 35 | target_os = "ios", 36 | target_family = "windows", 37 | target_arch = "wasm32", 38 | )), 39 | feature = "source-fontconfig-default" 40 | ), 41 | not(target_env = "ohos") 42 | ))] 43 | pub use crate::sources::fontconfig::FontconfigSource as SystemSource; 44 | #[cfg(all(target_os = "android", not(feature = "source-fontconfig-default")))] 45 | pub use crate::sources::fs::FsSource as SystemSource; 46 | 47 | #[cfg(target_env = "ohos")] 48 | pub use crate::sources::fs::FsSource as SystemSource; 49 | 50 | // FIXME(pcwalton): These could expand to multiple fonts, and they could be language-specific. 51 | #[cfg(any(target_family = "windows", target_os = "macos", target_os = "ios"))] 52 | const DEFAULT_FONT_FAMILY_SERIF: &'static str = "Times New Roman"; 53 | #[cfg(any(target_family = "windows", target_os = "macos", target_os = "ios"))] 54 | const DEFAULT_FONT_FAMILY_SANS_SERIF: &'static str = "Arial"; 55 | #[cfg(target_env = "ohos")] 56 | const DEFAULT_FONT_FAMILY_SANS_SERIF: &str = "HarmonyOS Sans"; 57 | #[cfg(any(target_family = "windows", target_os = "macos", target_os = "ios"))] 58 | const DEFAULT_FONT_FAMILY_MONOSPACE: &'static str = "Courier New"; 59 | #[cfg(target_env = "ohos")] 60 | const DEFAULT_FONT_FAMILY_MONOSPACE: &str = "HarmonyOS Sans"; 61 | #[cfg(any(target_family = "windows", target_os = "macos", target_os = "ios"))] 62 | const DEFAULT_FONT_FAMILY_CURSIVE: &'static str = "Comic Sans MS"; 63 | #[cfg(target_family = "windows")] 64 | const DEFAULT_FONT_FAMILY_FANTASY: &'static str = "Impact"; 65 | #[cfg(any(target_os = "macos", target_os = "ios"))] 66 | const DEFAULT_FONT_FAMILY_FANTASY: &'static str = "Papyrus"; 67 | 68 | #[cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))] 69 | const DEFAULT_FONT_FAMILY_SERIF: &str = "serif"; 70 | #[cfg(not(any( 71 | target_family = "windows", 72 | target_os = "macos", 73 | target_os = "ios", 74 | target_env = "ohos" 75 | )))] 76 | const DEFAULT_FONT_FAMILY_SANS_SERIF: &str = "sans-serif"; 77 | #[cfg(not(any( 78 | target_family = "windows", 79 | target_os = "macos", 80 | target_os = "ios", 81 | target_env = "ohos" 82 | )))] 83 | const DEFAULT_FONT_FAMILY_MONOSPACE: &str = "monospace"; 84 | #[cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))] 85 | const DEFAULT_FONT_FAMILY_CURSIVE: &str = "cursive"; 86 | #[cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))] 87 | const DEFAULT_FONT_FAMILY_FANTASY: &str = "fantasy"; 88 | 89 | /// A database of installed fonts that can be queried. 90 | /// 91 | /// This trait is object-safe. 92 | pub trait Source: Any { 93 | /// Returns paths of all fonts installed on the system. 94 | fn all_fonts(&self) -> Result, SelectionError>; 95 | 96 | /// Returns the names of all families installed on the system. 97 | fn all_families(&self) -> Result, SelectionError>; 98 | 99 | /// Looks up a font family by name and returns the handles of all the fonts in that family. 100 | fn select_family_by_name(&self, family_name: &str) -> Result; 101 | 102 | /// Selects a font by PostScript name, which should be a unique identifier. 103 | /// 104 | /// The default implementation, which is used by the DirectWrite and the filesystem backends, 105 | /// does a brute-force search of installed fonts to find the one that matches. 106 | fn select_by_postscript_name(&self, postscript_name: &str) -> Result { 107 | // TODO(pcwalton): Optimize this by searching for families with similar names first. 108 | for family_name in self.all_families()? { 109 | if let Ok(family_handle) = self.select_family_by_name(&family_name) { 110 | if let Ok(family) = Family::::from_handle(&family_handle) { 111 | for (handle, font) in family_handle.fonts().iter().zip(family.fonts().iter()) { 112 | if let Some(font_postscript_name) = font.postscript_name() { 113 | if font_postscript_name == postscript_name { 114 | return Ok((*handle).clone()); 115 | } 116 | } 117 | } 118 | } 119 | } 120 | } 121 | Err(SelectionError::NotFound) 122 | } 123 | 124 | // FIXME(pcwalton): This only returns one family instead of multiple families for the generic 125 | // family names. 126 | #[doc(hidden)] 127 | fn select_family_by_generic_name( 128 | &self, 129 | family_name: &FamilyName, 130 | ) -> Result { 131 | match *family_name { 132 | FamilyName::Title(ref title) => self.select_family_by_name(title), 133 | FamilyName::Serif => self.select_family_by_name(DEFAULT_FONT_FAMILY_SERIF), 134 | FamilyName::SansSerif => self.select_family_by_name(DEFAULT_FONT_FAMILY_SANS_SERIF), 135 | FamilyName::Monospace => self.select_family_by_name(DEFAULT_FONT_FAMILY_MONOSPACE), 136 | FamilyName::Cursive => self.select_family_by_name(DEFAULT_FONT_FAMILY_CURSIVE), 137 | FamilyName::Fantasy => self.select_family_by_name(DEFAULT_FONT_FAMILY_FANTASY), 138 | } 139 | } 140 | 141 | /// Performs font matching according to the CSS Fonts Level 3 specification and returns the 142 | /// handle. 143 | #[inline] 144 | fn select_best_match( 145 | &self, 146 | family_names: &[FamilyName], 147 | properties: &Properties, 148 | ) -> Result { 149 | for family_name in family_names { 150 | if let Ok(family_handle) = self.select_family_by_generic_name(family_name) { 151 | let candidates = self.select_descriptions_in_family(&family_handle)?; 152 | if let Ok(index) = matching::find_best_match(&candidates, properties) { 153 | return Ok(family_handle.fonts[index].clone()); 154 | } 155 | } 156 | } 157 | Err(SelectionError::NotFound) 158 | } 159 | 160 | #[doc(hidden)] 161 | fn select_descriptions_in_family( 162 | &self, 163 | family: &FamilyHandle, 164 | ) -> Result, SelectionError> { 165 | let mut fields = vec![]; 166 | for font_handle in family.fonts() { 167 | match Font::from_handle(font_handle) { 168 | Ok(font) => fields.push(font.properties()), 169 | Err(e) => log::warn!("Error loading font from handle: {:?}", e), 170 | } 171 | } 172 | Ok(fields) 173 | } 174 | 175 | /// Accesses this `Source` as `Any`, which allows downcasting back to a concrete type from a 176 | /// trait object. 177 | fn as_any(&self) -> &dyn Any; 178 | 179 | /// Accesses this `Source` as `Any`, which allows downcasting back to a concrete type from a 180 | /// trait object. 181 | fn as_mut_any(&mut self) -> &mut dyn Any; 182 | } 183 | -------------------------------------------------------------------------------- /src/sources/core_text.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/sources/core_text.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! A source that contains the installed fonts on macOS. 12 | 13 | use core_foundation::array::CFArray; 14 | use core_foundation::base::{CFType, TCFType}; 15 | use core_foundation::dictionary::CFDictionary; 16 | use core_foundation::string::CFString; 17 | use core_text::font_collection::{self, CTFontCollection}; 18 | use core_text::font_descriptor::{self, CTFontDescriptor}; 19 | use core_text::font_manager; 20 | use std::any::Any; 21 | use std::collections::HashMap; 22 | use std::f32; 23 | use std::fs::File; 24 | use std::path::PathBuf; 25 | use std::sync::Arc; 26 | 27 | use crate::error::SelectionError; 28 | use crate::family_handle::FamilyHandle; 29 | use crate::family_name::FamilyName; 30 | use crate::file_type::FileType; 31 | use crate::font::Font; 32 | use crate::handle::Handle; 33 | use crate::loaders::core_text::{self as core_text_loader, FONT_WEIGHT_MAPPING}; 34 | use crate::properties::{Properties, Stretch, Weight}; 35 | use crate::source::Source; 36 | use crate::utils; 37 | 38 | /// A source that contains the installed fonts on macOS. 39 | #[allow(missing_debug_implementations)] 40 | #[allow(missing_copy_implementations)] 41 | pub struct CoreTextSource; 42 | 43 | impl CoreTextSource { 44 | /// Opens a new connection to the system font source. 45 | /// 46 | /// (Note that this doesn't actually do any Mach communication to the font server; that is done 47 | /// lazily on demand by the Core Text/Core Graphics API.) 48 | #[inline] 49 | pub fn new() -> CoreTextSource { 50 | CoreTextSource 51 | } 52 | 53 | /// Returns paths of all fonts installed on the system. 54 | pub fn all_fonts(&self) -> Result, SelectionError> { 55 | let collection = font_collection::create_for_all_families(); 56 | create_handles_from_core_text_collection(collection) 57 | } 58 | 59 | /// Returns the names of all families installed on the system. 60 | pub fn all_families(&self) -> Result, SelectionError> { 61 | let core_text_family_names = font_manager::copy_available_font_family_names(); 62 | let mut families = Vec::with_capacity(core_text_family_names.len() as usize); 63 | for core_text_family_name in core_text_family_names.iter() { 64 | families.push(core_text_family_name.to_string()) 65 | } 66 | Ok(families) 67 | } 68 | 69 | /// Looks up a font family by name and returns the handles of all the fonts in that family. 70 | pub fn select_family_by_name(&self, family_name: &str) -> Result { 71 | let attributes: CFDictionary = CFDictionary::from_CFType_pairs(&[( 72 | CFString::new("NSFontFamilyAttribute"), 73 | CFString::new(family_name).as_CFType(), 74 | )]); 75 | 76 | let descriptor = font_descriptor::new_from_attributes(&attributes); 77 | let descriptors = CFArray::from_CFTypes(&[descriptor]); 78 | let collection = font_collection::new_from_descriptors(&descriptors); 79 | let handles = create_handles_from_core_text_collection(collection)?; 80 | Ok(FamilyHandle::from_font_handles(handles.into_iter())) 81 | } 82 | 83 | /// Selects a font by PostScript name, which should be a unique identifier. 84 | pub fn select_by_postscript_name( 85 | &self, 86 | postscript_name: &str, 87 | ) -> Result { 88 | let attributes: CFDictionary = CFDictionary::from_CFType_pairs(&[( 89 | CFString::new("NSFontNameAttribute"), 90 | CFString::new(postscript_name).as_CFType(), 91 | )]); 92 | 93 | let descriptor = font_descriptor::new_from_attributes(&attributes); 94 | let descriptors = CFArray::from_CFTypes(&[descriptor]); 95 | let collection = font_collection::new_from_descriptors(&descriptors); 96 | match collection.get_descriptors() { 97 | None => Err(SelectionError::NotFound), 98 | Some(descriptors) => create_handle_from_descriptor(&*descriptors.get(0).unwrap()), 99 | } 100 | } 101 | 102 | /// Performs font matching according to the CSS Fonts Level 3 specification and returns the 103 | /// handle. 104 | #[inline] 105 | pub fn select_best_match( 106 | &self, 107 | family_names: &[FamilyName], 108 | properties: &Properties, 109 | ) -> Result { 110 | ::select_best_match(self, family_names, properties) 111 | } 112 | } 113 | 114 | impl Source for CoreTextSource { 115 | fn all_fonts(&self) -> Result, SelectionError> { 116 | self.all_fonts() 117 | } 118 | 119 | fn all_families(&self) -> Result, SelectionError> { 120 | self.all_families() 121 | } 122 | 123 | fn select_family_by_name(&self, family_name: &str) -> Result { 124 | self.select_family_by_name(family_name) 125 | } 126 | 127 | fn select_by_postscript_name(&self, postscript_name: &str) -> Result { 128 | self.select_by_postscript_name(postscript_name) 129 | } 130 | 131 | #[inline] 132 | fn as_any(&self) -> &dyn Any { 133 | self 134 | } 135 | 136 | #[inline] 137 | fn as_mut_any(&mut self) -> &mut dyn Any { 138 | self 139 | } 140 | } 141 | 142 | #[allow(dead_code)] 143 | fn css_to_core_text_font_weight(css_weight: Weight) -> f32 { 144 | core_text_loader::piecewise_linear_lookup( 145 | f32::max(100.0, css_weight.0) / 100.0 - 1.0, 146 | &FONT_WEIGHT_MAPPING, 147 | ) 148 | } 149 | 150 | #[allow(dead_code)] 151 | fn css_stretchiness_to_core_text_width(css_stretchiness: Stretch) -> f32 { 152 | let css_stretchiness = utils::clamp(css_stretchiness.0, 0.5, 2.0); 153 | 0.1 * core_text_loader::piecewise_linear_find_index(css_stretchiness, &Stretch::MAPPING) - 0.4 154 | } 155 | 156 | #[derive(Clone)] 157 | struct FontDataInfo { 158 | data: Arc>, 159 | file_type: FileType, 160 | } 161 | 162 | fn create_handles_from_core_text_collection( 163 | collection: CTFontCollection, 164 | ) -> Result, SelectionError> { 165 | let mut fonts = vec![]; 166 | if let Some(descriptors) = collection.get_descriptors() { 167 | let mut font_data_info_cache: HashMap = HashMap::new(); 168 | 169 | 'outer: for index in 0..descriptors.len() { 170 | let descriptor = descriptors.get(index).unwrap(); 171 | let font_path = descriptor.font_path().unwrap(); 172 | 173 | let data_info = if let Some(data_info) = font_data_info_cache.get(&font_path) { 174 | data_info.clone() 175 | } else { 176 | let mut file = if let Ok(file) = File::open(&font_path) { 177 | file 178 | } else { 179 | continue; 180 | }; 181 | let data = if let Ok(data) = utils::slurp_file(&mut file) { 182 | Arc::new(data) 183 | } else { 184 | continue; 185 | }; 186 | 187 | let file_type = match Font::analyze_bytes(Arc::clone(&data)) { 188 | Ok(file_type) => file_type, 189 | Err(_) => continue, 190 | }; 191 | 192 | let data_info = FontDataInfo { data, file_type }; 193 | 194 | font_data_info_cache.insert(font_path.clone(), data_info.clone()); 195 | 196 | data_info 197 | }; 198 | 199 | match data_info.file_type { 200 | FileType::Collection(font_count) => { 201 | let postscript_name = descriptor.font_name(); 202 | for font_index in 0..font_count { 203 | if let Ok(font) = Font::from_bytes(Arc::clone(&data_info.data), font_index) 204 | { 205 | if let Some(font_postscript_name) = font.postscript_name() { 206 | if postscript_name == font_postscript_name { 207 | fonts.push(Handle::from_memory(data_info.data, font_index)); 208 | continue 'outer; 209 | } 210 | } 211 | } 212 | } 213 | } 214 | FileType::Single => { 215 | fonts.push(Handle::from_memory(data_info.data, 0)); 216 | } 217 | } 218 | } 219 | } 220 | if fonts.is_empty() { 221 | Err(SelectionError::NotFound) 222 | } else { 223 | Ok(fonts) 224 | } 225 | } 226 | 227 | fn create_handle_from_descriptor(descriptor: &CTFontDescriptor) -> Result { 228 | let font_path = descriptor.font_path().unwrap(); 229 | 230 | let mut file = if let Ok(file) = File::open(&font_path) { 231 | file 232 | } else { 233 | return Err(SelectionError::CannotAccessSource { reason: None }); 234 | }; 235 | 236 | let font_data = if let Ok(font_data) = utils::slurp_file(&mut file) { 237 | Arc::new(font_data) 238 | } else { 239 | return Err(SelectionError::CannotAccessSource { reason: None }); 240 | }; 241 | 242 | match Font::analyze_bytes(Arc::clone(&font_data)) { 243 | Ok(FileType::Collection(font_count)) => { 244 | let postscript_name = descriptor.font_name(); 245 | for font_index in 0..font_count { 246 | if let Ok(font) = Font::from_bytes(Arc::clone(&font_data), font_index) { 247 | if let Some(font_postscript_name) = font.postscript_name() { 248 | if postscript_name == font_postscript_name { 249 | return Ok(Handle::from_memory(font_data, font_index)); 250 | } 251 | } 252 | } 253 | } 254 | 255 | Err(SelectionError::NotFound) 256 | } 257 | Ok(FileType::Single) => Ok(Handle::from_memory(font_data, 0)), 258 | Err(e) => Err(SelectionError::CannotAccessSource { 259 | reason: Some(format!("{:?} error on path {:?}", e, font_path).into()), 260 | }), 261 | } 262 | } 263 | 264 | #[cfg(test)] 265 | mod test { 266 | use crate::properties::{Stretch, Weight}; 267 | 268 | #[test] 269 | fn test_css_to_core_text_font_weight() { 270 | // Exact matches 271 | assert_eq!(super::css_to_core_text_font_weight(Weight(100.0)), -0.7); 272 | assert_eq!(super::css_to_core_text_font_weight(Weight(400.0)), 0.0); 273 | assert_eq!(super::css_to_core_text_font_weight(Weight(700.0)), 0.4); 274 | assert_eq!(super::css_to_core_text_font_weight(Weight(900.0)), 0.8); 275 | 276 | // Linear interpolation 277 | assert_eq!(super::css_to_core_text_font_weight(Weight(450.0)), 0.1); 278 | } 279 | 280 | #[test] 281 | fn test_css_to_core_text_font_stretch() { 282 | // Exact matches 283 | assert_eq!( 284 | super::css_stretchiness_to_core_text_width(Stretch(1.0)), 285 | 0.0 286 | ); 287 | assert_eq!( 288 | super::css_stretchiness_to_core_text_width(Stretch(0.5)), 289 | -0.4 290 | ); 291 | assert_eq!( 292 | super::css_stretchiness_to_core_text_width(Stretch(2.0)), 293 | 0.4 294 | ); 295 | 296 | // Linear interpolation 297 | assert_eq!( 298 | super::css_stretchiness_to_core_text_width(Stretch(1.7)), 299 | 0.34 300 | ); 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/sources/directwrite.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/sources/directwrite.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! A source that contains the installed fonts on Windows. 12 | 13 | use dwrote::Font as DWriteFont; 14 | use dwrote::FontCollection as DWriteFontCollection; 15 | use std::any::Any; 16 | 17 | use crate::error::SelectionError; 18 | use crate::family_handle::FamilyHandle; 19 | use crate::family_name::FamilyName; 20 | use crate::handle::Handle; 21 | use crate::properties::Properties; 22 | use crate::source::Source; 23 | 24 | /// A source that contains the installed fonts on Windows. 25 | #[allow(missing_debug_implementations)] 26 | pub struct DirectWriteSource { 27 | system_font_collection: DWriteFontCollection, 28 | } 29 | 30 | impl DirectWriteSource { 31 | /// Opens the system font collection. 32 | pub fn new() -> DirectWriteSource { 33 | DirectWriteSource { 34 | system_font_collection: DWriteFontCollection::system(), 35 | } 36 | } 37 | 38 | /// Returns paths of all fonts installed on the system. 39 | pub fn all_fonts(&self) -> Result, SelectionError> { 40 | let mut handles = Vec::new(); 41 | 42 | for dwrite_family in self.system_font_collection.families_iter() { 43 | for font_index in 0..dwrite_family.get_font_count() { 44 | let Ok(dwrite_font) = dwrite_family.font(font_index) else { 45 | continue; 46 | }; 47 | handles.push(self.create_handle_from_dwrite_font(dwrite_font)) 48 | } 49 | } 50 | 51 | Ok(handles) 52 | } 53 | 54 | /// Returns the names of all families installed on the system. 55 | pub fn all_families(&self) -> Result, SelectionError> { 56 | Ok(self 57 | .system_font_collection 58 | .families_iter() 59 | .filter_map(|dwrite_family| dwrite_family.family_name().ok()) 60 | .collect()) 61 | } 62 | 63 | /// Looks up a font family by name and returns the handles of all the fonts in that family. 64 | /// 65 | /// TODO(pcwalton): Case-insensitivity. 66 | pub fn select_family_by_name(&self, family_name: &str) -> Result { 67 | let mut family = FamilyHandle::new(); 68 | let dwrite_family = match self.system_font_collection.font_family_by_name(family_name) { 69 | Ok(Some(dwrite_family)) => dwrite_family, 70 | Err(_) | Ok(None) => return Err(SelectionError::NotFound), 71 | }; 72 | for font_index in 0..dwrite_family.get_font_count() { 73 | let Ok(dwrite_font) = dwrite_family.font(font_index) else { 74 | continue; 75 | }; 76 | family.push(self.create_handle_from_dwrite_font(dwrite_font)); 77 | } 78 | Ok(family) 79 | } 80 | 81 | /// Selects a font by PostScript name, which should be a unique identifier. 82 | /// 83 | /// On the DirectWrite backend, this does a brute-force search of installed fonts to find the 84 | /// one that matches. 85 | pub fn select_by_postscript_name( 86 | &self, 87 | postscript_name: &str, 88 | ) -> Result { 89 | ::select_by_postscript_name(self, postscript_name) 90 | } 91 | 92 | /// Performs font matching according to the CSS Fonts Level 3 specification and returns the 93 | /// handle. 94 | #[inline] 95 | pub fn select_best_match( 96 | &self, 97 | family_names: &[FamilyName], 98 | properties: &Properties, 99 | ) -> Result { 100 | ::select_best_match(self, family_names, properties) 101 | } 102 | 103 | fn create_handle_from_dwrite_font(&self, dwrite_font: DWriteFont) -> Handle { 104 | let dwrite_font_face = dwrite_font.create_font_face(); 105 | let dwrite_font_files = dwrite_font_face.get_files(); 106 | Handle::Path { 107 | path: dwrite_font_files[0].get_font_file_path().unwrap(), 108 | font_index: dwrite_font_face.get_index(), 109 | } 110 | } 111 | } 112 | 113 | impl Source for DirectWriteSource { 114 | #[inline] 115 | fn all_fonts(&self) -> Result, SelectionError> { 116 | self.all_fonts() 117 | } 118 | 119 | #[inline] 120 | fn all_families(&self) -> Result, SelectionError> { 121 | self.all_families() 122 | } 123 | 124 | #[inline] 125 | fn select_family_by_name(&self, family_name: &str) -> Result { 126 | self.select_family_by_name(family_name) 127 | } 128 | 129 | #[inline] 130 | fn as_any(&self) -> &dyn Any { 131 | self 132 | } 133 | 134 | #[inline] 135 | fn as_mut_any(&mut self) -> &mut dyn Any { 136 | self 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/sources/fontconfig.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/sources/fontconfig.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! A source that contains the fonts installed on the system, as reported by the Fontconfig 12 | //! library. 13 | //! 14 | //! On macOS and Windows, the Cargo feature `source-fontconfig` can be used to opt into fontconfig 15 | //! support. To prefer it over the native font source (only if you know what you're doing), use the 16 | //! `source-fontconfig-default` feature. 17 | 18 | use crate::error::SelectionError; 19 | use crate::family_handle::FamilyHandle; 20 | use crate::family_name::FamilyName; 21 | use crate::handle::Handle; 22 | use crate::properties::Properties; 23 | use crate::source::Source; 24 | use std::any::Any; 25 | 26 | /// A source that contains the fonts installed on the system, as reported by the Fontconfig 27 | /// library. 28 | /// 29 | /// On macOS and Windows, the Cargo feature `source-fontconfig` can be used to opt into fontconfig 30 | /// support. To prefer it over the native font source (only if you know what you're doing), use the 31 | /// `source-fontconfig-default` feature. 32 | #[allow(missing_debug_implementations)] 33 | pub struct FontconfigSource { 34 | config: fc::Config, 35 | } 36 | 37 | impl Default for FontconfigSource { 38 | fn default() -> Self { 39 | Self::new() 40 | } 41 | } 42 | 43 | impl FontconfigSource { 44 | /// Initializes Fontconfig and prepares it for queries. 45 | pub fn new() -> FontconfigSource { 46 | FontconfigSource { 47 | config: fc::Config::new(), 48 | } 49 | } 50 | 51 | /// Returns paths of all fonts installed on the system. 52 | pub fn all_fonts(&self) -> Result, SelectionError> { 53 | let pattern = fc::Pattern::new(); 54 | 55 | // We want the family name. 56 | let mut object_set = fc::ObjectSet::new(); 57 | object_set.push_string(fc::Object::File); 58 | object_set.push_string(fc::Object::Index); 59 | 60 | let patterns = pattern 61 | .list(&self.config, object_set) 62 | .map_err(|_| SelectionError::NotFound)?; 63 | 64 | let mut handles = vec![]; 65 | for patt in patterns { 66 | let path = match patt.get_string(fc::Object::File) { 67 | Some(v) => v, 68 | None => continue, 69 | }; 70 | 71 | let index = match patt.get_integer(fc::Object::Index) { 72 | Some(v) => v, 73 | None => continue, 74 | }; 75 | 76 | handles.push(Handle::Path { 77 | path: path.into(), 78 | font_index: index as u32, 79 | }); 80 | } 81 | 82 | if !handles.is_empty() { 83 | Ok(handles) 84 | } else { 85 | Err(SelectionError::NotFound) 86 | } 87 | } 88 | 89 | /// Returns the names of all families installed on the system. 90 | pub fn all_families(&self) -> Result, SelectionError> { 91 | let pattern = fc::Pattern::new(); 92 | 93 | // We want the family name. 94 | let mut object_set = fc::ObjectSet::new(); 95 | object_set.push_string(fc::Object::Family); 96 | 97 | let patterns = pattern 98 | .list(&self.config, object_set) 99 | .map_err(|_| SelectionError::NotFound)?; 100 | 101 | let mut result_families = vec![]; 102 | for patt in patterns { 103 | if let Some(family) = patt.get_string(fc::Object::Family) { 104 | result_families.push(family); 105 | } 106 | } 107 | 108 | result_families.sort(); 109 | result_families.dedup(); 110 | 111 | if !result_families.is_empty() { 112 | Ok(result_families) 113 | } else { 114 | Err(SelectionError::NotFound) 115 | } 116 | } 117 | 118 | /// Looks up a font family by name and returns the handles of all the fonts in that family. 119 | pub fn select_family_by_name(&self, family_name: &str) -> Result { 120 | use std::borrow::Cow; 121 | 122 | let family_name = match family_name { 123 | "serif" | "sans-serif" | "monospace" | "cursive" | "fantasy" => { 124 | Cow::from(self.select_generic_font(family_name)?) 125 | } 126 | _ => Cow::from(family_name), 127 | }; 128 | 129 | let pattern = fc::Pattern::from_name(family_name.as_ref()); 130 | 131 | let mut object_set = fc::ObjectSet::new(); 132 | object_set.push_string(fc::Object::File); 133 | object_set.push_string(fc::Object::Index); 134 | 135 | let patterns = pattern 136 | .list(&self.config, object_set) 137 | .map_err(|_| SelectionError::NotFound)?; 138 | 139 | let mut handles = vec![]; 140 | for patt in patterns { 141 | let font_path = patt.get_string(fc::Object::File).unwrap(); 142 | let font_index = patt.get_integer(fc::Object::Index).unwrap() as u32; 143 | let handle = Handle::from_path(std::path::PathBuf::from(font_path), font_index); 144 | handles.push(handle); 145 | } 146 | 147 | if !handles.is_empty() { 148 | Ok(FamilyHandle::from_font_handles(handles.into_iter())) 149 | } else { 150 | Err(SelectionError::NotFound) 151 | } 152 | } 153 | 154 | /// Selects a font by a generic name. 155 | /// 156 | /// Accepts: serif, sans-serif, monospace, cursive and fantasy. 157 | fn select_generic_font(&self, name: &str) -> Result { 158 | let mut pattern = fc::Pattern::from_name(name); 159 | pattern.config_substitute(fc::MatchKind::Pattern); 160 | pattern.default_substitute(); 161 | 162 | let patterns = pattern 163 | .sorted(&self.config) 164 | .map_err(|_| SelectionError::NotFound)?; 165 | 166 | if let Some(patt) = patterns.into_iter().next() { 167 | if let Some(family) = patt.get_string(fc::Object::Family) { 168 | return Ok(family); 169 | } 170 | } 171 | 172 | Err(SelectionError::NotFound) 173 | } 174 | 175 | /// Selects a font by PostScript name, which should be a unique identifier. 176 | /// 177 | /// The default implementation, which is used by the DirectWrite and the filesystem backends, 178 | /// does a brute-force search of installed fonts to find the one that matches. 179 | pub fn select_by_postscript_name( 180 | &self, 181 | postscript_name: &str, 182 | ) -> Result { 183 | let mut pattern = fc::Pattern::new(); 184 | pattern.push_string(fc::Object::PostScriptName, postscript_name.to_owned()); 185 | 186 | // We want the file path and the font index. 187 | let mut object_set = fc::ObjectSet::new(); 188 | object_set.push_string(fc::Object::File); 189 | object_set.push_string(fc::Object::Index); 190 | 191 | let patterns = pattern 192 | .list(&self.config, object_set) 193 | .map_err(|_| SelectionError::NotFound)?; 194 | 195 | if let Some(patt) = patterns.into_iter().next() { 196 | let font_path = patt.get_string(fc::Object::File).unwrap(); 197 | let font_index = patt.get_integer(fc::Object::Index).unwrap() as u32; 198 | let handle = Handle::from_path(std::path::PathBuf::from(font_path), font_index); 199 | Ok(handle) 200 | } else { 201 | Err(SelectionError::NotFound) 202 | } 203 | } 204 | 205 | /// Performs font matching according to the CSS Fonts Level 3 specification and returns the 206 | /// handle. 207 | #[inline] 208 | pub fn select_best_match( 209 | &self, 210 | family_names: &[FamilyName], 211 | properties: &Properties, 212 | ) -> Result { 213 | ::select_best_match(self, family_names, properties) 214 | } 215 | } 216 | 217 | impl Source for FontconfigSource { 218 | #[inline] 219 | fn all_fonts(&self) -> Result, SelectionError> { 220 | self.all_fonts() 221 | } 222 | 223 | #[inline] 224 | fn all_families(&self) -> Result, SelectionError> { 225 | self.all_families() 226 | } 227 | 228 | #[inline] 229 | fn select_family_by_name(&self, family_name: &str) -> Result { 230 | self.select_family_by_name(family_name) 231 | } 232 | 233 | #[inline] 234 | fn select_by_postscript_name(&self, postscript_name: &str) -> Result { 235 | self.select_by_postscript_name(postscript_name) 236 | } 237 | 238 | #[inline] 239 | fn as_any(&self) -> &dyn Any { 240 | self 241 | } 242 | 243 | #[inline] 244 | fn as_mut_any(&mut self) -> &mut dyn Any { 245 | self 246 | } 247 | } 248 | 249 | // A minimal fontconfig wrapper. 250 | mod fc { 251 | #![allow(dead_code)] 252 | 253 | use fontconfig_sys as ffi; 254 | use fontconfig_sys::ffi_dispatch; 255 | 256 | #[cfg(feature = "source-fontconfig-dlopen")] 257 | use ffi::statics::LIB; 258 | #[cfg(not(feature = "source-fontconfig-dlopen"))] 259 | use ffi::*; 260 | 261 | use std::ffi::{CStr, CString}; 262 | use std::os::raw::{c_char, c_uchar}; 263 | use std::ptr; 264 | 265 | #[derive(Clone, Copy)] 266 | pub enum Error { 267 | NoMatch, 268 | TypeMismatch, 269 | NoId, 270 | OutOfMemory, 271 | } 272 | 273 | #[derive(Clone, Copy)] 274 | pub enum MatchKind { 275 | Pattern, 276 | Font, 277 | Scan, 278 | } 279 | 280 | impl MatchKind { 281 | fn to_u32(self) -> u32 { 282 | match self { 283 | MatchKind::Pattern => ffi::FcMatchPattern, 284 | MatchKind::Font => ffi::FcMatchFont, 285 | MatchKind::Scan => ffi::FcMatchScan, 286 | } 287 | } 288 | } 289 | 290 | // https://www.freedesktop.org/software/fontconfig/fontconfig-devel/x19.html 291 | #[derive(Clone, Copy)] 292 | pub enum Object { 293 | Family, 294 | File, 295 | Index, 296 | PostScriptName, 297 | } 298 | 299 | impl Object { 300 | fn as_bytes(&self) -> &[u8] { 301 | match self { 302 | Object::Family => b"family\0", 303 | Object::File => b"file\0", 304 | Object::Index => b"index\0", 305 | Object::PostScriptName => b"postscriptname\0", 306 | } 307 | } 308 | 309 | fn as_ptr(&self) -> *const libc::c_char { 310 | self.as_bytes().as_ptr() as *const libc::c_char 311 | } 312 | } 313 | 314 | pub struct Config { 315 | d: *mut ffi::FcConfig, 316 | } 317 | 318 | impl Config { 319 | // FcInitLoadConfigAndFonts 320 | pub fn new() -> Self { 321 | unsafe { 322 | Config { 323 | d: ffi_dispatch!( 324 | feature = "source-fontconfig-dlopen", 325 | LIB, 326 | FcInitLoadConfigAndFonts, 327 | ), 328 | } 329 | } 330 | } 331 | } 332 | 333 | impl Drop for Config { 334 | fn drop(&mut self) { 335 | unsafe { 336 | ffi_dispatch!( 337 | feature = "source-fontconfig-dlopen", 338 | LIB, 339 | FcConfigDestroy, 340 | self.d 341 | ); 342 | } 343 | } 344 | } 345 | 346 | pub struct Pattern { 347 | d: *mut ffi::FcPattern, 348 | c_strings: Vec, 349 | } 350 | 351 | impl Pattern { 352 | fn from_ptr(d: *mut ffi::FcPattern) -> Self { 353 | Pattern { 354 | d, 355 | c_strings: vec![], 356 | } 357 | } 358 | 359 | // FcPatternCreate 360 | pub fn new() -> Self { 361 | unsafe { 362 | Pattern::from_ptr(ffi_dispatch!( 363 | feature = "source-fontconfig-dlopen", 364 | LIB, 365 | FcPatternCreate, 366 | )) 367 | } 368 | } 369 | 370 | // FcNameParse 371 | pub fn from_name(name: &str) -> Self { 372 | let c_name = CString::new(name).unwrap(); 373 | unsafe { 374 | Pattern::from_ptr(ffi_dispatch!( 375 | feature = "source-fontconfig-dlopen", 376 | LIB, 377 | FcNameParse, 378 | c_name.as_ptr() as *mut c_uchar 379 | )) 380 | } 381 | } 382 | 383 | // FcPatternAddString 384 | pub fn push_string(&mut self, object: Object, value: String) { 385 | unsafe { 386 | let c_string = CString::new(value).unwrap(); 387 | ffi_dispatch!( 388 | feature = "source-fontconfig-dlopen", 389 | LIB, 390 | FcPatternAddString, 391 | self.d, 392 | object.as_ptr(), 393 | c_string.as_ptr() as *const c_uchar 394 | ); 395 | 396 | // We have to keep this string, because `FcPattern` has a pointer to it now. 397 | self.c_strings.push(c_string) 398 | } 399 | } 400 | 401 | // FcConfigSubstitute 402 | pub fn config_substitute(&mut self, match_kind: MatchKind) { 403 | unsafe { 404 | ffi_dispatch!( 405 | feature = "source-fontconfig-dlopen", 406 | LIB, 407 | FcConfigSubstitute, 408 | ptr::null_mut(), 409 | self.d, 410 | match_kind.to_u32() 411 | ); 412 | } 413 | } 414 | 415 | // FcDefaultSubstitute 416 | pub fn default_substitute(&mut self) { 417 | unsafe { 418 | ffi_dispatch!( 419 | feature = "source-fontconfig-dlopen", 420 | LIB, 421 | FcDefaultSubstitute, 422 | self.d 423 | ); 424 | } 425 | } 426 | 427 | // FcFontSort 428 | pub fn sorted(&self, config: &Config) -> Result { 429 | let mut res = ffi::FcResultMatch; 430 | let d = unsafe { 431 | ffi_dispatch!( 432 | feature = "source-fontconfig-dlopen", 433 | LIB, 434 | FcFontSort, 435 | config.d, 436 | self.d, 437 | 1, 438 | ptr::null_mut(), 439 | &mut res 440 | ) 441 | }; 442 | 443 | match res { 444 | ffi::FcResultMatch => Ok(FontSet { d, idx: 0 }), 445 | ffi::FcResultTypeMismatch => Err(Error::TypeMismatch), 446 | ffi::FcResultNoId => Err(Error::NoId), 447 | ffi::FcResultOutOfMemory => Err(Error::OutOfMemory), 448 | _ => Err(Error::NoMatch), 449 | } 450 | } 451 | 452 | // FcFontList 453 | pub fn list(&self, config: &Config, set: ObjectSet) -> Result { 454 | let d = unsafe { 455 | ffi_dispatch!( 456 | feature = "source-fontconfig-dlopen", 457 | LIB, 458 | FcFontList, 459 | config.d, 460 | self.d, 461 | set.d 462 | ) 463 | }; 464 | if !d.is_null() { 465 | Ok(FontSet { d, idx: 0 }) 466 | } else { 467 | Err(Error::NoMatch) 468 | } 469 | } 470 | } 471 | 472 | impl Drop for Pattern { 473 | #[inline] 474 | fn drop(&mut self) { 475 | unsafe { 476 | ffi_dispatch!( 477 | feature = "source-fontconfig-dlopen", 478 | LIB, 479 | FcPatternDestroy, 480 | self.d 481 | ) 482 | } 483 | } 484 | } 485 | 486 | // A read-only `FcPattern` without a destructor. 487 | pub struct PatternRef { 488 | d: *mut ffi::FcPattern, 489 | } 490 | 491 | impl PatternRef { 492 | // FcPatternGetString 493 | pub fn get_string(&self, object: Object) -> Option { 494 | unsafe { 495 | let mut string = ptr::null_mut(); 496 | let res = ffi_dispatch!( 497 | feature = "source-fontconfig-dlopen", 498 | LIB, 499 | FcPatternGetString, 500 | self.d, 501 | object.as_ptr(), 502 | 0, 503 | &mut string 504 | ); 505 | if res != ffi::FcResultMatch { 506 | return None; 507 | } 508 | 509 | if string.is_null() { 510 | return None; 511 | } 512 | 513 | CStr::from_ptr(string as *const c_char) 514 | .to_str() 515 | .ok() 516 | .map(|string| string.to_owned()) 517 | } 518 | } 519 | 520 | // FcPatternGetInteger 521 | pub fn get_integer(&self, object: Object) -> Option { 522 | unsafe { 523 | let mut integer = 0; 524 | let res = ffi_dispatch!( 525 | feature = "source-fontconfig-dlopen", 526 | LIB, 527 | FcPatternGetInteger, 528 | self.d, 529 | object.as_ptr(), 530 | 0, 531 | &mut integer 532 | ); 533 | if res != ffi::FcResultMatch { 534 | return None; 535 | } 536 | 537 | Some(integer) 538 | } 539 | } 540 | } 541 | 542 | pub struct FontSet { 543 | d: *mut ffi::FcFontSet, 544 | idx: usize, 545 | } 546 | 547 | impl FontSet { 548 | pub fn is_empty(&self) -> bool { 549 | self.len() == 0 550 | } 551 | 552 | pub fn len(&self) -> usize { 553 | unsafe { (*self.d).nfont as usize } 554 | } 555 | } 556 | 557 | impl Iterator for FontSet { 558 | type Item = PatternRef; 559 | 560 | fn next(&mut self) -> Option { 561 | if self.idx == self.len() { 562 | return None; 563 | } 564 | 565 | let idx = self.idx; 566 | self.idx += 1; 567 | 568 | let d = unsafe { *(*self.d).fonts.add(idx) }; 569 | Some(PatternRef { d }) 570 | } 571 | 572 | fn size_hint(&self) -> (usize, Option) { 573 | (0, Some(self.len())) 574 | } 575 | } 576 | 577 | impl Drop for FontSet { 578 | fn drop(&mut self) { 579 | unsafe { 580 | ffi_dispatch!( 581 | feature = "source-fontconfig-dlopen", 582 | LIB, 583 | FcFontSetDestroy, 584 | self.d 585 | ) 586 | } 587 | } 588 | } 589 | 590 | pub struct ObjectSet { 591 | d: *mut ffi::FcObjectSet, 592 | } 593 | 594 | impl ObjectSet { 595 | // FcObjectSetCreate 596 | pub fn new() -> Self { 597 | unsafe { 598 | ObjectSet { 599 | d: ffi_dispatch!(feature = "source-fontconfig-dlopen", LIB, FcObjectSetCreate,), 600 | } 601 | } 602 | } 603 | 604 | // FcObjectSetAdd 605 | pub fn push_string(&mut self, object: Object) { 606 | unsafe { 607 | // Returns `false` if the property name cannot be inserted 608 | // into the set (due to allocation failure). 609 | assert_eq!( 610 | ffi_dispatch!( 611 | feature = "source-fontconfig-dlopen", 612 | LIB, 613 | FcObjectSetAdd, 614 | self.d, 615 | object.as_ptr() 616 | ), 617 | 1 618 | ); 619 | } 620 | } 621 | } 622 | 623 | impl Drop for ObjectSet { 624 | fn drop(&mut self) { 625 | unsafe { 626 | ffi_dispatch!( 627 | feature = "source-fontconfig-dlopen", 628 | LIB, 629 | FcObjectSetDestroy, 630 | self.d 631 | ) 632 | } 633 | } 634 | } 635 | } 636 | -------------------------------------------------------------------------------- /src/sources/fs.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/sources/fs.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! A source that loads fonts from a directory or directories on disk. 12 | //! 13 | //! This source uses the WalkDir abstraction from the `walkdir` crate to locate fonts. 14 | //! 15 | //! This is the native source on Android and OpenHarmony. 16 | 17 | use std::any::Any; 18 | use std::fs::File; 19 | use std::path::{Path, PathBuf}; 20 | use walkdir::WalkDir; 21 | 22 | #[cfg(not(any(target_os = "android", target_family = "windows", target_env = "ohos")))] 23 | use dirs; 24 | #[cfg(target_family = "windows")] 25 | use std::ffi::OsString; 26 | #[cfg(target_family = "windows")] 27 | use std::os::windows::ffi::OsStringExt; 28 | #[cfg(target_family = "windows")] 29 | use winapi::shared::minwindef::{MAX_PATH, UINT}; 30 | #[cfg(target_family = "windows")] 31 | use winapi::um::sysinfoapi; 32 | 33 | use crate::error::SelectionError; 34 | use crate::family_handle::FamilyHandle; 35 | use crate::family_name::FamilyName; 36 | use crate::file_type::FileType; 37 | use crate::font::Font; 38 | use crate::handle::Handle; 39 | use crate::properties::Properties; 40 | use crate::source::Source; 41 | use crate::sources::mem::MemSource; 42 | 43 | /// A source that loads fonts from a directory or directories on disk. 44 | /// 45 | /// This source uses the WalkDir abstraction from the `walkdir` crate to locate fonts. 46 | /// 47 | /// This is the native source on Android and OpenHarmony. 48 | #[allow(missing_debug_implementations)] 49 | pub struct FsSource { 50 | mem_source: MemSource, 51 | } 52 | 53 | impl Default for FsSource { 54 | fn default() -> Self { 55 | Self::new() 56 | } 57 | } 58 | 59 | impl FsSource { 60 | /// Opens the default set of directories on this platform and indexes the fonts found within. 61 | /// 62 | /// Do not rely on this function for systems other than Android or OpenHarmony. It makes a best 63 | /// effort to locate fonts in the typical platform directories, but it is too simple to pick up 64 | /// fonts that are stored in unusual locations but nevertheless properly installed. 65 | pub fn new() -> FsSource { 66 | let mut fonts = vec![]; 67 | for font_directory in default_font_directories() { 68 | fonts.extend(Self::discover_fonts(&font_directory)); 69 | } 70 | 71 | FsSource { 72 | mem_source: MemSource::from_fonts(fonts.into_iter()).unwrap(), 73 | } 74 | } 75 | 76 | fn discover_fonts(path: &Path) -> Vec { 77 | let mut fonts = vec![]; 78 | for directory_entry in WalkDir::new(path).into_iter() { 79 | let directory_entry = match directory_entry { 80 | Ok(directory_entry) => directory_entry, 81 | Err(_) => continue, 82 | }; 83 | let path = directory_entry.path(); 84 | let mut file = match File::open(path) { 85 | Err(_) => continue, 86 | Ok(file) => file, 87 | }; 88 | match Font::analyze_file(&mut file) { 89 | Err(_) => continue, 90 | Ok(FileType::Single) => fonts.push(Handle::from_path(path.to_owned(), 0)), 91 | Ok(FileType::Collection(font_count)) => { 92 | for font_index in 0..font_count { 93 | fonts.push(Handle::from_path(path.to_owned(), font_index)) 94 | } 95 | } 96 | } 97 | } 98 | fonts 99 | } 100 | 101 | /// Indexes all fonts found in `path` 102 | pub fn in_path

(path: P) -> FsSource 103 | where 104 | P: AsRef, 105 | { 106 | let fonts = Self::discover_fonts(path.as_ref()); 107 | FsSource { 108 | mem_source: MemSource::from_fonts(fonts.into_iter()).unwrap(), 109 | } 110 | } 111 | 112 | /// Returns paths of all fonts installed on the system. 113 | pub fn all_fonts(&self) -> Result, SelectionError> { 114 | self.mem_source.all_fonts() 115 | } 116 | 117 | /// Returns the names of all families installed on the system. 118 | pub fn all_families(&self) -> Result, SelectionError> { 119 | self.mem_source.all_families() 120 | } 121 | 122 | /// Looks up a font family by name and returns the handles of all the fonts in that family. 123 | pub fn select_family_by_name(&self, family_name: &str) -> Result { 124 | self.mem_source.select_family_by_name(family_name) 125 | } 126 | 127 | /// Selects a font by PostScript name, which should be a unique identifier. 128 | /// 129 | /// This implementation does a brute-force search of installed fonts to find the one that 130 | /// matches. 131 | pub fn select_by_postscript_name( 132 | &self, 133 | postscript_name: &str, 134 | ) -> Result { 135 | self.mem_source.select_by_postscript_name(postscript_name) 136 | } 137 | 138 | /// Performs font matching according to the CSS Fonts Level 3 specification and returns the 139 | /// handle. 140 | #[inline] 141 | pub fn select_best_match( 142 | &self, 143 | family_names: &[FamilyName], 144 | properties: &Properties, 145 | ) -> Result { 146 | ::select_best_match(self, family_names, properties) 147 | } 148 | } 149 | 150 | impl Source for FsSource { 151 | #[inline] 152 | fn all_fonts(&self) -> Result, SelectionError> { 153 | self.all_fonts() 154 | } 155 | 156 | #[inline] 157 | fn all_families(&self) -> Result, SelectionError> { 158 | self.all_families() 159 | } 160 | 161 | fn select_family_by_name(&self, family_name: &str) -> Result { 162 | self.select_family_by_name(family_name) 163 | } 164 | 165 | fn select_by_postscript_name(&self, postscript_name: &str) -> Result { 166 | self.select_by_postscript_name(postscript_name) 167 | } 168 | 169 | #[inline] 170 | fn as_any(&self) -> &dyn Any { 171 | self 172 | } 173 | 174 | #[inline] 175 | fn as_mut_any(&mut self) -> &mut dyn Any { 176 | self 177 | } 178 | } 179 | 180 | #[cfg(any(target_os = "android", target_env = "ohos"))] 181 | fn default_font_directories() -> Vec { 182 | vec![PathBuf::from("/system/fonts")] 183 | } 184 | 185 | #[cfg(target_family = "windows")] 186 | fn default_font_directories() -> Vec { 187 | unsafe { 188 | let mut buffer = vec![0; MAX_PATH]; 189 | let len = sysinfoapi::GetWindowsDirectoryW(buffer.as_mut_ptr(), buffer.len() as UINT); 190 | assert!(len != 0); 191 | buffer.truncate(len as usize); 192 | 193 | let mut path = PathBuf::from(OsString::from_wide(&buffer)); 194 | path.push("Fonts"); 195 | vec![path] 196 | } 197 | } 198 | 199 | #[cfg(target_os = "macos")] 200 | fn default_font_directories() -> Vec { 201 | let mut directories = vec![ 202 | PathBuf::from("/System/Library/Fonts"), 203 | PathBuf::from("/Library/Fonts"), 204 | PathBuf::from("/Network/Library/Fonts"), 205 | ]; 206 | if let Some(mut path) = dirs::home_dir() { 207 | path.push("Library"); 208 | path.push("Fonts"); 209 | directories.push(path); 210 | } 211 | directories 212 | } 213 | 214 | #[cfg(not(any( 215 | target_os = "android", 216 | target_family = "windows", 217 | target_os = "macos", 218 | target_env = "ohos" 219 | )))] 220 | fn default_font_directories() -> Vec { 221 | let mut directories = vec![ 222 | PathBuf::from("/usr/share/fonts"), 223 | PathBuf::from("/usr/local/share/fonts"), 224 | // Flatpak specific 225 | PathBuf::from("/run/host/fonts"), 226 | PathBuf::from("/run/host/local-fonts"), 227 | PathBuf::from("/run/host/user-fonts"), 228 | ]; 229 | if let Some(path) = dirs::home_dir() { 230 | directories.push(path.join(".fonts")); // ~/.fonts is deprecated 231 | directories.push(path.join(".local").join("share").join("fonts")); 232 | } 233 | if let Some(mut path) = dirs::data_dir() { 234 | path.push("fonts"); 235 | directories.push(path); 236 | } 237 | directories 238 | } 239 | -------------------------------------------------------------------------------- /src/sources/mem.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/sources/mem.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! A source that keeps fonts in memory. 12 | 13 | use crate::error::{FontLoadingError, SelectionError}; 14 | use crate::family_handle::FamilyHandle; 15 | use crate::family_name::FamilyName; 16 | use crate::font::Font; 17 | use crate::handle::Handle; 18 | use crate::properties::Properties; 19 | use crate::source::Source; 20 | use std::any::Any; 21 | 22 | /// A source that keeps fonts in memory. 23 | #[allow(missing_debug_implementations)] 24 | pub struct MemSource { 25 | families: Vec, 26 | } 27 | 28 | impl MemSource { 29 | /// Creates a new empty memory source. 30 | pub fn empty() -> MemSource { 31 | MemSource { families: vec![] } 32 | } 33 | 34 | /// Creates a new memory source that contains the given set of font handles. 35 | /// 36 | /// The fonts referenced by the handles are eagerly loaded into memory. 37 | pub fn from_fonts(fonts: I) -> Result 38 | where 39 | I: Iterator, 40 | { 41 | let mut families = vec![]; 42 | for handle in fonts { 43 | add_font(handle, &mut families)?; 44 | } 45 | families.sort_by(|a, b| a.family_name.cmp(&b.family_name)); 46 | Ok(MemSource { families }) 47 | } 48 | 49 | /// Add an existing font handle to a `MemSource`. 50 | /// 51 | /// Returns the font that was just added. 52 | /// 53 | /// Note that adding fonts to an existing `MemSource` is slower than creating a new one from a 54 | /// `Handle` iterator, since this method sorts after every addition, rather than once at the 55 | /// end. 56 | pub fn add_font(&mut self, handle: Handle) -> Result { 57 | let font = add_font(handle, &mut self.families)?; 58 | self.families 59 | .sort_by(|a, b| a.family_name.cmp(&b.family_name)); 60 | Ok(font) 61 | } 62 | 63 | /// Add a number of existing font handles to a `MemSource`. 64 | /// 65 | /// Note that adding fonts to an existing `MemSource` is slower than creating a new one from a 66 | /// `Handle` iterator, because extra unnecessary sorting with occur with every call to this 67 | /// method. 68 | pub fn add_fonts( 69 | &mut self, 70 | handles: impl Iterator, 71 | ) -> Result<(), FontLoadingError> { 72 | for handle in handles { 73 | add_font(handle, &mut self.families)?; 74 | } 75 | self.families 76 | .sort_by(|a, b| a.family_name.cmp(&b.family_name)); 77 | Ok(()) 78 | } 79 | 80 | /// Returns paths of all fonts installed on the system. 81 | pub fn all_fonts(&self) -> Result, SelectionError> { 82 | Ok(self 83 | .families 84 | .iter() 85 | .map(|family| family.font.clone()) 86 | .collect()) 87 | } 88 | 89 | /// Returns the names of all families installed on the system. 90 | pub fn all_families(&self) -> Result, SelectionError> { 91 | let mut families = vec![]; 92 | for family in &self.families { 93 | if families.last() == Some(&family.family_name) { 94 | continue; 95 | } 96 | 97 | families.push(family.family_name.clone()); 98 | } 99 | 100 | Ok(families) 101 | } 102 | 103 | /// Looks up a font family by name and returns the handles of all the fonts in that family. 104 | /// 105 | /// FIXME(pcwalton): Case-insensitive comparison. 106 | pub fn select_family_by_name(&self, family_name: &str) -> Result { 107 | let mut first_family_index = self 108 | .families 109 | .binary_search_by(|family| (*family.family_name).cmp(family_name)) 110 | .map_err(|_| SelectionError::NotFound)?; 111 | 112 | while first_family_index > 0 113 | && self.families[first_family_index - 1].family_name == family_name 114 | { 115 | first_family_index -= 1 116 | } 117 | let mut last_family_index = first_family_index; 118 | while last_family_index + 1 < self.families.len() 119 | && self.families[last_family_index + 1].family_name == family_name 120 | { 121 | last_family_index += 1 122 | } 123 | 124 | let families = &self.families[first_family_index..(last_family_index + 1)]; 125 | Ok(FamilyHandle::from_font_handles( 126 | families.iter().map(|family| family.font.clone()), 127 | )) 128 | } 129 | 130 | /// Selects a font by PostScript name, which should be a unique identifier. 131 | /// 132 | /// The default implementation, which is used by the DirectWrite and the filesystem backends, 133 | /// does a brute-force search of installed fonts to find the one that matches. 134 | pub fn select_by_postscript_name( 135 | &self, 136 | postscript_name: &str, 137 | ) -> Result { 138 | self.families 139 | .iter() 140 | .filter(|family_entry| family_entry.postscript_name == postscript_name) 141 | .map(|family_entry| family_entry.font.clone()) 142 | .next() 143 | .ok_or(SelectionError::NotFound) 144 | } 145 | 146 | /// Performs font matching according to the CSS Fonts Level 3 specification and returns the 147 | /// handle. 148 | #[inline] 149 | pub fn select_best_match( 150 | &self, 151 | family_names: &[FamilyName], 152 | properties: &Properties, 153 | ) -> Result { 154 | ::select_best_match(self, family_names, properties) 155 | } 156 | } 157 | 158 | impl Source for MemSource { 159 | #[inline] 160 | fn all_fonts(&self) -> Result, SelectionError> { 161 | self.all_fonts() 162 | } 163 | 164 | #[inline] 165 | fn all_families(&self) -> Result, SelectionError> { 166 | self.all_families() 167 | } 168 | 169 | fn select_family_by_name(&self, family_name: &str) -> Result { 170 | self.select_family_by_name(family_name) 171 | } 172 | 173 | fn select_by_postscript_name(&self, postscript_name: &str) -> Result { 174 | self.select_by_postscript_name(postscript_name) 175 | } 176 | 177 | #[inline] 178 | fn as_any(&self) -> &dyn Any { 179 | self 180 | } 181 | 182 | #[inline] 183 | fn as_mut_any(&mut self) -> &mut dyn Any { 184 | self 185 | } 186 | } 187 | 188 | /// Adds a font, but doesn't sort. Returns the font that was created to check for validity. 189 | fn add_font(handle: Handle, families: &mut Vec) -> Result { 190 | let font = Font::from_handle(&handle)?; 191 | if let Some(postscript_name) = font.postscript_name() { 192 | families.push(FamilyEntry { 193 | family_name: font.family_name(), 194 | postscript_name, 195 | font: handle, 196 | }) 197 | } 198 | Ok(font) 199 | } 200 | 201 | struct FamilyEntry { 202 | family_name: String, 203 | postscript_name: String, 204 | font: Handle, 205 | } 206 | -------------------------------------------------------------------------------- /src/sources/mod.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/sources/mod.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Various databases of installed fonts that can be queried. 12 | //! 13 | //! The system-specific sources (Core Text, DirectWrite, and Fontconfig) contain the fonts that are 14 | //! installed on the system. The remaining databases (`fs`, `mem`, and `multi`) allow `font-kit` to 15 | //! query fonts not installed on the system. 16 | 17 | #[cfg(any(target_os = "macos", target_os = "ios"))] 18 | pub mod core_text; 19 | 20 | #[cfg(target_family = "windows")] 21 | pub mod directwrite; 22 | 23 | #[cfg(any( 24 | not(any( 25 | target_os = "macos", 26 | target_os = "ios", 27 | target_family = "windows", 28 | target_arch = "wasm32", 29 | target_env = "ohos", 30 | )), 31 | feature = "source-fontconfig" 32 | ))] 33 | pub mod fontconfig; 34 | 35 | #[cfg(not(target_arch = "wasm32"))] 36 | pub mod fs; 37 | 38 | pub mod mem; 39 | 40 | pub mod multi; 41 | -------------------------------------------------------------------------------- /src/sources/multi.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/sources/multi.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! A source that encapsulates multiple sources and allows them to be queried as a group. 12 | //! 13 | //! This is useful when an application wants a library of fonts consisting of the installed system 14 | //! fonts plus some other application-supplied fonts. 15 | 16 | use crate::error::SelectionError; 17 | use crate::family_handle::FamilyHandle; 18 | use crate::family_name::FamilyName; 19 | use crate::handle::Handle; 20 | use crate::properties::Properties; 21 | use crate::source::Source; 22 | use std::{ 23 | any::Any, 24 | fmt, 25 | ops::{Index, IndexMut}, 26 | slice, 27 | }; 28 | 29 | /// A source that encapsulates multiple sources and allows them to be queried as a group. 30 | /// 31 | /// This is useful when an application wants a library of fonts consisting of the installed system 32 | /// fonts plus some other application-supplied fonts. 33 | #[allow(missing_debug_implementations)] 34 | pub struct MultiSource { 35 | subsources: Vec>, 36 | } 37 | 38 | impl MultiSource { 39 | /// Creates a new source that contains all the fonts in the supplied sources. 40 | pub fn from_sources(subsources: Vec>) -> MultiSource { 41 | MultiSource { subsources } 42 | } 43 | 44 | /// Returns paths of all fonts installed on the system. 45 | pub fn all_fonts(&self) -> Result, SelectionError> { 46 | let mut handles = vec![]; 47 | for subsource in &self.subsources { 48 | handles.extend(subsource.all_fonts()?.into_iter()) 49 | } 50 | Ok(handles) 51 | } 52 | 53 | /// Returns the names of all families installed on the system. 54 | pub fn all_families(&self) -> Result, SelectionError> { 55 | let mut families = vec![]; 56 | for subsource in &self.subsources { 57 | families.extend(subsource.all_families()?.into_iter()) 58 | } 59 | Ok(families) 60 | } 61 | 62 | /// Looks up a font family by name and returns the handles of all the fonts in that family. 63 | pub fn select_family_by_name(&self, family_name: &str) -> Result { 64 | for subsource in &self.subsources { 65 | match subsource.select_family_by_name(family_name) { 66 | Ok(family) => return Ok(family), 67 | Err(SelectionError::NotFound) => {} 68 | Err(err) => return Err(err), 69 | } 70 | } 71 | Err(SelectionError::NotFound) 72 | } 73 | 74 | /// Selects a font by PostScript name, which should be a unique identifier. 75 | pub fn select_by_postscript_name( 76 | &self, 77 | postscript_name: &str, 78 | ) -> Result { 79 | for subsource in &self.subsources { 80 | match subsource.select_by_postscript_name(postscript_name) { 81 | Ok(font) => return Ok(font), 82 | Err(SelectionError::NotFound) => {} 83 | Err(err) => return Err(err), 84 | } 85 | } 86 | Err(SelectionError::NotFound) 87 | } 88 | 89 | /// Performs font matching according to the CSS Fonts Level 3 specification and returns the 90 | /// handle. 91 | #[inline] 92 | pub fn select_best_match( 93 | &self, 94 | family_names: &[FamilyName], 95 | properties: &Properties, 96 | ) -> Result { 97 | ::select_best_match(self, family_names, properties) 98 | } 99 | 100 | /// Returns an iterator over the contained sources. 101 | #[inline] 102 | pub fn iter(&self) -> MultiIter<'_> { 103 | MultiIter(self.subsources.iter()) 104 | } 105 | 106 | /// Returns an iterator over the contained sources with mutable access. 107 | #[inline] 108 | pub fn iter_mut(&mut self) -> MultiIterMut<'_> { 109 | MultiIterMut(self.subsources.iter_mut()) 110 | } 111 | 112 | /// A convenience method to get the first source with the given type. 113 | /// 114 | /// Returns `None` if no source of the given type was found. 115 | pub fn find_source(&self) -> Option<&T> { 116 | self.iter().find_map(|v| v.as_any().downcast_ref()) 117 | } 118 | 119 | /// A convenience method to get the first source with the given type. 120 | /// 121 | /// Returns `None` if no source of the given type was found. 122 | pub fn find_source_mut(&mut self) -> Option<&mut T> { 123 | self.iter_mut().find_map(|v| v.as_mut_any().downcast_mut()) 124 | } 125 | } 126 | 127 | impl Source for MultiSource { 128 | #[inline] 129 | fn all_fonts(&self) -> Result, SelectionError> { 130 | self.all_fonts() 131 | } 132 | 133 | #[inline] 134 | fn all_families(&self) -> Result, SelectionError> { 135 | self.all_families() 136 | } 137 | 138 | #[inline] 139 | fn select_family_by_name(&self, family_name: &str) -> Result { 140 | self.select_family_by_name(family_name) 141 | } 142 | 143 | #[inline] 144 | fn select_by_postscript_name(&self, postscript_name: &str) -> Result { 145 | self.select_by_postscript_name(postscript_name) 146 | } 147 | 148 | fn as_any(&self) -> &dyn Any { 149 | self 150 | } 151 | 152 | fn as_mut_any(&mut self) -> &mut dyn Any { 153 | self 154 | } 155 | } 156 | 157 | impl Index for MultiSource { 158 | type Output = dyn Source; 159 | 160 | fn index(&self, idx: usize) -> &Self::Output { 161 | &*self.subsources[idx] 162 | } 163 | } 164 | 165 | impl IndexMut for MultiSource { 166 | fn index_mut(&mut self, idx: usize) -> &mut Self::Output { 167 | &mut *self.subsources[idx] 168 | } 169 | } 170 | 171 | /// An iterator over the sources in a [`MultiSource`]. 172 | pub struct MultiIter<'a>(slice::Iter<'a, Box>); 173 | 174 | impl<'a> Iterator for MultiIter<'a> { 175 | type Item = &'a dyn Source; 176 | 177 | fn next(&mut self) -> Option { 178 | self.0.next().map(|v| &**v) 179 | } 180 | } 181 | 182 | impl fmt::Debug for MultiIter<'_> { 183 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 184 | f.debug_struct("MultiIter").finish() 185 | } 186 | } 187 | 188 | /// An iterator over the mutable sources in a [`MultiSource`]. 189 | pub struct MultiIterMut<'a>(slice::IterMut<'a, Box>); 190 | 191 | impl<'a> Iterator for MultiIterMut<'a> { 192 | type Item = &'a mut dyn Source; 193 | 194 | fn next(&mut self) -> Option { 195 | self.0.next().map(|v| &mut **v) 196 | } 197 | } 198 | 199 | impl fmt::Debug for MultiIterMut<'_> { 200 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 201 | f.debug_struct("MultiIterMut").finish() 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | // font-kit/src/utils.rs 2 | // 3 | // Copyright © 2018 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Miscellaneous utilities for use in this crate. 12 | 13 | #![allow(dead_code)] 14 | 15 | use std::fs::File; 16 | use std::io::{Error as IOError, Read}; 17 | 18 | pub(crate) static SFNT_VERSIONS: [[u8; 4]; 4] = [ 19 | [0x00, 0x01, 0x00, 0x00], 20 | [b'O', b'T', b'T', b'O'], 21 | [b't', b'r', b'u', b'e'], 22 | [b't', b'y', b'p', b'1'], 23 | ]; 24 | 25 | pub(crate) fn clamp(x: f32, min: f32, max: f32) -> f32 { 26 | if x < min { 27 | min 28 | } else if x > max { 29 | max 30 | } else { 31 | x 32 | } 33 | } 34 | 35 | #[inline] 36 | pub(crate) fn lerp(a: f32, b: f32, t: f32) -> f32 { 37 | a + (b - a) * t 38 | } 39 | 40 | #[inline] 41 | pub(crate) fn div_round_up(a: usize, b: usize) -> usize { 42 | (a + b - 1) / b 43 | } 44 | 45 | pub(crate) fn slurp_file(file: &mut File) -> Result, IOError> { 46 | let mut data = match file.metadata() { 47 | Ok(metadata) => Vec::with_capacity(metadata.len() as usize), 48 | Err(_) => vec![], 49 | }; 50 | file.read_to_end(&mut data)?; 51 | Ok(data) 52 | } 53 | -------------------------------------------------------------------------------- /tests/select_font.rs: -------------------------------------------------------------------------------- 1 | // font-kit/tests/select-font.rs 2 | // 3 | // Copyright © 2019 The Pathfinder Project Developers. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | extern crate font_kit; 12 | 13 | use font_kit::error::SelectionError; 14 | use font_kit::family_name::FamilyName; 15 | use font_kit::handle::Handle; 16 | use font_kit::properties::Properties; 17 | use std::ffi::OsStr; 18 | 19 | #[cfg(feature = "source")] 20 | use font_kit::source::SystemSource; 21 | 22 | #[cfg(all(feature = "source", any(target_os = "windows", target_os = "macos")))] 23 | macro_rules! match_handle { 24 | ($handle:expr, $path:expr, $index:expr) => { 25 | match $handle { 26 | Handle::Path { 27 | ref path, 28 | font_index, 29 | } => { 30 | assert_eq!(path, path); 31 | assert_eq!( 32 | font_index, $index, 33 | "expecting font index {} not {}", 34 | font_index, $index 35 | ); 36 | } 37 | Handle::Memory { 38 | bytes: _, 39 | font_index, 40 | } => { 41 | assert_eq!( 42 | font_index, $index, 43 | "expecting font index {} not {}", 44 | font_index, $index 45 | ); 46 | } 47 | } 48 | }; 49 | } 50 | 51 | #[inline(always)] 52 | fn check_filename(handle: &Handle, filename: &str) { 53 | match *handle { 54 | Handle::Path { 55 | ref path, 56 | font_index, 57 | } => { 58 | assert_eq!(path.file_name(), Some(OsStr::new(filename))); 59 | assert_eq!(font_index, 0); 60 | } 61 | _ => panic!("Expected path handle!"), 62 | } 63 | } 64 | 65 | #[cfg(all(feature = "source", target_os = "windows"))] 66 | mod test { 67 | use super::*; 68 | 69 | #[test] 70 | fn select_best_match_serif() { 71 | let handle = SystemSource::new() 72 | .select_best_match(&[FamilyName::Serif], &Properties::default()) 73 | .unwrap(); 74 | match_handle!(handle, "C:\\WINDOWS\\FONTS\\TIMES.TTF", 0); 75 | } 76 | 77 | #[test] 78 | fn select_best_match_sans_serif() { 79 | let handle = SystemSource::new() 80 | .select_best_match(&[FamilyName::SansSerif], &Properties::default()) 81 | .unwrap(); 82 | match_handle!(handle, ":\\WINDOWS\\FONTS\\ARIAL.TTF", 0); 83 | } 84 | 85 | #[test] 86 | fn select_best_match_monospace() { 87 | let handle = SystemSource::new() 88 | .select_best_match(&[FamilyName::Monospace], &Properties::default()) 89 | .unwrap(); 90 | match_handle!(handle, "C:\\WINDOWS\\FONTS\\COUR.TTF", 0); 91 | } 92 | 93 | #[test] 94 | fn select_best_match_cursive() { 95 | let handle = SystemSource::new() 96 | .select_best_match(&[FamilyName::Cursive], &Properties::default()) 97 | .unwrap(); 98 | match_handle!(handle, "C:\\WINDOWS\\FONTS\\COMIC.TTF", 0); 99 | } 100 | 101 | // TODO: no such font in a Travis CI instance. 102 | // https://github.com/servo/font-kit/issues/62 103 | // #[test] 104 | // fn select_best_match_fantasy() { 105 | // let handle = SystemSource::new() 106 | // .select_best_match(&[FamilyName::Fantasy], &Properties::default()) 107 | // .unwrap(); 108 | // match_handle!(handle, "C:\\WINDOWS\\FONTS\\IMPACT.TTF", 0); 109 | // } 110 | 111 | #[test] 112 | fn select_best_match_bold_sans_serif() { 113 | let handle = SystemSource::new() 114 | .select_best_match( 115 | &[FamilyName::SansSerif], 116 | &Properties { 117 | style: font_kit::properties::Style::Normal, 118 | weight: font_kit::properties::Weight::BOLD, 119 | stretch: font_kit::properties::Stretch::NORMAL, 120 | }, 121 | ) 122 | .unwrap(); 123 | match_handle!(handle, "C:\\WINDOWS\\FONTS\\ARIALBD.TTF", 0); 124 | } 125 | 126 | #[test] 127 | fn select_best_match_by_name_after_invalid() { 128 | let handle = SystemSource::new() 129 | .select_best_match( 130 | &[ 131 | FamilyName::Title("Invalid".to_string()), 132 | FamilyName::Title("Times New Roman".to_string()), 133 | ], 134 | &Properties::default(), 135 | ) 136 | .unwrap(); 137 | match_handle!(handle, "C:\\WINDOWS\\FONTS\\TIMES.TTF", 0); 138 | } 139 | 140 | #[test] 141 | fn select_family_by_name_arial() { 142 | let family = SystemSource::new().select_family_by_name("Arial").unwrap(); 143 | assert_eq!(family.fonts().len(), 8); 144 | match_handle!(family.fonts()[0], "C:\\WINDOWS\\FONTS\\ARIAL.TTF", 0); 145 | match_handle!(family.fonts()[1], "C:\\WINDOWS\\FONTS\\ARIALI.TTF", 0); 146 | match_handle!(family.fonts()[2], "C:\\WINDOWS\\FONTS\\ARIALBD.TTF", 0); 147 | match_handle!(family.fonts()[3], "C:\\WINDOWS\\FONTS\\ARIALBI.TTF", 0); 148 | match_handle!(family.fonts()[4], "C:\\WINDOWS\\FONTS\\ARIBLK.TTF", 0); 149 | match_handle!(family.fonts()[5], "C:\\WINDOWS\\FONTS\\ARIAL.TTF", 0); 150 | match_handle!(family.fonts()[6], "C:\\WINDOWS\\FONTS\\ARIALBD.TTF", 0); 151 | match_handle!(family.fonts()[7], "C:\\WINDOWS\\FONTS\\ARIBLK.TTF", 0); 152 | } 153 | 154 | #[allow(non_snake_case)] 155 | #[test] 156 | fn select_by_postscript_name_ArialMT() { 157 | let font = SystemSource::new() 158 | .select_by_postscript_name("ArialMT") 159 | .unwrap() 160 | .load() 161 | .unwrap(); 162 | 163 | assert_eq!(font.postscript_name().unwrap(), "ArialMT"); 164 | } 165 | 166 | #[test] 167 | fn select_by_postscript_name_invalid() { 168 | match SystemSource::new().select_by_postscript_name("zxhjfgkadsfhg") { 169 | Err(SelectionError::NotFound) => {} 170 | other => panic!("unexpected error: {:?}", other), 171 | } 172 | } 173 | 174 | // This test fails on TravisCI's Windows environment. 175 | #[test] 176 | #[ignore] 177 | fn select_localized_family_name() { 178 | let handle = SystemSource::new() 179 | .select_best_match( 180 | &[FamilyName::Title("MS ゴシック".to_string())], 181 | &Properties::default(), 182 | ) 183 | .unwrap(); 184 | match_handle!(handle, "C:\\WINDOWS\\FONTS\\msgothic.ttc", 0); 185 | } 186 | } 187 | 188 | #[cfg(all(feature = "source", target_os = "linux"))] 189 | mod test { 190 | use super::*; 191 | 192 | #[test] 193 | fn select_best_match_serif() { 194 | let handle = SystemSource::new() 195 | .select_best_match(&[FamilyName::Serif], &Properties::default()) 196 | .unwrap(); 197 | check_filename(&handle, "DejaVuSerif.ttf"); 198 | } 199 | 200 | #[test] 201 | fn select_best_match_sans_serif() { 202 | let handle = SystemSource::new() 203 | .select_best_match(&[FamilyName::SansSerif], &Properties::default()) 204 | .unwrap(); 205 | check_filename(&handle, "DejaVuSans.ttf"); 206 | } 207 | 208 | #[test] 209 | fn select_best_match_monospace() { 210 | let handle = SystemSource::new() 211 | .select_best_match(&[FamilyName::Monospace], &Properties::default()) 212 | .unwrap(); 213 | check_filename(&handle, "DejaVuSansMono.ttf"); 214 | } 215 | 216 | #[test] 217 | fn select_best_match_bold_sans_serif() { 218 | let handle = SystemSource::new() 219 | .select_best_match( 220 | &[FamilyName::SansSerif], 221 | &Properties { 222 | style: font_kit::properties::Style::Normal, 223 | weight: font_kit::properties::Weight::BOLD, 224 | stretch: font_kit::properties::Stretch::NORMAL, 225 | }, 226 | ) 227 | .unwrap(); 228 | check_filename(&handle, "DejaVuSans-Bold.ttf"); 229 | } 230 | 231 | #[test] 232 | fn select_best_match_by_name_after_invalid() { 233 | let handle = SystemSource::new() 234 | .select_best_match( 235 | &[ 236 | FamilyName::Title("Invalid".to_string()), 237 | FamilyName::Title("DejaVu Sans".to_string()), 238 | ], 239 | &Properties::default(), 240 | ) 241 | .unwrap(); 242 | check_filename(&handle, "DejaVuSans.ttf"); 243 | } 244 | 245 | #[test] 246 | fn select_family_by_name_dejavu() { 247 | let family = SystemSource::new() 248 | .select_family_by_name("DejaVu Sans") 249 | .unwrap(); 250 | let filenames: Vec = family 251 | .fonts() 252 | .iter() 253 | .map(|handle| match *handle { 254 | Handle::Path { 255 | ref path, 256 | font_index, 257 | } => { 258 | assert_eq!(font_index, 0); 259 | path.file_name() 260 | .expect("Where's the filename?") 261 | .to_string_lossy() 262 | .into_owned() 263 | } 264 | _ => panic!("Expected path handle!"), 265 | }) 266 | .collect(); 267 | assert!(filenames.iter().any(|name| name == "DejaVuSans-Bold.ttf")); 268 | assert!(filenames.iter().any(|name| name == "DejaVuSans.ttf")); 269 | } 270 | 271 | #[allow(non_snake_case)] 272 | #[test] 273 | fn select_by_postscript_name_ArialMT() { 274 | let font = SystemSource::new() 275 | .select_by_postscript_name("DejaVuSans") 276 | .unwrap() 277 | .load() 278 | .unwrap(); 279 | 280 | assert_eq!(font.postscript_name().unwrap(), "DejaVuSans"); 281 | } 282 | 283 | #[test] 284 | fn select_by_postscript_name_invalid() { 285 | match SystemSource::new().select_by_postscript_name("zxhjfgkadsfhg") { 286 | Err(SelectionError::NotFound) => {} 287 | other => panic!("unexpected error: {:?}", other), 288 | } 289 | } 290 | 291 | #[test] 292 | fn select_localized_family_name() { 293 | if let Ok(handle) = SystemSource::new().select_best_match( 294 | &[FamilyName::Title("さざなみゴシック".to_string())], 295 | &Properties::default(), 296 | ) { 297 | check_filename(&handle, "sazanami-gothic.ttf"); 298 | } 299 | } 300 | } 301 | 302 | #[cfg(all(feature = "source", target_os = "macos"))] 303 | mod test { 304 | use super::*; 305 | 306 | #[test] 307 | fn select_best_match_serif() { 308 | let handle = SystemSource::new() 309 | .select_best_match(&[FamilyName::Serif], &Properties::default()) 310 | .unwrap(); 311 | match_handle!(handle, "/Library/Fonts/Times New Roman.ttf", 0); 312 | } 313 | 314 | #[test] 315 | fn select_best_match_sans_serif() { 316 | let handle = SystemSource::new() 317 | .select_best_match(&[FamilyName::SansSerif], &Properties::default()) 318 | .unwrap(); 319 | match_handle!(handle, "/Library/Fonts/Arial.ttf", 0); 320 | } 321 | 322 | #[test] 323 | fn select_best_match_monospace() { 324 | let handle = SystemSource::new() 325 | .select_best_match(&[FamilyName::Monospace], &Properties::default()) 326 | .unwrap(); 327 | match_handle!(handle, "/Library/Fonts/Courier New.ttf", 0); 328 | } 329 | 330 | #[test] 331 | fn select_best_match_cursive() { 332 | let handle = SystemSource::new() 333 | .select_best_match(&[FamilyName::Cursive], &Properties::default()) 334 | .unwrap(); 335 | match_handle!(handle, "/Library/Fonts/Comic Sans MS.ttf", 0); 336 | } 337 | 338 | #[test] 339 | fn select_best_match_fantasy() { 340 | let handle = SystemSource::new() 341 | .select_best_match(&[FamilyName::Fantasy], &Properties::default()) 342 | .unwrap(); 343 | match_handle!(handle, "/Library/Fonts/Papyrus.ttc", 1); 344 | } 345 | 346 | #[test] 347 | fn select_best_match_bold_sans_serif() { 348 | let handle = SystemSource::new() 349 | .select_best_match( 350 | &[FamilyName::SansSerif], 351 | &Properties { 352 | style: font_kit::properties::Style::Normal, 353 | weight: font_kit::properties::Weight::BOLD, 354 | stretch: font_kit::properties::Stretch::NORMAL, 355 | }, 356 | ) 357 | .unwrap(); 358 | match_handle!(handle, "/Library/Fonts/Arial Bold.ttf", 0); 359 | } 360 | 361 | #[test] 362 | fn select_best_match_by_name_after_invalid() { 363 | let handle = SystemSource::new() 364 | .select_best_match( 365 | &[ 366 | FamilyName::Title("Invalid".to_string()), 367 | FamilyName::Title("Times New Roman".to_string()), 368 | ], 369 | &Properties::default(), 370 | ) 371 | .unwrap(); 372 | match_handle!(handle, "/Library/Fonts/Times New Roman.ttf", 0); 373 | } 374 | 375 | #[test] 376 | fn select_family_by_name_arial() { 377 | let family = SystemSource::new().select_family_by_name("Arial").unwrap(); 378 | assert_eq!(family.fonts().len(), 4); 379 | match_handle!(family.fonts()[0], "/Library/Fonts/Arial.ttf", 0); 380 | match_handle!(family.fonts()[1], "/Library/Fonts/Arial Bold.ttf", 0); 381 | match_handle!(family.fonts()[2], "/Library/Fonts/Arial Bold Italic.ttf", 0); 382 | match_handle!(family.fonts()[3], "/Library/Fonts/Arial Italic.ttf", 0); 383 | } 384 | 385 | #[allow(non_snake_case)] 386 | #[test] 387 | fn select_by_postscript_name_ArialMT() { 388 | let font = SystemSource::new() 389 | .select_by_postscript_name("ArialMT") 390 | .unwrap() 391 | .load() 392 | .unwrap(); 393 | 394 | assert_eq!(font.postscript_name().unwrap(), "ArialMT"); 395 | } 396 | 397 | #[test] 398 | fn select_by_postscript_name_invalid() { 399 | match SystemSource::new().select_by_postscript_name("zxhjfgkadsfhg") { 400 | Err(SelectionError::NotFound) => {} 401 | other => panic!("unexpected error: {:?}", other), 402 | } 403 | } 404 | 405 | #[test] 406 | fn select_localized_family_name() { 407 | let handle = SystemSource::new() 408 | .select_best_match( 409 | &[FamilyName::Title("רעננה".to_string())], 410 | &Properties::default(), 411 | ) 412 | .unwrap(); 413 | match_handle!(handle, "/Library/Fonts/Raanana.ttc", 0); 414 | } 415 | } 416 | --------------------------------------------------------------------------------