rusttype-0.9.3/.cargo_vcs_info.json0000644000000001360000000000100127440ustar { "git": { "sha1": "99ee9676e3143337806a4e4aab6ece1fad04ae82" }, "path_in_vcs": "" }rusttype-0.9.3/.gitignore000064400000000000000000000000551046102023000135240ustar 00000000000000target Cargo.lock image_example.png rls.toml rusttype-0.9.3/.gitlab-ci.yml000064400000000000000000000027131046102023000141730ustar 00000000000000image: "rust:latest" stages: - build - test before_script: - rustup toolchain add $toolchain variables: CARGO_INCREMENTAL: 0 RUST_BACKTRACE: 1 build:stable: stage: build variables: toolchain: stable script: - cargo +stable build test:stable: stage: test variables: toolchain: stable dependencies: - build:stable script: - cargo +stable test build:stable:gpu_cache: stage: build variables: toolchain: stable script: - cargo +stable build --features 'gpu_cache' test:stable:gpu_cache: stage: test variables: toolchain: stable dependencies: - build:stable:gpu_cache script: - cargo +stable test --features 'gpu_cache' # Heavier testing using "dev" test:dev: stage: test variables: toolchain: stable dependencies: - build:stable:gpu_cache script: - cargo test --workspace --all-features - cargo test --workspace --all-features --benches build:nightly:gpu_cache: stage: build variables: toolchain: nightly script: - cargo +nightly build --features 'gpu_cache' build:redox: stage: build variables: toolchain: nightly script: - rustup target add x86_64-unknown-redox --toolchain nightly - cargo +nightly build --target=x86_64-unknown-redox -v build:no_std: stage: build variables: toolchain: stable script: - rustup target add thumbv6m-none-eabi --toolchain stable - cargo +stable build --target thumbv6m-none-eabi --no-default-features --features libm-math -v rusttype-0.9.3/.travis.yml000064400000000000000000000001371046102023000136460ustar 00000000000000language: rust rust: stable env: RUST_BACKTRACE=full script: cargo test --features 'gpu_cache' rusttype-0.9.3/CHANGELOG.md000064400000000000000000000143521046102023000133520ustar 00000000000000## Unreleased * Improve lifetime flexibility for `Font::glyphs_for` & `Font::layout`. * Update owned_ttf_parser -> `0.15`. * Update ab_glyph_rasterizer => `0.1.5`. * Update crossbeam-queue -> `0.8`. * Update crossbeam-utils -> `0.8`. * Update num_cpus => `1.13`. * Update approx => `0.5`. ## 0.9.2 * Update ttf-parser -> `0.6`. * Use more flexible lifetime bounds for `Font::layout`. ## 0.9.1 * Use crate owned_ttf_parser to provide `OwnedFont` eliminating direct unsafe usage in rusttype. * Remove unused legacy trait `BoundingBox`. * Add `ScaledGlyph::build_outline` & `PositionedGlyph::build_outline` methods. ## 0.9.0 * Major rework to use crates **ttf-parser** & **ab_glyph_rasterizer** to respectively read and render OpenType .oft format fonts. * Remove dependencies **approx**, **stb_truetype** & **ordered-float** along with in-crate rasterization code. * Strip back some non-vital API functionality. - Remove support for `.standalone()` variants which are sparsely used. - Remove some functions that didn't immediately translate to ttf-parser. Please raise issues to re-add any you relied on via the new stack. ## 0.8.3 * Remove arrayvec dependency. * Add `Default` implementations for geometry structs. ## 0.8.2 * Update crossbeam-utils -> `0.7`. * Update libm -> `0.2.1`. ## 0.8.1 * Update arrayvec -> `0.5`. ## 0.8 * Support no-std usage by disabling the new default feature `std` and using new features `libm-math` and `has-atomics`. The gpu_cache module/feature requires std. ## 0.7.9 * Use semver trick to re-expect rusttype `0.8` with default-features on. ## 0.7.8 _yanked_ ## 0.7.7 * gpu_cache: Add `CacheBuilder::align_4x4` method which forces texture updates to align to 4x4 pixel boxes. * gpu_cache: Disable multithread code and remove dependencies on wasm32. ## 0.7.6 * `GlyphIter` and `LayoutIter` provide the lifetime of the font data. ## 0.7.5 * gpu_cache: `Cache::cache_queued` now returns `CachedBy` for successes which can allow callers to tell that the texture cache has been re-ordered. ## 0.7.4 * Add fn `PositionedGlyph::set_position` * gpu_cache: Update crossbeam-deque -> `0.7`, use `Injector` for minor rasterization performance boost. ## 0.7.3 * gpu_cache: Update crossbeam-utils -> `0.6`. ## 0.7.2 * Update ordered-float -> `1`. ## 0.7.1 * Fix `PositionedGlyph::pixel_bounding_box()` size inconsistencies at different positions with identical sub-pixel positions. ## 0.7 * Rework `CacheBuilder` to use methods to allow non-breaking additions in future. New style is `Cache::builder().dimensions(512, 512).build()`. * Support multithreaded rasterization in the gpu_cache. This yields significant improvements in worst case performance when more than 1 CPU core is available. _Thrashing, resizing & population benchmarks are ~3x faster on a 4-core Haswell._ Multithreading is enabled by default in environments with more than a single core, but can be explicitly disabled using `Cache::builder().multithread(false)`. * Remove all deprecated API. * Add `Debug` implementations for `Font`, `Glyph`, `ScaledGlyph` & `PositionedGlyph` * Add and improve documentation + examples. ## 0.6.5 * Re-export rusttype `0.7` non-breaking main API, while keeping the current version of the gpu_cache module. ## 0.6.4 * Add `CacheBuilder::rebuild` & `Cache::to_builder` methods. * gpu_cache: Only rasterize & upload after queue has successfully fit in cache producing a 1.16-1.29x speedup in resizing & thrashing benchmarks. ## 0.6.3 * Documentation clarifications * Avoid depending on unused dependency default-features ## 0.6.2 * Add `From<&AsRef<[u8]>> for SharedBytes`. * Optimise `gpu_cache` hashing to improve benchmark performance by ~30%. ## 0.6.1 * Optimise rasterizer removing internal hashing. Improves draw benchmark performance by 11-91%. ## 0.6 * Rework gpu_cache data structures allowing constant time hash lookup of matching cached glyph textures. Improve performance by ~60-200%. * Deprecate `gpu_cache::Cache::new` in favour of `gpu_cache::CacheBuilder`. * Deprecate `gpu_cache::Cache::set_scale_tolerance` & `gpu_cache::Cache::set_position_tolerance`. These are now equivalent to recreating the cache as they invalidate the cache keys. * gpu_cache `scale_tolerance` & `position_tolerance` now have subtly different meanings but guarantee their error in all cases, where previously the worst case was double the set tolerance. ## 0.5.2 * Add gpu cache glyph padding option to fix texture bleeding from other glyphs when using interpolated texture coordinates near edges. Use `CacheBuilder` to construct a `Cache` that makes use of padding. * Inlining performance improvements. ## 0.5.1 * Fix tree removal on row clear (gpu_cache). ## 0.5 * Let functions like `Font::glyph` and `Font::pair_kerning` work with both characters and glyph ids by having them accept any type that implements the new `IntoGlyphId` trait. This replaces the `CodepointOrGlyph` enum, which didn't seem widely used. * Make `Font::glyph` always return a `Glyph`, not `Option`. Passing a `char` the font doesn't cover returns a `.notdef` glyph (id 0), as it did before. Passing an invalid glyph id now panics, like a bad array index: glyph ids should only be used to index the font they were looked up for. * Introduce `rusttype::Error`, which implements `std::error::Error`, `Debug` and `Display`, and can be converted to `std::io::Error`. * Use `Result<_, rusttype::Error>` to report failures in FontCollection, Font and associated iterators. * Add `Font::from_bytes` method similar to `FontCollection::from_bytes` for 1 font collections. * Improve gpu_cache performance ~2-6% ## 0.4.3 * Improve gpu_cache performance ~6-17% ## 0.4.2 * Allow users to get font names from `Font`. (#86) ## 0.4 * Add more debugging features * Add support for unscaled fonts * Improve performance * Make gpu_cache optional ## 0.3 * Transfer to redox-os organization, merge a number of pull requests ## 0.2.1 * Made the API more convenient (courtesy of @mitchmindtree, @I1048576). * Fixes for the examples (@I1048576) * Removed the dependency on ndarray (@I1048576) ## 0.2 * Initial GPU caching implementation. * Made font data management more flexible. * Made the interface for font scales simpler. ## 0.1.2 Fixed issue #8 ## 0.1.1 Fixed issue #7 ## 0.1 Initial release rusttype-0.9.3/Cargo.toml0000644000000044740000000000100107530ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "rusttype" version = "0.9.3" authors = [ "Dylan Ede ", "Jeremy Soller ", "Alex Butler ", ] exclude = ["/dev/**"] description = """ A pure Rust alternative to libraries like FreeType. RustType provides an API for loading, querying and rasterising TrueType fonts. It also provides an implementation of a dynamic GPU glyph cache for hardware font rendering. """ homepage = "https://gitlab.redox-os.org/redox-os/rusttype" documentation = "https://docs.rs/rusttype" readme = "README.md" keywords = [ "font", "truetype", "opentype", "ttf", "otf", ] license = "MIT / Apache-2.0" repository = "https://gitlab.redox-os.org/redox-os/rusttype" [package.metadata.docs.rs] features = ["gpu_cache"] [dependencies.ab_glyph_rasterizer] version = "0.1.5" default-features = false [dependencies.libm] version = "0.2.1" optional = true default-features = false [dependencies.linked-hash-map] version = "0.5" optional = true [dependencies.owned_ttf_parser] version = "0.15" default-features = false [dependencies.rustc-hash] version = "1" optional = true [dev-dependencies.approx] version = "0.5" default-features = false [features] default = ["std"] gpu_cache = [ "std", "linked-hash-map", "rustc-hash", "crossbeam-deque", "crossbeam-utils", "num_cpus", ] has-atomics = [] libm-math = [ "libm", "ab_glyph_rasterizer/libm", ] std = [ "has-atomics", "owned_ttf_parser/default", "ab_glyph_rasterizer/default", ] [target."cfg(not(target_arch = \"wasm32\"))".dependencies.crossbeam-deque] version = "0.8" optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.crossbeam-utils] version = "0.8" optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.num_cpus] version = "1.13" optional = true rusttype-0.9.3/Cargo.toml.orig000064400000000000000000000037711046102023000144330ustar 00000000000000[package] name = "rusttype" version = "0.9.3" authors = [ "Dylan Ede ", "Jeremy Soller ", "Alex Butler ", ] edition = "2018" description = """ A pure Rust alternative to libraries like FreeType. RustType provides an API for loading, querying and rasterising TrueType fonts. It also provides an implementation of a dynamic GPU glyph cache for hardware font rendering. """ documentation = "https://docs.rs/rusttype" homepage = "https://gitlab.redox-os.org/redox-os/rusttype" repository = "https://gitlab.redox-os.org/redox-os/rusttype" readme = "README.md" license = "MIT / Apache-2.0" keywords = ["font", "truetype", "opentype", "ttf", "otf"] exclude = ["/dev/**"] [package.metadata.docs.rs] features = ["gpu_cache"] [dependencies] owned_ttf_parser = { version = "0.15", default-features = false } ab_glyph_rasterizer = { version = "0.1.5", default-features = false } libm = { version = "0.2.1", default-features = false, optional = true } linked-hash-map = { version = "0.5", optional = true } rustc-hash = { version = "1", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] crossbeam-deque = { version = "0.8", optional = true } crossbeam-utils = { version = "0.8", optional = true } num_cpus = { version = "1.13", optional = true } [dev-dependencies] # don't add any more, instead use ./dev approx = { version = "0.5", default-features = false } [features] default = ["std"] # Activates usage of std. std = ["has-atomics", "owned_ttf_parser/default", "ab_glyph_rasterizer/default"] # Uses libm when not using std. This needs to be active in that case. libm-math = ["libm", "ab_glyph_rasterizer/libm"] # Some targets don't have atomics, this activates usage of Arc instead of Rc. has-atomics = [] # Adds `gpu_cache` module gpu_cache = ["std", "linked-hash-map", "rustc-hash", "crossbeam-deque", "crossbeam-utils", "num_cpus"] [workspace] # Used for tests, examples etc that require extra dependencies members = ["dev"] rusttype-0.9.3/LICENSE-APACHE000064400000000000000000000261351046102023000134670ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. rusttype-0.9.3/LICENSE-MIT000064400000000000000000000020641046102023000131720ustar 00000000000000The MIT License (MIT) Copyright (c) 2016 Dylan Ede Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rusttype-0.9.3/README.md000064400000000000000000000056731046102023000130260ustar 00000000000000# RustType [![crates.io](https://img.shields.io/crates/v/rusttype.svg)](https://crates.io/crates/rusttype) [![docs.rs](https://docs.rs/rusttype/badge.svg)](https://docs.rs/rusttype) RustType is a pure Rust alternative to libraries like FreeType. The current capabilities of RustType: * Reading OpenType formatted fonts and font collections. This includes `*.ttf` as well as `*.otf` font files. * Retrieving glyph shapes and commonly used properties for a font and its glyphs. * Laying out glyphs horizontally using horizontal and vertical metrics, and glyph-pair-specific kerning. * Rasterising glyphs with sub-pixel positioning using an accurate analytical algorithm (not based on sampling). * Managing a font cache on the GPU with the `gpu_cache` module. This keeps recently used glyph renderings in a dynamic cache in GPU memory to minimise texture uploads per-frame. It also allows you keep the draw call count for text very low, as all glyphs are kept in one GPU texture. Notable things that RustType does not support *yet*: * Font hinting. * Ligatures of any kind. * Some less common TrueType sub-formats. * Right-to-left and vertical text layout. ## Testing & examples Heavier examples, tests & benchmarks are in the `./dev` directory. This avoids dev-dependency feature bleed. Run all tests with `cargo test --all --all-features`. Run examples with `cargo run --example -p dev` ## Getting Started To hit the ground running with RustType, look at `dev/examples/ascii.rs` supplied with the crate. It demonstrates loading a font file, rasterising an arbitrary string, and displaying the result as ASCII art. If you prefer to just look at the documentation, the entry point for loading fonts is `Font`, from which you can access individual fonts, then their glyphs. ## Future Plans The initial motivation for the project was to provide easy-to-use font rendering for games. There are numerous avenues for improving RustType. Ideas: * Support for some common forms of ligatures. * And, eventually, support for embedded right-to-left Unicode text. If you think you could help with achieving any of these goals, feel free to open a tracking issue for discussing them. ## Minimum supported rust compiler This crate is maintained with [latest stable rust](https://gist.github.com/alexheretic/d1e98d8433b602e57f5d0a9637927e0c). ## License Licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. ### See Also - [glyph_brush](https://github.com/alexheretic/glyph-brush) - can cache vertex generation & provides more complex layouts. rusttype-0.9.3/rustfmt.toml000064400000000000000000000002161046102023000141340ustar 00000000000000# rustfmt 0.3.x-nightly wrap_comments = true error_on_line_overflow = false use_field_init_shorthand = true condense_wildcard_suffixes = true rusttype-0.9.3/src/font.rs000064400000000000000000000211321046102023000136360ustar 00000000000000use crate::{Glyph, GlyphIter, IntoGlyphId, LayoutIter, Point, Scale, VMetrics}; #[cfg(not(feature = "has-atomics"))] use alloc::rc::Rc as Arc; #[cfg(feature = "has-atomics")] use alloc::sync::Arc; #[cfg(not(feature = "std"))] use alloc::vec::Vec; use core::fmt; /// A single font. This may or may not own the font data. /// /// # Lifetime /// The lifetime reflects the font data lifetime. `Font<'static>` covers most /// cases ie both dynamically loaded owned data and for referenced compile time /// font data. /// /// # Example /// /// ``` /// # use rusttype::Font; /// # fn example() -> Option<()> { /// let font_data: &[u8] = include_bytes!("../dev/fonts/dejavu/DejaVuSansMono.ttf"); /// let font: Font<'static> = Font::try_from_bytes(font_data)?; /// /// let owned_font_data: Vec = font_data.to_vec(); /// let from_owned_font: Font<'static> = Font::try_from_vec(owned_font_data)?; /// # Some(()) /// # } /// ``` #[derive(Clone)] pub enum Font<'a> { Ref(Arc>), Owned(Arc), } impl fmt::Debug for Font<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Font") } } impl Font<'_> { /// Creates a Font from byte-slice data. /// /// Returns `None` for invalid data. pub fn try_from_bytes(bytes: &[u8]) -> Option> { Self::try_from_bytes_and_index(bytes, 0) } /// Creates a Font from byte-slice data & a font collection `index`. /// /// Returns `None` for invalid data. pub fn try_from_bytes_and_index(bytes: &[u8], index: u32) -> Option> { let inner = Arc::new(owned_ttf_parser::Face::from_slice(bytes, index).ok()?); Some(Font::Ref(inner)) } /// Creates a Font from owned font data. /// /// Returns `None` for invalid data. pub fn try_from_vec(data: Vec) -> Option> { Self::try_from_vec_and_index(data, 0) } /// Creates a Font from owned font data & a font collection `index`. /// /// Returns `None` for invalid data. pub fn try_from_vec_and_index(data: Vec, index: u32) -> Option> { let inner = Arc::new(owned_ttf_parser::OwnedFace::from_vec(data, index).ok()?); Some(Font::Owned(inner)) } } impl<'font> Font<'font> { #[inline] pub(crate) fn inner(&self) -> &owned_ttf_parser::Face<'_> { use owned_ttf_parser::AsFaceRef; match self { Self::Ref(f) => f, Self::Owned(f) => f.as_face_ref(), } } /// The "vertical metrics" for this font at a given scale. These metrics are /// shared by all of the glyphs in the font. See `VMetrics` for more detail. pub fn v_metrics(&self, scale: Scale) -> VMetrics { self.v_metrics_unscaled() * self.scale_for_pixel_height(scale.y) } /// Get the unscaled VMetrics for this font, shared by all glyphs. /// See `VMetrics` for more detail. pub fn v_metrics_unscaled(&self) -> VMetrics { let font = self.inner(); VMetrics { ascent: font.ascender() as f32, descent: font.descender() as f32, line_gap: font.line_gap() as f32, } } /// Returns the units per EM square of this font pub fn units_per_em(&self) -> u16 { self.inner().units_per_em() } /// The number of glyphs present in this font. Glyph identifiers for this /// font will always be in the range `0..self.glyph_count()` pub fn glyph_count(&self) -> usize { self.inner().number_of_glyphs() as _ } /// Returns the corresponding glyph for a Unicode code point or a glyph id /// for this font. /// /// If `id` is a `GlyphId`, it must be valid for this font; otherwise, this /// function panics. `GlyphId`s should always be produced by looking up some /// other sort of designator (like a Unicode code point) in a font, and /// should only be used to index the font they were produced for. /// /// Note that code points without corresponding glyphs in this font map to /// the ".notdef" glyph, glyph 0. pub fn glyph(&self, id: C) -> Glyph<'font> { let gid = id.into_glyph_id(self); assert!((gid.0 as usize) < self.glyph_count()); // font clone either a reference clone, or arc clone Glyph { font: self.clone(), id: gid, } } /// A convenience function. /// /// Returns an iterator that produces the glyphs corresponding to the code /// points or glyph ids produced by the given iterator `itr`. /// /// This is equivalent in behaviour to `itr.map(|c| font.glyph(c))`. pub fn glyphs_for<'a, I: Iterator>(&'a self, itr: I) -> GlyphIter<'a, 'font, I> where I::Item: IntoGlyphId, { GlyphIter { font: self, itr } } /// A convenience function for laying out glyphs for a string horizontally. /// It does not take control characters like line breaks into account, as /// treatment of these is likely to depend on the application. /// /// Note that this function does not perform Unicode normalisation. /// Composite characters (such as ö constructed from two code points, ¨ and /// o), will not be normalised to single code points. So if a font does not /// contain a glyph for each separate code point, but does contain one for /// the normalised single code point (which is common), the desired glyph /// will not be produced, despite being present in the font. Deal with this /// by performing Unicode normalisation on the input string before passing /// it to `layout`. The crate /// [unicode-normalization](http://crates.io/crates/unicode-normalization) /// is perfect for this purpose. /// /// Calling this function is equivalent to a longer sequence of operations /// involving `glyphs_for`, e.g. /// /// ```no_run /// # use rusttype::*; /// # let (scale, start) = (Scale::uniform(0.0), point(0.0, 0.0)); /// # let font: Font = unimplemented!(); /// font.layout("Hello World!", scale, start) /// # ; /// ``` /// /// produces an iterator with behaviour equivalent to the following: /// /// ```no_run /// # use rusttype::*; /// # let (scale, start) = (Scale::uniform(0.0), point(0.0, 0.0)); /// # let font: Font = unimplemented!(); /// font.glyphs_for("Hello World!".chars()) /// .scan((None, 0.0), |(last, x), g| { /// let g = g.scaled(scale); /// if let Some(last) = last { /// *x += font.pair_kerning(scale, *last, g.id()); /// } /// let w = g.h_metrics().advance_width; /// let next = g.positioned(start + vector(*x, 0.0)); /// *last = Some(next.id()); /// *x += w; /// Some(next) /// }) /// # ; /// ``` pub fn layout<'a, 's>( &'a self, s: &'s str, scale: Scale, start: Point, ) -> LayoutIter<'a, 'font, 's> { LayoutIter { font: self, chars: s.chars(), caret: 0.0, scale, start, last_glyph: None, } } /// Returns additional kerning to apply as well as that given by HMetrics /// for a particular pair of glyphs. pub fn pair_kerning(&self, scale: Scale, first: A, second: B) -> f32 where A: IntoGlyphId, B: IntoGlyphId, { let first_id = first.into_glyph_id(self).into(); let second_id = second.into_glyph_id(self).into(); let factor = { let hscale = self.scale_for_pixel_height(scale.y); hscale * (scale.x / scale.y) }; let kern = if let Some(kern) = self.inner().tables().kern { kern.subtables .into_iter() .filter(|st| st.horizontal && !st.variable) .find_map(|st| st.glyphs_kerning(first_id, second_id)) .unwrap_or(0) } else { 0 }; factor * f32::from(kern) } /// Computes a scale factor to produce a font whose "height" is 'pixels' /// tall. Height is measured as the distance from the highest ascender /// to the lowest descender; in other words, it's equivalent to calling /// GetFontVMetrics and computing: /// scale = pixels / (ascent - descent) /// so if you prefer to measure height by the ascent only, use a similar /// calculation. pub fn scale_for_pixel_height(&self, height: f32) -> f32 { let inner = self.inner(); let fheight = f32::from(inner.ascender()) - f32::from(inner.descender()); height / fheight } } rusttype-0.9.3/src/geometry.rs000064400000000000000000000102701046102023000145240ustar 00000000000000use core::ops; /// A point in 2-dimensional space, with each dimension of type `N`. /// /// Legal operations on points are addition and subtraction by vectors, and /// subtraction between points, to give a vector representing the offset between /// the two points. Combined with the legal operations on vectors, meaningful /// manipulations of vectors and points can be performed. /// /// For example, to interpolate between two points by a factor `t`: /// /// ``` /// # use rusttype::*; /// # let t = 0.5; let p0 = point(0.0, 0.0); let p1 = point(0.0, 0.0); /// let interpolated_point = p0 + (p1 - p0) * t; /// ``` #[derive(Copy, Clone, Debug, Default, PartialOrd, Ord, PartialEq, Eq, Hash)] pub struct Point { pub x: N, pub y: N, } /// A vector in 2-dimensional space, with each dimension of type `N`. /// /// Legal operations on vectors are addition and subtraction by vectors, /// addition by points (to give points), and multiplication and division by /// scalars. #[derive(Copy, Clone, Debug, Default, PartialOrd, Ord, PartialEq, Eq, Hash)] pub struct Vector { pub x: N, pub y: N, } /// A convenience function for generating `Point`s. #[inline] pub fn point(x: N, y: N) -> Point { Point { x, y } } /// A convenience function for generating `Vector`s. #[inline] pub fn vector(x: N, y: N) -> Vector { Vector { x, y } } impl> ops::Sub for Point { type Output = Vector; fn sub(self, rhs: Point) -> Vector { vector(self.x - rhs.x, self.y - rhs.y) } } impl> ops::Add for Vector { type Output = Vector; fn add(self, rhs: Vector) -> Vector { vector(self.x + rhs.x, self.y + rhs.y) } } impl> ops::Sub for Vector { type Output = Vector; fn sub(self, rhs: Vector) -> Vector { vector(self.x - rhs.x, self.y - rhs.y) } } impl ops::Mul for Vector { type Output = Vector; fn mul(self, rhs: f32) -> Vector { vector(self.x * rhs, self.y * rhs) } } impl ops::Mul> for f32 { type Output = Vector; fn mul(self, rhs: Vector) -> Vector { vector(self * rhs.x, self * rhs.y) } } impl ops::Mul for Vector { type Output = Vector; fn mul(self, rhs: f64) -> Vector { vector(self.x * rhs, self.y * rhs) } } impl ops::Mul> for f64 { type Output = Vector; fn mul(self, rhs: Vector) -> Vector { vector(self * rhs.x, self * rhs.y) } } impl ops::Div for Vector { type Output = Vector; fn div(self, rhs: f32) -> Vector { vector(self.x / rhs, self.y / rhs) } } impl ops::Div> for f32 { type Output = Vector; fn div(self, rhs: Vector) -> Vector { vector(self / rhs.x, self / rhs.y) } } impl ops::Div for Vector { type Output = Vector; fn div(self, rhs: f64) -> Vector { vector(self.x / rhs, self.y / rhs) } } impl ops::Div> for f64 { type Output = Vector; fn div(self, rhs: Vector) -> Vector { vector(self / rhs.x, self / rhs.y) } } impl> ops::Add> for Point { type Output = Point; fn add(self, rhs: Vector) -> Point { point(self.x + rhs.x, self.y + rhs.y) } } impl> ops::Sub> for Point { type Output = Point; fn sub(self, rhs: Vector) -> Point { point(self.x - rhs.x, self.y - rhs.y) } } impl> ops::Add> for Vector { type Output = Point; fn add(self, rhs: Point) -> Point { point(self.x + rhs.x, self.y + rhs.y) } } /// A rectangle, with top-left corner at `min`, and bottom-right corner at /// `max`. #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Rect { pub min: Point, pub max: Point, } impl + Copy> Rect { pub fn width(&self) -> N { self.max.x - self.min.x } pub fn height(&self) -> N { self.max.y - self.min.y } } rusttype-0.9.3/src/gpu_cache.rs000064400000000000000000001322451046102023000146160ustar 00000000000000//! This module provides capabilities for managing a cache of rendered glyphs in //! GPU memory, with the goal of minimisng the size and frequency of glyph //! uploads to GPU memory from the CPU. //! //! This module is optional, and not compiled by default. To use it enable the //! `gpu_cache` feature in your Cargo.toml. //! //! Typical applications that render directly with hardware graphics APIs (e.g. //! games) need text rendering. There is not yet a performant solution for high //! quality text rendering directly on the GPU that isn't experimental research //! work. Quality is often critical for legibility, so many applications use //! text or individual characters that have been rendered on the CPU. This is //! done either ahead-of-time, giving a fixed set of fonts, characters, and //! sizes that can be used at runtime, or dynamically as text is required. This //! latter scenario is more flexible and the focus of this module. //! //! To minimise the CPU load and texture upload bandwidth saturation, recently //! used glyphs should be cached on the GPU for use by future frames. This //! module provides a mechanism for maintaining such a cache in the form of a //! single packed 2D GPU texture. When a rendered glyph is requested, it is //! either retrieved from its location in the texture if it is present or room //! is made in the cache (if necessary), the CPU renders the glyph then it is //! uploaded into a gap in the texture to be available for GPU rendering. This //! cache uses a Least Recently Used (LRU) cache eviction scheme - glyphs in the //! cache that have not been used recently are as a rule of thumb not likely to //! be used again soon, so they are the best candidates for eviction to make //! room for required glyphs. //! //! The API for the cache does not assume a particular graphics API. The //! intended usage is to queue up glyphs that need to be present for the current //! frame using `Cache::queue_glyph`, update the cache to ensure that the queued //! glyphs are present using `Cache::cache_queued` (providing a function for //! uploading pixel data), then when it's time to render call `Cache::rect_for` //! to get the UV coordinates in the cache texture for each glyph. For a //! concrete use case see the `gpu_cache` example. //! //! Cache dimensions are immutable. If you need to change the dimensions of the //! cache texture (e.g. due to high cache pressure), rebuild a new `Cache`. //! Either from scratch or with `CacheBuilder::rebuild`. //! //! # Example //! //! ``` //! # use rusttype::{Font, gpu_cache::Cache, point, Scale}; //! # use std::error::Error; //! # fn example() -> Result<(), Box> { //! # let font_data: &[u8] = include_bytes!("../dev/fonts/dejavu/DejaVuSansMono.ttf"); //! # let font: Font<'static> = Font::try_from_bytes(font_data).unwrap(); //! # let glyph = font.glyph('a').scaled(Scale::uniform(25.0)).positioned(point(0.0, 0.0)); //! # let glyph2 = glyph.clone(); //! # fn update_gpu_texture(_: rusttype::Rect, _: &[u8]) {}; //! // Build a default Cache. //! let mut cache = Cache::builder().build(); //! //! // Queue some positioned glyphs needed for the next frame. //! cache.queue_glyph(0, glyph); //! //! // Cache all queued glyphs somewhere in the cache texture. //! // If new glyph data has been drawn the closure is called to upload //! // the pixel data to GPU memory. //! cache.cache_queued(|region, data| update_gpu_texture(region, data))?; //! //! # let glyph = glyph2; //! // Lookup a positioned glyph's texture location //! if let Ok(Some((uv_rect, screen_rect))) = cache.rect_for(0, &glyph) { //! // Generate vertex data, etc //! } //! # Ok(()) //! # } //! ``` use crate::{point, vector, GlyphId, Point, PositionedGlyph, Rect, Vector}; use linked_hash_map::LinkedHashMap; use rustc_hash::{FxHashMap, FxHasher}; use std::collections::{HashMap, HashSet}; use std::error; use std::fmt; use std::hash::BuildHasherDefault; type FxBuildHasher = BuildHasherDefault; /// Texture coordinates (floating point) of the quad for a glyph in the cache, /// as well as the pixel-space (integer) coordinates that this region should be /// drawn at. pub type TextureCoords = (Rect, Rect); type FontId = usize; /// Indicates where a glyph texture is stored in the cache /// (row position, glyph index in row) type TextureRowGlyphIndex = (u32, u32); /// Texture lookup key that uses scale & offset as integers attained /// by dividing by the relevant tolerance. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] struct LossyGlyphInfo { font_id: FontId, glyph_id: GlyphId, /// x & y scales divided by `scale_tolerance` & rounded scale_over_tolerance: (u32, u32), /// Normalised subpixel positions divided by `position_tolerance` & rounded /// /// `u16` is enough as subpixel position `[-0.5, 0.5]` converted to `[0, 1]` /// divided by the min `position_tolerance` (`0.001`) is small. offset_over_tolerance: (u16, u16), } #[derive(Debug, Clone, PartialEq, Eq)] struct ByteArray2d { inner_array: Vec, row: usize, col: usize, } impl ByteArray2d { #[inline] pub fn zeros(row: usize, col: usize) -> Self { ByteArray2d { inner_array: vec![0; row * col], row, col, } } #[inline] fn as_slice(&self) -> &[u8] { self.inner_array.as_slice() } #[inline] fn get_vec_index(&self, row: usize, col: usize) -> usize { debug_assert!( row < self.row, "row out of range: row={}, given={}", self.row, row ); debug_assert!( col < self.col, "column out of range: col={}, given={}", self.col, col ); row * self.col + col } } impl std::ops::Index<(usize, usize)> for ByteArray2d { type Output = u8; #[inline] fn index(&self, (row, col): (usize, usize)) -> &u8 { &self.inner_array[self.get_vec_index(row, col)] } } impl std::ops::IndexMut<(usize, usize)> for ByteArray2d { #[inline] fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut u8 { let vec_index = self.get_vec_index(row, col); &mut self.inner_array[vec_index] } } /// Row of pixel data struct Row { /// Row pixel height height: u32, /// Pixel width current in use by glyphs width: u32, glyphs: Vec, } struct GlyphTexInfo { glyph_info: LossyGlyphInfo, /// Actual (lossless) normalised subpixel offset of rasterized glyph offset: Vector, tex_coords: Rect, } trait PaddingAware { fn unpadded(self) -> Self; } impl PaddingAware for Rect { /// A padded texture has 1 extra pixel on all sides fn unpadded(mut self) -> Self { self.min.x += 1; self.min.y += 1; self.max.x -= 1; self.max.y -= 1; self } } /// An implementation of a dynamic GPU glyph cache. See the module documentation /// for more information. pub struct Cache<'font> { scale_tolerance: f32, position_tolerance: f32, width: u32, height: u32, rows: LinkedHashMap, /// Mapping of row gaps bottom -> top space_start_for_end: FxHashMap, /// Mapping of row gaps top -> bottom space_end_for_start: FxHashMap, queue: Vec<(FontId, PositionedGlyph<'font>)>, all_glyphs: FxHashMap, pad_glyphs: bool, align_4x4: bool, multithread: bool, } /// Builder & rebuilder for `Cache`. /// /// # Example /// /// ``` /// use rusttype::gpu_cache::Cache; /// /// // Create a cache with all default values set explicitly /// // equivalent to `Cache::builder().build()` /// let default_cache = Cache::builder() /// .dimensions(256, 256) /// .scale_tolerance(0.1) /// .position_tolerance(0.1) /// .pad_glyphs(true) /// .align_4x4(false) /// .multithread(true) /// .build(); /// /// // Create a cache with all default values, except with a dimension of 1024x1024 /// let bigger_cache = Cache::builder().dimensions(1024, 1024).build(); /// ``` #[derive(Debug, Clone)] pub struct CacheBuilder { dimensions: (u32, u32), scale_tolerance: f32, position_tolerance: f32, pad_glyphs: bool, align_4x4: bool, multithread: bool, } impl Default for CacheBuilder { fn default() -> Self { Self { dimensions: (256, 256), scale_tolerance: 0.1, position_tolerance: 0.1, pad_glyphs: true, align_4x4: false, multithread: true, } } } impl CacheBuilder { /// `width` & `height` dimensions of the 2D texture that will hold the /// cache contents on the GPU. /// /// This must match the dimensions of the actual texture used, otherwise /// `cache_queued` will try to cache into coordinates outside the bounds of /// the texture. /// /// # Example (set to default value) /// /// ``` /// # use rusttype::gpu_cache::Cache; /// let cache = Cache::builder().dimensions(256, 256).build(); /// ``` pub fn dimensions(mut self, width: u32, height: u32) -> Self { self.dimensions = (width, height); self } /// Specifies the tolerances (maximum allowed difference) for judging /// whether an existing glyph in the cache is close enough to the /// requested glyph in scale to be used in its place. Due to floating /// point inaccuracies a min value of `0.001` is enforced. /// /// Both `scale_tolerance` and `position_tolerance` are measured in pixels. /// /// Tolerances produce even steps for scale and subpixel position. Only a /// single glyph texture will be used within a single step. For example, /// `scale_tolerance = 0.1` will have a step `9.95-10.05` so similar glyphs /// with scale `9.98` & `10.04` will match. /// /// A typical application will produce results with no perceptible /// inaccuracies with `scale_tolerance` and `position_tolerance` set to /// 0.1. Depending on the target DPI higher tolerance may be acceptable. /// /// # Example (set to default value) /// /// ``` /// # use rusttype::gpu_cache::Cache; /// let cache = Cache::builder().scale_tolerance(0.1).build(); /// ``` pub fn scale_tolerance>(mut self, scale_tolerance: V) -> Self { self.scale_tolerance = scale_tolerance.into(); self } /// Specifies the tolerances (maximum allowed difference) for judging /// whether an existing glyph in the cache is close enough to the requested /// glyph in subpixel offset to be used in its place. Due to floating /// point inaccuracies a min value of `0.001` is enforced. /// /// Both `scale_tolerance` and `position_tolerance` are measured in pixels. /// /// Tolerances produce even steps for scale and subpixel position. Only a /// single glyph texture will be used within a single step. For example, /// `scale_tolerance = 0.1` will have a step `9.95-10.05` so similar glyphs /// with scale `9.98` & `10.04` will match. /// /// Note that since `position_tolerance` is a tolerance of subpixel /// offsets, setting it to 1.0 or higher is effectively a "don't care" /// option. /// /// A typical application will produce results with no perceptible /// inaccuracies with `scale_tolerance` and `position_tolerance` set to /// 0.1. Depending on the target DPI higher tolerance may be acceptable. /// /// # Example (set to default value) /// /// ``` /// # use rusttype::gpu_cache::Cache; /// let cache = Cache::builder().position_tolerance(0.1).build(); /// ``` pub fn position_tolerance>(mut self, position_tolerance: V) -> Self { self.position_tolerance = position_tolerance.into(); self } /// Pack glyphs in texture with a padding of a single zero alpha pixel to /// avoid bleeding from interpolated shader texture lookups near edges. /// /// If glyphs are never transformed this may be set to `false` to slightly /// improve the glyph packing. /// /// # Example (set to default value) /// /// ``` /// # use rusttype::gpu_cache::Cache; /// let cache = Cache::builder().pad_glyphs(true).build(); /// ``` pub fn pad_glyphs(mut self, pad_glyphs: bool) -> Self { self.pad_glyphs = pad_glyphs; self } /// Align glyphs in texture to 4x4 texel boundaries. /// /// If your backend requires texture updates to be aligned to 4x4 texel /// boundaries (e.g. WebGL), this should be set to `true`. /// /// # Example (set to default value) /// /// ``` /// # use rusttype::gpu_cache::Cache; /// let cache = Cache::builder().align_4x4(false).build(); /// ``` pub fn align_4x4(mut self, align_4x4: bool) -> Self { self.align_4x4 = align_4x4; self } /// When multiple CPU cores are available spread rasterization work across /// all cores. /// /// Significantly reduces worst case latency in multicore environments. /// /// # Platform-specific behaviour /// /// This option has no effect on wasm32. /// /// # Example (set to default value) /// /// ``` /// # use rusttype::gpu_cache::Cache; /// let cache = Cache::builder().multithread(true).build(); /// ``` pub fn multithread(mut self, multithread: bool) -> Self { self.multithread = multithread; self } fn validated(self) -> Self { assert!(self.scale_tolerance >= 0.0); assert!(self.position_tolerance >= 0.0); let scale_tolerance = self.scale_tolerance.max(0.001); let position_tolerance = self.position_tolerance.max(0.001); #[cfg(not(target_arch = "wasm32"))] let multithread = self.multithread && num_cpus::get() > 1; Self { scale_tolerance, position_tolerance, #[cfg(not(target_arch = "wasm32"))] multithread, ..self } } /// Constructs a new cache. Note that this is just the CPU side of the /// cache. The GPU texture is managed by the user. /// /// # Panics /// /// `scale_tolerance` or `position_tolerance` are less than or equal to /// zero. /// /// # Example /// /// ``` /// # use rusttype::gpu_cache::Cache; /// let cache = Cache::builder().build(); /// ``` pub fn build<'a>(self) -> Cache<'a> { let CacheBuilder { dimensions: (width, height), scale_tolerance, position_tolerance, pad_glyphs, align_4x4, multithread, } = self.validated(); Cache { scale_tolerance, position_tolerance, width, height, rows: LinkedHashMap::default(), space_start_for_end: { let mut m = HashMap::default(); m.insert(height, 0); m }, space_end_for_start: { let mut m = HashMap::default(); m.insert(0, height); m }, queue: Vec::new(), all_glyphs: HashMap::default(), pad_glyphs, align_4x4, multithread, } } /// Rebuilds a `Cache` with new attributes. All cached glyphs are cleared, /// however the glyph queue is retained unmodified. /// /// # Panics /// /// `scale_tolerance` or `position_tolerance` are less than or equal to /// zero. /// /// # Example /// /// ``` /// # use rusttype::gpu_cache::Cache; /// # let mut cache = Cache::builder().build(); /// // Rebuild the cache with different dimensions /// cache.to_builder().dimensions(768, 768).rebuild(&mut cache); /// ``` pub fn rebuild(self, cache: &mut Cache) { let CacheBuilder { dimensions: (width, height), scale_tolerance, position_tolerance, pad_glyphs, align_4x4, multithread, } = self.validated(); cache.width = width; cache.height = height; cache.scale_tolerance = scale_tolerance; cache.position_tolerance = position_tolerance; cache.pad_glyphs = pad_glyphs; cache.align_4x4 = align_4x4; cache.multithread = multithread; cache.clear(); } } /// Returned from `Cache::rect_for`. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum CacheReadErr { /// Indicates that the requested glyph is not present in the cache GlyphNotCached, } impl fmt::Display for CacheReadErr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { CacheReadErr::GlyphNotCached => "Glyph not cached", } .fmt(f) } } impl error::Error for CacheReadErr {} /// Returned from `Cache::cache_queued`. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum CacheWriteErr { /// At least one of the queued glyphs is too big to fit into the cache, even /// if all other glyphs are removed. GlyphTooLarge, /// Not all of the requested glyphs can fit into the cache, even if the /// cache is completely cleared before the attempt. NoRoomForWholeQueue, } impl fmt::Display for CacheWriteErr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { CacheWriteErr::GlyphTooLarge => "Glyph too large", CacheWriteErr::NoRoomForWholeQueue => "No room for whole queue", } .fmt(f) } } impl error::Error for CacheWriteErr {} /// Successful method of caching of the queue. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum CachedBy { /// Added any additional glyphs into the texture without affecting /// the position of any already cached glyphs in the latest queue. /// /// Glyphs not in the latest queue may have been removed. Adding, /// Fit the glyph queue by re-ordering all glyph texture positions. /// Previous texture positions are no longer valid. Reordering, } fn normalised_offset_from_position(position: Point) -> Vector { let mut offset = vector(position.x.fract(), position.y.fract()); if offset.x > 0.5 { offset.x -= 1.0; } else if offset.x < -0.5 { offset.x += 1.0; } if offset.y > 0.5 { offset.y -= 1.0; } else if offset.y < -0.5 { offset.y += 1.0; } offset } impl<'font> Cache<'font> { /// Returns a default `CacheBuilder`. #[inline] pub fn builder() -> CacheBuilder { CacheBuilder::default() } /// Returns the current scale tolerance for the cache. pub fn scale_tolerance(&self) -> f32 { self.scale_tolerance } /// Returns the current subpixel position tolerance for the cache. pub fn position_tolerance(&self) -> f32 { self.position_tolerance } /// Returns the cache texture dimensions assumed by the cache. For proper /// operation this should match the dimensions of the used GPU texture. pub fn dimensions(&self) -> (u32, u32) { (self.width, self.height) } /// Queue a glyph for caching by the next call to `cache_queued`. `font_id` /// is used to disambiguate glyphs from different fonts. The user should /// ensure that `font_id` is unique to the font the glyph is from. pub fn queue_glyph(&mut self, font_id: usize, glyph: PositionedGlyph<'font>) { if glyph.pixel_bounding_box().is_some() { self.queue.push((font_id, glyph)); } } /// Clears the cache. Does not affect the glyph queue. pub fn clear(&mut self) { self.rows.clear(); self.space_end_for_start.clear(); self.space_end_for_start.insert(0, self.height); self.space_start_for_end.clear(); self.space_start_for_end.insert(self.height, 0); self.all_glyphs.clear(); } /// Clears the glyph queue. pub fn clear_queue(&mut self) { self.queue.clear(); } /// Returns a `CacheBuilder` with this cache's attributes. pub fn to_builder(&self) -> CacheBuilder { CacheBuilder { dimensions: (self.width, self.height), position_tolerance: self.position_tolerance, scale_tolerance: self.scale_tolerance, pad_glyphs: self.pad_glyphs, align_4x4: self.align_4x4, multithread: self.multithread, } } /// Returns glyph info with accuracy according to the set tolerances. fn lossy_info_for(&self, font_id: FontId, glyph: &PositionedGlyph<'font>) -> LossyGlyphInfo { let scale = glyph.scale(); let offset = normalised_offset_from_position(glyph.position()); LossyGlyphInfo { font_id, glyph_id: glyph.id(), scale_over_tolerance: ( (scale.x / self.scale_tolerance + 0.5) as u32, (scale.y / self.scale_tolerance + 0.5) as u32, ), // convert [-0.5, 0.5] -> [0, 1] then divide offset_over_tolerance: ( ((offset.x + 0.5) / self.position_tolerance + 0.5) as u16, ((offset.y + 0.5) / self.position_tolerance + 0.5) as u16, ), } } /// Caches the queued glyphs. If this is unsuccessful, the queue is /// untouched. Any glyphs cached by previous calls to this function may be /// removed from the cache to make room for the newly queued glyphs. Thus if /// you want to ensure that a glyph is in the cache, the most recently /// cached queue must have contained that glyph. /// /// `uploader` is the user-provided function that should perform the texture /// uploads to the GPU. The information provided is the rectangular region /// to insert the pixel data into, and the pixel data itself. This data is /// provided in horizontal scanline format (row major), with stride equal to /// the rectangle width. /// /// If successful returns a `CachedBy` that can indicate the validity of /// previously cached glyph textures. pub fn cache_queued, &[u8])>( &mut self, mut uploader: F, ) -> Result { let mut queue_success = true; let from_empty = self.all_glyphs.is_empty(); { let (mut in_use_rows, mut uncached_glyphs) = { let mut in_use_rows = HashSet::with_capacity_and_hasher(self.rows.len(), FxBuildHasher::default()); let mut uncached_glyphs = Vec::with_capacity(self.queue.len()); // divide glyphs into texture rows where a matching glyph texture // already exists & glyphs where new textures must be cached for (font_id, ref glyph) in &self.queue { let glyph_info = self.lossy_info_for(*font_id, glyph); if let Some((row, ..)) = self.all_glyphs.get(&glyph_info) { in_use_rows.insert(*row); } else { uncached_glyphs.push((glyph, glyph_info)); } } (in_use_rows, uncached_glyphs) }; for row in &in_use_rows { self.rows.get_refresh(row); } // tallest first gives better packing // can use 'sort_unstable' as order of equal elements is unimportant uncached_glyphs .sort_unstable_by_key(|(glyph, ..)| -glyph.pixel_bounding_box().unwrap().height()); self.all_glyphs.reserve(uncached_glyphs.len()); let mut draw_and_upload = Vec::with_capacity(uncached_glyphs.len()); 'per_glyph: for (glyph, glyph_info) in uncached_glyphs { // glyph may match a texture cached by a previous iteration if self.all_glyphs.contains_key(&glyph_info) { continue; } // Not cached, so add it: let (unaligned_width, unaligned_height) = { let bb = glyph.pixel_bounding_box().unwrap(); if self.pad_glyphs { (bb.width() as u32 + 2, bb.height() as u32 + 2) } else { (bb.width() as u32, bb.height() as u32) } }; let (aligned_width, aligned_height) = if self.align_4x4 { // align to the next 4x4 texel boundary ((unaligned_width + 3) & !3, (unaligned_height + 3) & !3) } else { (unaligned_width, unaligned_height) }; if aligned_width >= self.width || aligned_height >= self.height { return Result::Err(CacheWriteErr::GlyphTooLarge); } // find row to put the glyph in, most used rows first let mut row_top = None; for (top, row) in self.rows.iter().rev() { if row.height >= aligned_height && self.width - row.width >= aligned_width { // found a spot on an existing row row_top = Some(*top); break; } } if row_top.is_none() { let mut gap = None; // See if there is space for a new row for (start, end) in &self.space_end_for_start { if end - start >= aligned_height { gap = Some((*start, *end)); break; } } if gap.is_none() { // Remove old rows until room is available while !self.rows.is_empty() { // check that the oldest row isn't also in use if !in_use_rows.contains(self.rows.front().unwrap().0) { // Remove row let (top, row) = self.rows.pop_front().unwrap(); for g in row.glyphs { self.all_glyphs.remove(&g.glyph_info); } let (mut new_start, mut new_end) = (top, top + row.height); // Update the free space maps // Combine with neighbouring free space if possible if let Some(end) = self.space_end_for_start.remove(&new_end) { new_end = end; } if let Some(start) = self.space_start_for_end.remove(&new_start) { new_start = start; } self.space_start_for_end.insert(new_end, new_start); self.space_end_for_start.insert(new_start, new_end); if new_end - new_start >= aligned_height { // The newly formed gap is big enough gap = Some((new_start, new_end)); break; } } // all rows left are in use // try a clean insert of all needed glyphs // if that doesn't work, fail else if from_empty { // already trying a clean insert, don't do it again return Err(CacheWriteErr::NoRoomForWholeQueue); } else { // signal that a retry is needed queue_success = false; break 'per_glyph; } } } let (gap_start, gap_end) = gap.unwrap(); // fill space for new row let new_space_start = gap_start + aligned_height; self.space_end_for_start.remove(&gap_start); if new_space_start == gap_end { self.space_start_for_end.remove(&gap_end); } else { self.space_end_for_start.insert(new_space_start, gap_end); self.space_start_for_end.insert(gap_end, new_space_start); } // add the row self.rows.insert( gap_start, Row { width: 0, height: aligned_height, glyphs: Vec::new(), }, ); row_top = Some(gap_start); } let row_top = row_top.unwrap(); // calculate the target rect let row = self.rows.get_refresh(&row_top).unwrap(); let aligned_tex_coords = Rect { min: point(row.width, row_top), max: point(row.width + aligned_width, row_top + aligned_height), }; let unaligned_tex_coords = Rect { min: point(row.width, row_top), max: point(row.width + unaligned_width, row_top + unaligned_height), }; draw_and_upload.push((aligned_tex_coords, glyph)); // add the glyph to the row row.glyphs.push(GlyphTexInfo { glyph_info, offset: normalised_offset_from_position(glyph.position()), tex_coords: unaligned_tex_coords, }); row.width += aligned_width; in_use_rows.insert(row_top); self.all_glyphs .insert(glyph_info, (row_top, row.glyphs.len() as u32 - 1)); } if queue_success { #[cfg(not(target_arch = "wasm32"))] { let glyph_count = draw_and_upload.len(); if self.multithread && glyph_count > 1 { // multithread rasterization use crossbeam_deque::Steal; use std::{ mem, sync::mpsc::{self, TryRecvError}, }; let rasterize_queue = crossbeam_deque::Injector::new(); let (to_main, from_stealers) = mpsc::channel(); let pad_glyphs = self.pad_glyphs; for el in draw_and_upload { rasterize_queue.push(el); } crossbeam_utils::thread::scope(|scope| { for _ in 0..num_cpus::get().min(glyph_count).saturating_sub(1) { let rasterize_queue = &rasterize_queue; let to_main = to_main.clone(); scope.spawn(move |_| loop { match rasterize_queue.steal() { Steal::Success((tex_coords, glyph)) => { let pixels = draw_glyph(tex_coords, glyph, pad_glyphs); to_main.send((tex_coords, pixels)).unwrap(); } Steal::Empty => break, Steal::Retry => {} } }); } mem::drop(to_main); let mut workers_finished = false; loop { match rasterize_queue.steal() { Steal::Success((tex_coords, glyph)) => { let pixels = draw_glyph(tex_coords, glyph, pad_glyphs); uploader(tex_coords, pixels.as_slice()); } Steal::Empty if workers_finished => break, Steal::Empty | Steal::Retry => {} } while !workers_finished { match from_stealers.try_recv() { Ok((tex_coords, pixels)) => { uploader(tex_coords, pixels.as_slice()) } Err(TryRecvError::Disconnected) => workers_finished = true, Err(TryRecvError::Empty) => break, } } } }) .unwrap(); } else { // single thread rasterization for (tex_coords, glyph) in draw_and_upload { let pixels = draw_glyph(tex_coords, glyph, self.pad_glyphs); uploader(tex_coords, pixels.as_slice()); } } } #[cfg(target_arch = "wasm32")] { for (tex_coords, glyph) in draw_and_upload { let pixels = draw_glyph(tex_coords, glyph, self.pad_glyphs); uploader(tex_coords, pixels.as_slice()); } } } } if queue_success { self.queue.clear(); Ok(CachedBy::Adding) } else { // clear the cache then try again with optimal packing self.clear(); self.cache_queued(uploader).map(|_| CachedBy::Reordering) } } /// Retrieves the (floating point) texture coordinates of the quad for a /// glyph in the cache, as well as the pixel-space (integer) coordinates /// that this region should be drawn at. These pixel-space coordinates /// assume an origin at the top left of the quad. In the majority of cases /// these pixel-space coordinates should be identical to the bounding box of /// the input glyph. They only differ if the cache has returned a substitute /// glyph that is deemed close enough to the requested glyph as specified by /// the cache tolerance parameters. /// /// A sucessful result is `Some` if the glyph is not an empty glyph (no /// shape, and thus no rect to return). /// /// Ensure that `font_id` matches the `font_id` that was passed to /// `queue_glyph` with this `glyph`. pub fn rect_for( &self, font_id: usize, glyph: &PositionedGlyph, ) -> Result, CacheReadErr> { if glyph.pixel_bounding_box().is_none() { return Ok(None); } let (row, index) = self .all_glyphs .get(&self.lossy_info_for(font_id, glyph)) .ok_or(CacheReadErr::GlyphNotCached)?; let (tex_width, tex_height) = (self.width as f32, self.height as f32); let GlyphTexInfo { tex_coords: mut tex_rect, offset: tex_offset, .. } = self.rows[&row].glyphs[*index as usize]; if self.pad_glyphs { tex_rect = tex_rect.unpadded(); } let uv_rect = Rect { min: point( tex_rect.min.x as f32 / tex_width, tex_rect.min.y as f32 / tex_height, ), max: point( tex_rect.max.x as f32 / tex_width, tex_rect.max.y as f32 / tex_height, ), }; let local_bb = glyph .unpositioned() .clone() .positioned(point(0.0, 0.0) + tex_offset) .pixel_bounding_box() .unwrap(); let min_from_origin = point(local_bb.min.x as f32, local_bb.min.y as f32) - (point(0.0, 0.0) + tex_offset); let ideal_min = min_from_origin + glyph.position(); let min = point(ideal_min.x.round() as i32, ideal_min.y.round() as i32); let bb_offset = min - local_bb.min; let bb = Rect { min, max: local_bb.max + bb_offset, }; Ok(Some((uv_rect, bb))) } } #[inline] fn draw_glyph(tex_coords: Rect, glyph: &PositionedGlyph<'_>, pad_glyphs: bool) -> ByteArray2d { let mut pixels = ByteArray2d::zeros(tex_coords.height() as usize, tex_coords.width() as usize); if pad_glyphs { glyph.draw(|x, y, v| { let v = (v * 255.0).round() as u8; // `+ 1` accounts for top/left glyph padding pixels[(y as usize + 1, x as usize + 1)] = v; }); } else { glyph.draw(|x, y, v| { let v = (v * 255.0).round() as u8; pixels[(y as usize, x as usize)] = v; }); } pixels } #[cfg(test)] mod test { use super::*; use crate::{Font, Scale}; use approx::*; #[test] fn cache_test() { let font_data = include_bytes!("../dev/fonts/wqy-microhei/WenQuanYiMicroHei.ttf"); let font = Font::try_from_bytes(font_data as &[u8]).unwrap(); let mut cache: Cache<'static> = Cache::builder() .dimensions(32, 32) .scale_tolerance(0.1) .position_tolerance(0.1) .pad_glyphs(false) .build(); let strings = [ ("Hello World!", 15.0), ("Hello World!", 14.0), ("Hello World!", 10.0), ("Hello World!", 15.0), ("Hello World!", 14.0), ("Hello World!", 10.0), ]; for &(string, scale) in &strings { println!("Caching {:?}", (string, scale)); for glyph in font.layout(string, Scale::uniform(scale), point(0.0, 0.0)) { cache.queue_glyph(0, glyph); } cache.cache_queued(|_, _| {}).unwrap(); } } #[test] fn need_to_check_whole_cache() { let font_data = include_bytes!("../dev/fonts/wqy-microhei/WenQuanYiMicroHei.ttf"); let font = Font::try_from_bytes(font_data as &[u8]).unwrap(); let glyph = font.glyph('l'); let small = glyph.clone().scaled(Scale::uniform(10.0)); let large = glyph.clone().scaled(Scale::uniform(10.05)); let small_left = small.clone().positioned(point(0.0, 0.0)); let large_left = large.clone().positioned(point(0.0, 0.0)); let large_right = large.clone().positioned(point(-0.2, 0.0)); let mut cache = Cache::builder() .dimensions(32, 32) .scale_tolerance(0.1) .position_tolerance(0.1) .pad_glyphs(false) .build(); cache.queue_glyph(0, small_left.clone()); // Next line is noop since it's within the scale tolerance of small_left: cache.queue_glyph(0, large_left.clone()); cache.queue_glyph(0, large_right.clone()); cache.cache_queued(|_, _| {}).unwrap(); cache.rect_for(0, &small_left).unwrap(); cache.rect_for(0, &large_left).unwrap(); cache.rect_for(0, &large_right).unwrap(); } #[test] fn lossy_info() { let font_data = include_bytes!("../dev/fonts/wqy-microhei/WenQuanYiMicroHei.ttf"); let font = Font::try_from_bytes(font_data as &[u8]).unwrap(); let glyph = font.glyph('l'); let small = glyph.clone().scaled(Scale::uniform(9.91)); let near = glyph.clone().scaled(Scale::uniform(10.09)); let far = glyph.clone().scaled(Scale::uniform(10.11)); let really_far = glyph.clone().scaled(Scale::uniform(12.0)); let small_pos = small.clone().positioned(point(0.0, 0.0)); let match_1 = near.clone().positioned(point(-10.0, -0.1)); let match_2 = near.clone().positioned(point(5.1, 0.24)); let match_3 = small.clone().positioned(point(-100.2, 50.1)); let miss_1 = far.clone().positioned(point(0.0, 0.0)); let miss_2 = really_far.clone().positioned(point(0.0, 0.0)); let miss_3 = small.clone().positioned(point(0.3, 0.0)); let cache = Cache::builder() .scale_tolerance(0.2) .position_tolerance(0.5) .build(); let small_info = cache.lossy_info_for(0, &small_pos); assert_eq!(small_info, cache.lossy_info_for(0, &match_1)); assert_eq!(small_info, cache.lossy_info_for(0, &match_2)); assert_eq!(small_info, cache.lossy_info_for(0, &match_3)); assert_ne!(small_info, cache.lossy_info_for(0, &miss_1)); assert_ne!(small_info, cache.lossy_info_for(0, &miss_2)); assert_ne!(small_info, cache.lossy_info_for(0, &miss_3)); } #[test] fn cache_to_builder() { let cache = CacheBuilder { dimensions: (32, 64), scale_tolerance: 0.2, position_tolerance: 0.3, pad_glyphs: false, align_4x4: false, multithread: false, } .build(); let to_builder: CacheBuilder = cache.to_builder(); assert_eq!(to_builder.dimensions, (32, 64)); assert_relative_eq!(to_builder.scale_tolerance, 0.2); assert_relative_eq!(to_builder.position_tolerance, 0.3); assert_eq!(to_builder.pad_glyphs, false); assert_eq!(to_builder.align_4x4, false); assert_eq!(to_builder.multithread, false); } #[test] fn builder_rebuild() { let mut cache = Cache::builder() .dimensions(32, 64) .scale_tolerance(0.2) .position_tolerance(0.3) .pad_glyphs(false) .align_4x4(true) .multithread(true) .build(); let font = Font::try_from_bytes(include_bytes!( "../dev/fonts/wqy-microhei/WenQuanYiMicroHei.ttf" ) as &[u8]) .unwrap(); cache.queue_glyph( 0, font.glyph('l') .scaled(Scale::uniform(25.0)) .positioned(point(0.0, 0.0)), ); cache.cache_queued(|_, _| {}).unwrap(); cache.queue_glyph( 0, font.glyph('a') .scaled(Scale::uniform(25.0)) .positioned(point(0.0, 0.0)), ); Cache::builder() .dimensions(64, 128) .scale_tolerance(0.05) .position_tolerance(0.15) .pad_glyphs(true) .align_4x4(false) .multithread(false) .rebuild(&mut cache); assert_eq!(cache.width, 64); assert_eq!(cache.height, 128); assert_relative_eq!(cache.scale_tolerance, 0.05); assert_relative_eq!(cache.position_tolerance, 0.15); assert_eq!(cache.pad_glyphs, true); assert_eq!(cache.align_4x4, false); assert_eq!(cache.multithread, false); assert!( cache.all_glyphs.is_empty(), "cache should have been cleared" ); assert_eq!(cache.queue.len(), 1, "cache should have an unchanged queue"); } /// Provide to caller that the cache was re-ordered to fit the latest queue #[test] fn return_cache_by_reordering() { let font_data = include_bytes!("../dev/fonts/wqy-microhei/WenQuanYiMicroHei.ttf"); let font = Font::try_from_bytes(font_data as &[u8]).unwrap(); let mut cache = Cache::builder() .dimensions(36, 27) .scale_tolerance(0.1) .position_tolerance(0.1) .build(); for glyph in font.layout("ABCDEFG", Scale::uniform(16.0), point(0.0, 0.0)) { cache.queue_glyph(0, glyph); } assert_eq!(cache.cache_queued(|_, _| {}), Ok(CachedBy::Adding)); for glyph in font.layout("DEFGHIJK", Scale::uniform(16.0), point(0.0, 0.0)) { cache.queue_glyph(0, glyph); } assert_eq!(cache.cache_queued(|_, _| {}), Ok(CachedBy::Reordering)); } #[test] fn align_4x4() { // First, test align_4x4 disabled, to confirm non-4x4 alignment align_4x4_helper(false, 5, 19); // Now, test with align_4x4 enabled, to confirm 4x4 alignment align_4x4_helper(true, 8, 20); } fn align_4x4_helper(align_4x4: bool, expected_width: u32, expected_height: u32) { let mut cache = Cache::builder() .dimensions(64, 64) .align_4x4(align_4x4) .build(); let font = Font::try_from_bytes(include_bytes!( "../dev/fonts/wqy-microhei/WenQuanYiMicroHei.ttf" ) as &[u8]) .unwrap(); let glyph = font .glyph('l') .scaled(Scale::uniform(25.0)) .positioned(point(0.0, 0.0)); cache.queue_glyph(0, glyph.clone()); cache .cache_queued(|rect, _| { assert_eq!(rect.width(), expected_width); assert_eq!(rect.height(), expected_height); }) .unwrap(); let (uv_rect, _screen_rect) = cache.rect_for(0, &glyph).unwrap().unwrap(); assert_eq!( uv_rect, crate::Rect { min: crate::point(0.015_625, 0.015_625), max: crate::point(0.0625, 0.28125), } ); } } rusttype-0.9.3/src/lib.rs000064400000000000000000000471641046102023000134530ustar 00000000000000//! RustType is a pure Rust alternative to libraries like FreeType. //! //! The current capabilities of RustType: //! //! * Reading TrueType formatted fonts and font collections. This includes //! `*.ttf` as well as a subset of `*.otf` font files. //! * Retrieving glyph shapes and commonly used properties for a font and its //! glyphs. //! * Laying out glyphs horizontally using horizontal and vertical metrics, and //! glyph-pair-specific kerning. //! * Rasterising glyphs with sub-pixel positioning using an accurate analytical //! algorithm (not based on sampling). //! * Managing a font cache on the GPU with the `gpu_cache` module. This keeps //! recently used glyph renderings in a dynamic cache in GPU memory to //! minimise texture uploads per-frame. It also allows you keep the draw call //! count for text very low, as all glyphs are kept in one GPU texture. //! //! Notable things that RustType does not support *yet*: //! //! * OpenType formatted fonts that are not just TrueType fonts (OpenType is a //! superset of TrueType). Notably there is no support yet for cubic Bezier //! curves used in glyphs. //! * Font hinting. //! * Ligatures of any kind. //! * Some less common TrueType sub-formats. //! * Right-to-left and vertical text layout. //! //! # Getting Started //! //! To hit the ground running with RustType, look at the `ascii.rs` example //! supplied with the crate. It demonstrates loading a font file, rasterising an //! arbitrary string, and displaying the result as ASCII art. If you prefer to //! just look at the documentation, the entry point for loading fonts is //! `Font`, from which you can access individual fonts, then their //! glyphs. //! //! # Glyphs //! //! The glyph API uses wrapper structs to augment a glyph with information such //! as scaling and positioning, making relevant methods that make use of this //! information available as appropriate. For example, given a `Glyph` `glyph` //! obtained directly from a `Font`: //! //! ```no_run //! # use rusttype::*; //! # let glyph: Glyph<'static> = unimplemented!(); //! // One of the few things you can do with an unsized, positionless glyph is get its id. //! let id = glyph.id(); //! let glyph = glyph.scaled(Scale::uniform(10.0)); //! // Now glyph is a ScaledGlyph, you can do more with it, as well as what you can do with Glyph. //! // For example, you can access the correctly scaled horizontal metrics for the glyph. //! let h_metrics = glyph.h_metrics(); //! let glyph = glyph.positioned(point(5.0, 3.0)); //! // Now glyph is a PositionedGlyph, and you can do even more with it, e.g. drawing. //! glyph.draw(|x, y, v| {}); // In this case the pixel values are not used. //! ``` //! //! # Unicode terminology //! //! This crate uses terminology for computerised typography as specified by the //! Unicode standard. If you are not sure of the differences between a code //! point, a character, and a glyph, you may want to check the [official Unicode //! glossary](http://unicode.org/glossary/), or alternatively, here's my take on //! it from a practical perspective: //! //! * A character is what you would conventionally call a single symbol, //! independent of its appearance or representation in a particular font. //! Examples include `a`, `A`, `ä`, `å`, `1`, `*`, `Ω`, etc. //! * A Unicode code point is the particular number that the Unicode standard //! associates with a particular character. Note however that code points also //! exist for things not conventionally thought of as characters by //! themselves, but can be combined to form characters, such as diacritics //! like accents. These "characters" are known in Unicode as "combining //! characters". E.g., a diaeresis (`¨`) has the code point U+0308. If this //! code point follows the code point U+0055 (the letter `u`), this sequence //! represents the character `ü`. Note that there is also a single codepoint //! for `ü`, U+00FC. This means that what visually looks like the same string //! can have multiple different Unicode representations. Some fonts will have //! glyphs (see below) for one sequence of codepoints, but not another that //! has the same meaning. To deal with this problem it is recommended to use //! Unicode normalisation, as provided by, for example, the //! [unicode-normalization](http://crates.io/crates/unicode-normalization) //! crate, to convert to code point sequences that work with the font in //! question. Typically a font is more likely to support a single code point //! vs. a sequence with the same meaning, so the best normalisation to use is //! "canonical recomposition", known as NFC in the normalisation crate. //! * A glyph is a particular font's shape to draw the character for a //! particular Unicode code point. This will have its own identifying number //! unique to the font, its ID. #![allow( clippy::cognitive_complexity, clippy::doc_markdown, clippy::cast_lossless, clippy::many_single_char_names )] #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; mod font; mod geometry; mod outliner; #[cfg(all(feature = "libm-math", not(feature = "std")))] mod nostd_float; #[cfg(feature = "gpu_cache")] pub mod gpu_cache; pub use crate::geometry::{point, vector, Point, Rect, Vector}; pub use font::*; use core::fmt; #[cfg(all(feature = "libm-math", not(feature = "std")))] use crate::nostd_float::FloatExt; pub use owned_ttf_parser::OutlineBuilder; #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] pub struct GlyphId(pub u16); impl From for GlyphId { fn from(id: owned_ttf_parser::GlyphId) -> Self { Self(id.0) } } impl From for owned_ttf_parser::GlyphId { fn from(id: GlyphId) -> Self { Self(id.0) } } /// A single glyph of a font. /// /// A `Glyph` does not have an inherent scale or position associated with it. To /// augment a glyph with a size, give it a scale using `scaled`. You can then /// position it using `positioned`. #[derive(Clone)] pub struct Glyph<'font> { font: Font<'font>, id: GlyphId, } impl<'font> Glyph<'font> { /// The font to which this glyph belongs. pub fn font(&self) -> &Font<'font> { &self.font } /// The glyph identifier for this glyph. pub fn id(&self) -> GlyphId { self.id } /// Augments this glyph with scaling information, making methods that depend /// on the scale of the glyph available. pub fn scaled(self, scale: Scale) -> ScaledGlyph<'font> { let scale_y = self.font.scale_for_pixel_height(scale.y); let scale_x = scale_y * scale.x / scale.y; ScaledGlyph { g: self, api_scale: scale, scale: vector(scale_x, scale_y), } } } impl fmt::Debug for Glyph<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Glyph").field("id", &self.id().0).finish() } } /// The "horizontal metrics" of a glyph. This is useful for calculating the /// horizontal offset of a glyph from the previous one in a string when laying a /// string out horizontally. #[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] pub struct HMetrics { /// The horizontal offset that the origin of the next glyph should be from /// the origin of this glyph. pub advance_width: f32, /// The horizontal offset between the origin of this glyph and the leftmost /// edge/point of the glyph. pub left_side_bearing: f32, } /// The "vertical metrics" of a font at a particular scale. This is useful for /// calculating the amount of vertical space to give a line of text, and for /// computing the vertical offset between successive lines. #[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] pub struct VMetrics { /// The highest point that any glyph in the font extends to above the /// baseline. Typically positive. pub ascent: f32, /// The lowest point that any glyph in the font extends to below the /// baseline. Typically negative. pub descent: f32, /// The gap to leave between the descent of one line and the ascent of the /// next. This is of course only a guideline given by the font's designers. pub line_gap: f32, } impl core::ops::Mul for VMetrics { type Output = VMetrics; fn mul(self, rhs: f32) -> Self { Self { ascent: self.ascent * rhs, descent: self.descent * rhs, line_gap: self.line_gap * rhs, } } } /// A glyph augmented with scaling information. You can query such a glyph for /// information that depends on the scale of the glyph. #[derive(Clone)] pub struct ScaledGlyph<'font> { g: Glyph<'font>, api_scale: Scale, scale: Vector, } impl<'font> ScaledGlyph<'font> { /// The glyph identifier for this glyph. pub fn id(&self) -> GlyphId { self.g.id() } /// The font to which this glyph belongs. #[inline] pub fn font(&self) -> &Font<'font> { self.g.font() } /// A reference to this glyph without the scaling pub fn into_unscaled(self) -> Glyph<'font> { self.g } /// Removes the scaling from this glyph pub fn unscaled(&self) -> &Glyph<'font> { &self.g } /// Builds the outline of the glyph with the builder specified. Returns /// `false` when the outline is either malformed or empty. pub fn build_outline(&self, builder: &mut impl OutlineBuilder) -> bool { let mut outliner = crate::outliner::OutlineScaler::new(builder, vector(self.scale.x, -self.scale.y)); self.font() .inner() .outline_glyph(self.id().into(), &mut outliner) .is_some() } /// Augments this glyph with positioning information, making methods that /// depend on the position of the glyph available. pub fn positioned(self, p: Point) -> PositionedGlyph<'font> { let bb = self.pixel_bounds_at(p); PositionedGlyph { sg: self, position: p, bb, } } pub fn scale(&self) -> Scale { self.api_scale } /// Retrieves the "horizontal metrics" of this glyph. See `HMetrics` for /// more detail. pub fn h_metrics(&self) -> HMetrics { let inner = self.font().inner(); let id = self.id().into(); let advance = inner.glyph_hor_advance(id).unwrap(); let left_side_bearing = inner.glyph_hor_side_bearing(id).unwrap(); HMetrics { advance_width: advance as f32 * self.scale.x, left_side_bearing: left_side_bearing as f32 * self.scale.x, } } /// The bounding box of the shape of this glyph, not to be confused with /// `pixel_bounding_box`, the conservative pixel-boundary bounding box. The /// coordinates are relative to the glyph's origin. pub fn exact_bounding_box(&self) -> Option> { let owned_ttf_parser::Rect { x_min, y_min, x_max, y_max, } = self.font().inner().glyph_bounding_box(self.id().into())?; Some(Rect { min: point(x_min as f32 * self.scale.x, -y_max as f32 * self.scale.y), max: point(x_max as f32 * self.scale.x, -y_min as f32 * self.scale.y), }) } fn glyph_bitmap_box_subpixel( &self, font: &Font<'font>, shift_x: f32, shift_y: f32, ) -> Option> { let owned_ttf_parser::Rect { x_min, y_min, x_max, y_max, } = font.inner().glyph_bounding_box(self.id().into())?; Some(Rect { min: point( (x_min as f32 * self.scale.x + shift_x).floor() as i32, (-y_max as f32 * self.scale.y + shift_y).floor() as i32, ), max: point( (x_max as f32 * self.scale.x + shift_x).ceil() as i32, (-y_min as f32 * self.scale.y + shift_y).ceil() as i32, ), }) } #[inline] fn pixel_bounds_at(&self, p: Point) -> Option> { // Use subpixel fraction in floor/ceil rounding to eliminate rounding error // from identical subpixel positions let (x_trunc, x_fract) = (p.x.trunc() as i32, p.x.fract()); let (y_trunc, y_fract) = (p.y.trunc() as i32, p.y.fract()); let Rect { min, max } = self.glyph_bitmap_box_subpixel(self.font(), x_fract, y_fract)?; Some(Rect { min: point(x_trunc + min.x, y_trunc + min.y), max: point(x_trunc + max.x, y_trunc + max.y), }) } } impl fmt::Debug for ScaledGlyph<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ScaledGlyph") .field("id", &self.id().0) .field("scale", &self.api_scale) .finish() } } /// A glyph augmented with positioning and scaling information. You can query /// such a glyph for information that depends on the scale and position of the /// glyph. #[derive(Clone)] pub struct PositionedGlyph<'font> { sg: ScaledGlyph<'font>, position: Point, bb: Option>, } impl<'font> PositionedGlyph<'font> { /// The glyph identifier for this glyph. pub fn id(&self) -> GlyphId { self.sg.id() } /// The font to which this glyph belongs. #[inline] pub fn font(&self) -> &Font<'font> { self.sg.font() } /// A reference to this glyph without positioning pub fn unpositioned(&self) -> &ScaledGlyph<'font> { &self.sg } /// Removes the positioning from this glyph pub fn into_unpositioned(self) -> ScaledGlyph<'font> { self.sg } /// The conservative pixel-boundary bounding box for this glyph. This is the /// smallest rectangle aligned to pixel boundaries that encloses the shape /// of this glyph at this position. Note that the origin of the glyph, at /// pixel-space coordinates (0, 0), is at the top left of the bounding box. pub fn pixel_bounding_box(&self) -> Option> { self.bb } pub fn scale(&self) -> Scale { self.sg.api_scale } pub fn position(&self) -> Point { self.position } /// Builds the outline of the glyph with the builder specified. Returns /// `false` when the outline is either malformed or empty. pub fn build_outline(&self, builder: &mut impl OutlineBuilder) -> bool { let bb = if let Some(bb) = self.bb.as_ref() { bb } else { return false; }; let offset = vector(bb.min.x as f32, bb.min.y as f32); let mut outliner = crate::outliner::OutlineTranslator::new(builder, self.position - offset); self.sg.build_outline(&mut outliner) } /// Rasterises this glyph. For each pixel in the rect given by /// `pixel_bounding_box()`, `o` is called: /// /// ```ignore /// o(x, y, v) /// ``` /// /// where `x` and `y` are the coordinates of the pixel relative to the `min` /// coordinates of the bounding box, and `v` is the analytically calculated /// coverage of the pixel by the shape of the glyph. Calls to `o` proceed in /// horizontal scanline order, similar to this pseudo-code: /// /// ```ignore /// let bb = glyph.pixel_bounding_box(); /// for y in 0..bb.height() { /// for x in 0..bb.width() { /// o(x, y, calc_coverage(&glyph, x, y)); /// } /// } /// ``` pub fn draw(&self, o: O) { let bb = if let Some(bb) = self.bb.as_ref() { bb } else { return; }; let width = (bb.max.x - bb.min.x) as u32; let height = (bb.max.y - bb.min.y) as u32; let mut outliner = crate::outliner::OutlineRasterizer::new(width as _, height as _); self.build_outline(&mut outliner); outliner.rasterizer.for_each_pixel_2d(o); } /// Resets positioning information and recalculates the pixel bounding box pub fn set_position(&mut self, p: Point) { let p_diff = p - self.position; if p_diff.x.fract().is_near_zero() && p_diff.y.fract().is_near_zero() { if let Some(bb) = self.bb.as_mut() { let rounded_diff = vector(p_diff.x.round() as i32, p_diff.y.round() as i32); bb.min = bb.min + rounded_diff; bb.max = bb.max + rounded_diff; } } else { self.bb = self.sg.pixel_bounds_at(p); } self.position = p; } } impl fmt::Debug for PositionedGlyph<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PositionedGlyph") .field("id", &self.id().0) .field("scale", &self.scale()) .field("position", &self.position) .finish() } } /// Defines the size of a rendered face of a font, in pixels, horizontally and /// vertically. A vertical scale of `y` pixels means that the distance between /// the ascent and descent lines (see `VMetrics`) of the face will be `y` /// pixels. If `x` and `y` are equal the scaling is uniform. Non-uniform scaling /// by a factor *f* in the horizontal direction is achieved by setting `x` equal /// to *f* times `y`. #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] pub struct Scale { /// Horizontal scale, in pixels. pub x: f32, /// Vertical scale, in pixels. pub y: f32, } impl Scale { /// Uniform scaling, equivalent to `Scale { x: s, y: s }`. #[inline] pub fn uniform(s: f32) -> Scale { Scale { x: s, y: s } } } /// A trait for types that can be converted into a `GlyphId`, in the context of /// a specific font. /// /// Many `rusttype` functions that operate on characters accept values of any /// type that implements `IntoGlyphId`. Such types include `char`, `Codepoint`, /// and obviously `GlyphId` itself. pub trait IntoGlyphId { /// Convert `self` into a `GlyphId`, consulting the index map of `font` if /// necessary. fn into_glyph_id(self, font: &Font<'_>) -> GlyphId; } impl IntoGlyphId for char { #[inline] fn into_glyph_id(self, font: &Font<'_>) -> GlyphId { font.inner() .glyph_index(self) .unwrap_or(owned_ttf_parser::GlyphId(0)) .into() } } impl> IntoGlyphId for G { #[inline] fn into_glyph_id(self, _font: &Font<'_>) -> GlyphId { self.into() } } #[derive(Clone)] pub struct GlyphIter<'a, 'font, I: Iterator> where I::Item: IntoGlyphId, { font: &'a Font<'font>, itr: I, } impl<'a, 'font, I> Iterator for GlyphIter<'a, 'font, I> where I: Iterator, I::Item: IntoGlyphId, { type Item = Glyph<'font>; fn next(&mut self) -> Option> { self.itr.next().map(|c| self.font.glyph(c)) } } #[derive(Clone)] pub struct LayoutIter<'a, 'font, 's> { font: &'a Font<'font>, chars: core::str::Chars<'s>, caret: f32, scale: Scale, start: Point, last_glyph: Option, } impl<'a, 'font, 's> Iterator for LayoutIter<'a, 'font, 's> { type Item = PositionedGlyph<'font>; fn next(&mut self) -> Option> { self.chars.next().map(|c| { let g = self.font.glyph(c).scaled(self.scale); if let Some(last) = self.last_glyph { self.caret += self.font.pair_kerning(self.scale, last, g.id()); } let g = g.positioned(point(self.start.x + self.caret, self.start.y)); self.caret += g.sg.h_metrics().advance_width; self.last_glyph = Some(g.id()); g }) } } pub(crate) trait NearZero { /// Returns if this number is kinda pretty much zero. fn is_near_zero(&self) -> bool; } impl NearZero for f32 { #[inline] fn is_near_zero(&self) -> bool { self.abs() <= core::f32::EPSILON } } rusttype-0.9.3/src/nostd_float.rs000064400000000000000000000012421046102023000152040ustar 00000000000000pub(crate) trait FloatExt { fn floor(self) -> Self; fn ceil(self) -> Self; fn fract(self) -> Self; fn trunc(self) -> Self; fn round(self) -> Self; fn abs(self) -> Self; } impl FloatExt for f32 { #[inline] fn floor(self) -> Self { libm::floorf(self) } #[inline] fn ceil(self) -> Self { libm::ceilf(self) } #[inline] fn fract(self) -> Self { self - self.trunc() } #[inline] fn trunc(self) -> Self { libm::truncf(self) } #[inline] fn round(self) -> Self { libm::roundf(self) } #[inline] fn abs(self) -> Self { libm::fabsf(self) } } rusttype-0.9.3/src/outliner.rs000064400000000000000000000076551046102023000145470ustar 00000000000000use crate::{Point, Vector}; use ab_glyph_rasterizer::{point as ab_point, Point as AbPoint, Rasterizer}; use owned_ttf_parser::OutlineBuilder; pub(crate) struct OutlineScaler<'b, T: ?Sized> { inner: &'b mut T, scale: Vector, } impl<'b, T: ?Sized> OutlineScaler<'b, T> { pub(crate) fn new(inner: &'b mut T, scale: Vector) -> Self { Self { inner, scale } } } impl OutlineBuilder for OutlineScaler<'_, T> { fn move_to(&mut self, x: f32, y: f32) { self.inner.move_to(x * self.scale.x, y * self.scale.y) } fn line_to(&mut self, x1: f32, y1: f32) { self.inner.line_to(x1 * self.scale.x, y1 * self.scale.y) } fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) { self.inner.quad_to( x1 * self.scale.x, y1 * self.scale.y, x2 * self.scale.x, y2 * self.scale.y, ) } fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) { self.inner.curve_to( x1 * self.scale.x, y1 * self.scale.y, x2 * self.scale.x, y2 * self.scale.y, x3 * self.scale.x, y3 * self.scale.y, ) } fn close(&mut self) { self.inner.close() } } pub(crate) struct OutlineTranslator<'b, T: ?Sized> { inner: &'b mut T, translation: Point, } impl<'b, T: ?Sized> OutlineTranslator<'b, T> { pub(crate) fn new(inner: &'b mut T, translation: Point) -> Self { Self { inner, translation } } } impl OutlineBuilder for OutlineTranslator<'_, T> { fn move_to(&mut self, x: f32, y: f32) { self.inner .move_to(x + self.translation.x, y + self.translation.y) } fn line_to(&mut self, x1: f32, y1: f32) { self.inner .line_to(x1 + self.translation.x, y1 + self.translation.y) } fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) { self.inner.quad_to( x1 + self.translation.x, y1 + self.translation.y, x2 + self.translation.x, y2 + self.translation.y, ) } fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) { self.inner.curve_to( x1 + self.translation.x, y1 + self.translation.y, x2 + self.translation.x, y2 + self.translation.y, x3 + self.translation.x, y3 + self.translation.y, ) } fn close(&mut self) { self.inner.close() } } pub(crate) struct OutlineRasterizer { pub(crate) rasterizer: Rasterizer, last: AbPoint, last_move: Option, } impl OutlineRasterizer { pub(crate) fn new(width: usize, height: usize) -> Self { Self { rasterizer: Rasterizer::new(width, height), last: ab_point(0.0, 0.0), last_move: None, } } } impl OutlineBuilder for OutlineRasterizer { fn move_to(&mut self, x: f32, y: f32) { self.last = AbPoint { x, y }; self.last_move = Some(self.last); } fn line_to(&mut self, x1: f32, y1: f32) { let p1 = AbPoint { x: x1, y: y1 }; self.rasterizer.draw_line(self.last, p1); self.last = p1; } fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) { let p1 = AbPoint { x: x1, y: y1 }; let p2 = AbPoint { x: x2, y: y2 }; self.rasterizer.draw_quad(self.last, p1, p2); self.last = p2; } fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) { let p1 = AbPoint { x: x1, y: y1 }; let p2 = AbPoint { x: x2, y: y2 }; let p3 = AbPoint { x: x3, y: y3 }; self.rasterizer.draw_cubic(self.last, p1, p2, p3); self.last = p3; } fn close(&mut self) { if let Some(m) = self.last_move { self.rasterizer.draw_line(self.last, m); } } }