├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── draw-cache ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches │ ├── draw_cache.rs │ ├── lipsum.txt │ ├── loads-of-unicode.txt │ └── st_vs_mt.rs └── src │ ├── geometry.rs │ └── lib.rs ├── fonts ├── DejaVuSans.ttf ├── DejaVuSansMono.ttf ├── Exo2-Light.otf ├── GaramondNo8-Reg.ttf ├── OpenSans-Italic.ttf ├── OpenSans-Light.ttf └── WenQuanYiMicroHei.ttf ├── gfx-glyph ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples │ ├── depth.rs │ ├── init.rs │ ├── lipsum.txt │ ├── loads-of-unicode.txt │ ├── paragraph.rs │ ├── performance.rs │ ├── pre_positioned.rs │ └── varied.rs └── src │ ├── builder.rs │ ├── draw_builder.rs │ ├── lib.rs │ ├── pipe.rs │ ├── shader │ ├── frag.glsl │ └── vert.glsl │ └── trace.rs ├── glyph-brush ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches │ ├── glyph_brush.rs │ ├── lipsum.txt │ ├── lots_of_lipsum.txt │ └── small_lipsum.txt ├── examples │ ├── draw_cache_guts.rs │ ├── opengl.rs │ ├── shader │ │ ├── img.fs │ │ ├── img.vs │ │ ├── text.fs │ │ └── text.vs │ └── text │ │ └── lipsum.txt └── src │ ├── extra.rs │ ├── glyph_brush.rs │ ├── glyph_brush │ └── builder.rs │ ├── glyph_calculator.rs │ ├── legacy.rs │ ├── lib.rs │ ├── section.rs │ └── section │ ├── builder.rs │ ├── owned.rs │ └── refed.rs ├── layout ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md └── src │ ├── builtin.rs │ ├── characters.rs │ ├── font.rs │ ├── lib.rs │ ├── linebreak.rs │ ├── lines.rs │ ├── section.rs │ └── words.rs └── test /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - run: rustup update stable 14 | - uses: actions/checkout@v4 15 | - run: cargo test 16 | - run: cargo test --benches 17 | 18 | ## TODO fix? 19 | # test_32bit: 20 | # runs-on: ubuntu-latest 21 | # steps: 22 | # - run: rustup update stable 23 | # - run: sudo apt update && sudo apt install -yq gcc-multilib musl-tools 24 | # - run: rustup target add i686-unknown-linux-musl 25 | # - uses: actions/checkout@v4 26 | # - run: cargo test --target i686-unknown-linux-musl 27 | 28 | check_wasm: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - run: rustup update stable 32 | - run: rustup target add wasm32-unknown-unknown 33 | - uses: actions/checkout@v4 34 | - run: cargo check --target wasm32-unknown-unknown 35 | 36 | rustfmt: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - run: rustup update stable 40 | - uses: actions/checkout@v4 41 | - run: cargo fmt -- --check 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["gfx-glyph", "glyph-brush", "layout", "draw-cache"] 3 | resolver = "2" 4 | 5 | [profile.bench] 6 | lto = "thin" 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # glyph-brush 2 | Fast cached text rendering. 3 | 4 | ## [glyph_brush](glyph-brush) [![](https://img.shields.io/crates/v/glyph_brush.svg)](https://crates.io/crates/glyph_brush) [![](https://docs.rs/glyph_brush/badge.svg)](https://docs.rs/glyph_brush) 5 | Render API agnostic rasterization & draw caching text rendering. See [readme](glyph-brush), [changelog](glyph-brush/CHANGELOG.md). 6 | 7 | ## [glyph_brush_layout](layout) [![](https://img.shields.io/crates/v/glyph_brush_layout.svg)](https://crates.io/crates/glyph_brush_layout) [![](https://docs.rs/glyph_brush_layout/badge.svg)](https://docs.rs/glyph_brush_layout) 8 | Text layout for [ab_glyph](https://github.com/alexheretic/ab-glyph) used in glyph_brush. See [readme](layout), [changelog](layout/CHANGELOG.md). 9 | 10 | ## [glyph_brush_draw_cache](draw-cache) [![](https://img.shields.io/crates/v/glyph_brush_draw_cache.svg)](https://crates.io/crates/glyph_brush_draw_cache) [![](https://docs.rs/glyph_brush_draw_cache/badge.svg)](https://docs.rs/glyph_brush_draw_cache) 11 | Rasterization cache for [ab_glyph](https://github.com/alexheretic/ab-glyph) used in glyph_brush. See [readme](draw-cache), [changelog](draw-cache/CHANGELOG.md). 12 | 13 | ## [gfx_glyph](gfx-glyph) [![](https://img.shields.io/crates/v/gfx_glyph.svg)](https://crates.io/crates/gfx_glyph) [![](https://docs.rs/gfx_glyph/badge.svg)](https://docs.rs/gfx_glyph) 14 | glyph_brush wrapper for [gfx-rs v0.18](https://github.com/gfx-rs/gfx/tree/pre-ll) (OpenGL). See [readme](gfx-glyph), [changelog](gfx-glyph/CHANGELOG.md). 15 | 16 | ## Examples 17 | `cargo run -p glyph_brush --example opengl --release` 18 | 19 | ![](https://i.ibb.co/rvS2vp9/glyph-brush-example.png) 20 | 21 | Also look at the individual crate readmes. 22 | 23 | ## Minimum supported rust compiler 24 | All crates maintained with [latest stable rust](https://gist.github.com/alexheretic/d1e98d8433b602e57f5d0a9637927e0c). 25 | -------------------------------------------------------------------------------- /draw-cache/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.6 2 | * Clarify `Rectangle` docs. 3 | * Update _rustc-hash_ to `2`. 4 | 5 | # 0.1.5 6 | * Micro-optimise avoid `.round()` during glyph drawing when converting pixel coverage to `u8`. 7 | 8 | # 0.1.4 9 | * Optimise frequent lower workload efficiency by only using multithreading code paths when a 10 | significant speedup can be expected. 11 | * Remove concurrent outlining as it didn't provide enough of a speedup after more thorough analysis. 12 | 13 | # 0.1.3 14 | * Update _crossbeam-deque_ to 0.8, _crossbeam-channel_ to 0.5. 15 | 16 | # 0.1.2 17 | * Optimise empty cache `cache_queued` calls by bundling texture data into a single upload. 18 | 19 | # 0.1.1 20 | * Require _ab_glyph_ 0.2.2. 21 | 22 | # 0.1 23 | * Port _rusttype_ gpu cache to _ab_glyph_. 24 | * Use exact texture rect position, adjusted for different sub-pixel matches. 25 | * Use rayon for concurrent outline calculation & rasterization. 26 | * Use crossbeam-channel for channelling. 27 | * Implement local batch work stealing for rasterization tasks improving population performance by **1.1x**. 28 | -------------------------------------------------------------------------------- /draw-cache/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "glyph_brush_draw_cache" 3 | version = "0.1.6" 4 | authors = ["Alex Butler "] 5 | edition = "2021" 6 | description = "Texture draw cache for ab_glyph" 7 | repository = "https://github.com/alexheretic/glyph-brush" 8 | keywords = ["font", "ttf", "truetype", "gfx", "text"] 9 | license = "Apache-2.0" 10 | readme = "README.md" 11 | 12 | [dependencies] 13 | ab_glyph = "0.2.2" 14 | linked-hash-map = "0.5.4" 15 | rustc-hash = "2" 16 | 17 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 18 | crossbeam-channel = "0.5" 19 | crossbeam-deque = "0.8" 20 | rayon = "1.3" 21 | 22 | [dev-dependencies] 23 | approx = "0.5" 24 | criterion = "0.6" 25 | glyph_brush_layout = { version = "0.2", path = "../layout" } 26 | 27 | [[bench]] 28 | name = "draw_cache" 29 | harness = false 30 | 31 | [[bench]] 32 | name = "st_vs_mt" 33 | harness = false 34 | -------------------------------------------------------------------------------- /draw-cache/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /draw-cache/README.md: -------------------------------------------------------------------------------- 1 | glyph_brush_draw_cache 2 | [![crates.io](https://img.shields.io/crates/v/glyph_brush_draw_cache.svg)](https://crates.io/crates/glyph_brush_draw_cache) 3 | [![Documentation](https://docs.rs/glyph_brush_draw_cache/badge.svg)](https://docs.rs/glyph_brush_draw_cache) 4 | ====================== 5 | Rasterization cache for [ab_glyph](https://github.com/alexheretic/ab-glyph) used in glyph_brush. 6 | 7 | * Manages a texture. Draws glyphs into it and provides texture rect lookup for glyphs. 8 | * Automatic re-use & reordering when needed. 9 | 10 | ```rust 11 | use glyph_brush_draw_cache::DrawCache; 12 | 13 | // build a cache with default settings 14 | let mut draw_cache = DrawCache::builder().build(); 15 | 16 | // queue up some glyphs to store in the cache 17 | for (font_id, glyph) in glyphs { 18 | draw_cache.queue_glyph(font_id, glyph); 19 | } 20 | 21 | // process everything in the queue, rasterizing & uploading as necessary 22 | draw_cache.cache_queued(&fonts, |rect, tex_data| update_texture(rect, tex_data))?; 23 | 24 | // access a given glyph's texture position & pixel position for the texture quad 25 | match draw_cache.rect_for(font_id, &glyph) { 26 | Some((tex_coords, px_coords)) => {} 27 | None => {/* The glyph has no outline, or wasn't queued up to be cached */} 28 | } 29 | ``` 30 | 31 | ## Example 32 | See the **draw_cache_guts** example to see how it works _(run it from the top level)_. 33 | 34 | ``` 35 | cargo run --example draw_cache_guts 36 | ``` 37 | 38 | ![](https://user-images.githubusercontent.com/2331607/82690363-f97a9380-9c53-11ea-97bc-6f3397cde00f.png) -------------------------------------------------------------------------------- /draw-cache/benches/lipsum.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, ferri simul omittantur eam eu, no debet doming dolorem ius. Iriure vocibus est te, natum delicata dignissim pri ea. Purto docendi definitiones no qui. Vel ridens instructior ad, vidisse percipitur et eos. Alienum ocurreret laboramus mei cu, usu ne meliore nostrum, usu tritani luptatum electram ad. 2 | 3 | Vis oratio tantas prodesset et, id stet inermis mea, at his copiosae accusata. Mel diam accusata argumentum cu, ut agam consul invidunt est. Ocurreret appellantur deterruisset no vis, his alia postulant inciderint no. Has albucius offendit at. An has noluisse comprehensam, vel veri dicit blandit ea, per paulo noluisse reformidans no. Nec ad sale illum soleat, agam scriptorem ad per. 4 | 5 | An cum odio mucius apeirian, labores conceptam ex nec, eruditi habemus qualisque eam an. Eu facilisi maluisset eos, fabulas apeirian ut qui, no atqui blandit vix. Apeirian phaedrum pri ex, vel hinc omnes sapientem et, vim vocibus legendos disputando ne. Et vel semper nominati rationibus, eum lorem causae scripta no. 6 | 7 | Ut quo elitr viderer constituam, pro omnesque forensibus at. Timeam scaevola mediocrem ut pri, te pro congue delicatissimi. Mei wisi nostro imperdiet ea, ridens salutatus per no, ut viris partem disputationi sit. Exerci eripuit referrentur vix at, sale mediocrem repudiare per te, modus admodum an eam. No vocent indoctum vis, ne quodsi patrioque vix. Vocent labores omittam et usu. 8 | 9 | Democritum signiferumque id nam, enim idque facilis at his. Inermis percipitur scriptorem sea cu, est ne error ludus option. Graecis expetenda contentiones cum et, ius nullam impetus suscipit ex. Modus clita corrumpit mel te, qui at lorem harum, primis cetero habemus sea id. Ei mutat affert dolorum duo, eum dissentias voluptatibus te, libris theophrastus duo id. 10 | -------------------------------------------------------------------------------- /draw-cache/benches/st_vs_mt.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, measurement::WallTime, Bencher, Criterion}; 2 | use glyph_brush_draw_cache::*; 3 | use glyph_brush_layout::ab_glyph::*; 4 | use std::sync::LazyLock; 5 | 6 | /// Simple paragraph layout for glyphs into `target`. 7 | /// 8 | /// This is for testing and examples. 9 | pub fn layout_paragraph( 10 | font: SF, 11 | position: Point, 12 | max_width: f32, 13 | text: &str, 14 | target: &mut Vec, 15 | ) where 16 | F: Font, 17 | SF: ScaleFont, 18 | { 19 | let v_advance = font.height() + font.line_gap(); 20 | let mut caret = position + point(0.0, font.ascent()); 21 | let mut last_glyph: Option = None; 22 | for c in text.chars() { 23 | if c.is_control() { 24 | if c == '\n' { 25 | caret = point(position.x, caret.y + v_advance); 26 | last_glyph = None; 27 | } 28 | continue; 29 | } 30 | let mut glyph = font.scaled_glyph(c); 31 | if let Some(previous) = last_glyph.take() { 32 | caret.x += font.kern(previous.id, glyph.id); 33 | } 34 | glyph.position = caret; 35 | 36 | last_glyph = Some(glyph.clone()); 37 | caret.x += font.h_advance(glyph.id); 38 | 39 | if !c.is_whitespace() && caret.x > position.x + max_width { 40 | caret = point(position.x, caret.y + v_advance); 41 | glyph.position = caret; 42 | last_glyph = None; 43 | } 44 | 45 | target.push(glyph); 46 | } 47 | } 48 | 49 | static DEJA_VU_SANS: LazyLock> = LazyLock::new(|| { 50 | FontRef::try_from_slice(include_bytes!("../../fonts/DejaVuSans.ttf") as &[u8]).unwrap() 51 | }); 52 | 53 | const LOADS_OF_UNICODE: &str = include_str!("loads-of-unicode.txt"); 54 | 55 | fn unicode_chars(len: usize) -> &'static str { 56 | let (index, _) = LOADS_OF_UNICODE.char_indices().nth(len).unwrap(); 57 | &LOADS_OF_UNICODE[..index] 58 | } 59 | 60 | #[inline] 61 | fn do_population_bench( 62 | b: &mut Bencher, 63 | cache_builder: DrawCacheBuilder, 64 | text: &str, 65 | scale: f32, 66 | ) { 67 | let font_id = 0; 68 | 69 | let mut glyphs = Vec::new(); 70 | layout_paragraph( 71 | DEJA_VU_SANS.as_scaled(scale), 72 | point(0.0, 0.0), 73 | 500.0, 74 | text, 75 | &mut glyphs, 76 | ); 77 | 78 | let mut cache = cache_builder.build(); 79 | 80 | { 81 | // warm up / avoid benching population performance 82 | for glyph in &glyphs { 83 | cache.queue_glyph(font_id, glyph.clone()); 84 | } 85 | cache 86 | .cache_queued(&[&*DEJA_VU_SANS], |_, _| {}) 87 | .expect("cache_queued initial"); 88 | } 89 | 90 | b.iter(|| { 91 | cache.clear(); 92 | cache.clear_queue(); 93 | 94 | for glyph in &glyphs { 95 | cache.queue_glyph(font_id, glyph.clone()); 96 | } 97 | cache 98 | .cache_queued(&[&*DEJA_VU_SANS], |_, _| {}) 99 | .expect("cache_queued"); 100 | }) 101 | } 102 | 103 | /// Run single threaded: 104 | /// * Leave code unmodified 105 | /// * `cargo bench --bench st_vs_mt -- --save-baseline st` 106 | /// 107 | /// Run multithreaded: 108 | /// * Modify to `.multithread(true)` 109 | /// * `cargo bench --bench st_vs_mt -- --save-baseline mt` 110 | /// 111 | /// Compare with `critcmp mt st --target-dir draw-cache/target/` 112 | fn bench_population_st_vs_mt(c: &mut Criterion) { 113 | for char_len in &[1500, 300, 50, 16] { 114 | for scale in &[150.0, 75.0, 30.0, 12.0] { 115 | let title = format!("bench_{char_len}_chars_{scale}px"); 116 | c.bench_function(&title, |b| { 117 | do_population_bench( 118 | b, 119 | DrawCache::builder() 120 | .dimensions(4096, 4096) 121 | .multithread(false), // use `true` and save as `mt` baseline 122 | unicode_chars(*char_len), 123 | *scale, 124 | ); 125 | }); 126 | } 127 | } 128 | } 129 | 130 | criterion_group!(draw_st_mt, bench_population_st_vs_mt); 131 | 132 | criterion_main!(draw_st_mt); 133 | -------------------------------------------------------------------------------- /draw-cache/src/geometry.rs: -------------------------------------------------------------------------------- 1 | use core::ops; 2 | 3 | /// A rectangle, with top-left corner at min, and bottom-right corner at max. 4 | /// Both field are in `[offset from left, offset from top]` format. 5 | #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] 6 | pub struct Rectangle { 7 | /// Min `[x, y]`. 8 | pub min: [N; 2], 9 | /// Max `[x, y]`. 10 | pub max: [N; 2], 11 | } 12 | 13 | impl + Copy> Rectangle { 14 | #[inline] 15 | pub fn width(&self) -> N { 16 | self.max[0] - self.min[0] 17 | } 18 | 19 | #[inline] 20 | pub fn height(&self) -> N { 21 | self.max[1] - self.min[1] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /fonts/DejaVuSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexheretic/glyph-brush/761b7192b0cbadcf97cea71b807f0fd2b9991732/fonts/DejaVuSans.ttf -------------------------------------------------------------------------------- /fonts/DejaVuSansMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexheretic/glyph-brush/761b7192b0cbadcf97cea71b807f0fd2b9991732/fonts/DejaVuSansMono.ttf -------------------------------------------------------------------------------- /fonts/Exo2-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexheretic/glyph-brush/761b7192b0cbadcf97cea71b807f0fd2b9991732/fonts/Exo2-Light.otf -------------------------------------------------------------------------------- /fonts/GaramondNo8-Reg.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexheretic/glyph-brush/761b7192b0cbadcf97cea71b807f0fd2b9991732/fonts/GaramondNo8-Reg.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexheretic/glyph-brush/761b7192b0cbadcf97cea71b807f0fd2b9991732/fonts/OpenSans-Italic.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexheretic/glyph-brush/761b7192b0cbadcf97cea71b807f0fd2b9991732/fonts/OpenSans-Light.ttf -------------------------------------------------------------------------------- /fonts/WenQuanYiMicroHei.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexheretic/glyph-brush/761b7192b0cbadcf97cea71b807f0fd2b9991732/fonts/WenQuanYiMicroHei.ttf -------------------------------------------------------------------------------- /gfx-glyph/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased (0.17.2) 2 | * Up minimum _gfx_ version to `0.18.3`. 3 | 4 | # 0.17.1 5 | * Specify `#[repr(C)]` for vertex structs. 6 | 7 | # 0.17 8 | * **OpenType (.otf) fonts are now supported** in addition to .ttf fonts. 9 | * Rework crate switching from rusttype to ab_glyph. See [glyph_brush changelog](https://github.com/alexheretic/glyph-brush/blob/master/glyph-brush/CHANGELOG.md#07). 10 | 11 | # 0.16 12 | * Remove deprecated `GlyphBrush::draw_queued` (use `use_queue()`). 13 | * Update glyph_brush -> `0.6`. 14 | 15 | # 0.15 16 | * New API for drawing queued glyphs. Depth buffer usage is now optional. 17 | ```rust 18 | // v0.14 19 | glyph_brush.draw_queued(encoder, color, depth)?; 20 | // v0.15 21 | glyph_brush.use_queue().depth_target(depth).draw(encoder, color)?; 22 | ``` 23 | * Depth test now defaults to _Only draw when the fragment's output depth is less than or equal to the current depth buffer value, and update the buffer_. Instead of _Always pass, never write_. This is because depth buffer interaction is now optional. 24 | * Custom transform usages are now expected to provide the orthographic projection, whereas before this projection was pre-baked. The shader also now inverts the y-axis to be more in-line with other APIs. Previous usages can be technically converted with: 25 | ```rust 26 | // v0.14 27 | glyph_brush.draw_queued_with_transform(custom_transform, ..); 28 | 29 | // v0.15 30 | glyph_brush 31 | .use_queue() 32 | .transform(invert_y * custom_transform * gfx_glyph::default_transform(&gfx_color)) 33 | .draw(..); 34 | ``` 35 | The new style allows easier pre-projection transformations, like rotation, as before only post-projection transforms were possible. Draws without custom transforms are unchanged, they now internally use `gfx_glyph::default_transform`. 36 | * Deprecated `GlyphBrush::draw_queued` in favour of `use_queue()` draw builder usage. 37 | * **Removed** `GlyphBrush::draw_queued_with_transform` in favour of `use_queue()` draw builder usage. 38 | * Update glyph_brush -> `0.5`. 39 | 40 | # 0.14.1 41 | * Enlarge textures within `GL_MAX_TEXTURE_SIZE` if possible. 42 | 43 | # 0.14 44 | * Update gfx -> `0.18`. 45 | 46 | # 0.13.3 47 | * Update glyph_brush -> `0.4`, big performance improvements for changing sections. 48 | 49 | # 0.13.2 50 | * Optimise vertex updating by declaring 'Dynamic' usage & using explicit update calls. 51 | * Add `GlyphBrush::queue_pre_positioned` and example *pre_positioned*. 52 | * Add `SectionGeometry`, `GlyphPositioner` to glyph_brush re-exported types. 53 | * Update glyph_brush -> `0.3`. 54 | 55 | # 0.13.1 56 | * Add `GlyphBrush::keep_cached`. 57 | 58 | # 0.13 59 | * Split crate creating layout project _glyph-brush-layout_ and render API agnostic _glyph-brush_. _gfx-glyph_ becomes a gfx-rs wrapper of _glyph-brush_. See [glyph_brush changes](../glyph-brush/CHANGELOG.md) & [glyph_brush_layout changes](../glyph-brush-layout/CHANGELOG.md). 60 | ``` 61 | gfx-glyph 62 | └── glyph-brush 63 | └── glyph-brush-layout 64 | ``` 65 | 66 | # 0.12.2 67 | * Update rusttype -> `0.7` bringing multithreaded rasterization in the texture cache. This brings a significant reduction in worst case latency in multicore environments. 68 | ``` 69 | name 0.6.4 ns/iter 0.7 ns/iter diff ns/iter diff % speedup 70 | cache::multi_font_population 8,239,309 2,570,034 -5,669,275 -68.81% x 3.21 71 | cache_bad_cases::moving_text_thrashing 21,589,054 6,691,719 -14,897,335 -69.00% x 3.23 72 | cache_bad_cases::resizing 15,162,054 4,607,499 -10,554,555 -69.61% x 3.29 73 | ``` 74 | * Improve cache resizing performance using the new rusttype API. 75 | 76 | _This release is semver compatible with rusttype `0.6.5` & `0.7`._ 77 | 78 | # 0.12.1 79 | * Filter out of bounds glyphs in `VerticalAlign::Center` & `VerticalAlign::Bottom` layouts before texture cache phase as an extra step that reduces later work & gpu texture cache max size requirements. 80 | 81 | New benchmarks added for the v-align center & bottom worst-case performance of a very large section only showing a partial amount. Filtering yields a 1.2x speedup. 82 | ``` 83 | name control ns/iter change ns/iter diff ns/iter diff % speedup 84 | no_cache_render_v_bottom_1_large_section_partially 12,412,793 10,342,991 -2,069,802 -16.67% x 1.20 85 | no_cache_render_v_center_1_large_section_partially 12,408,500 10,305,646 -2,102,854 -16.95% x 1.20 86 | render_v_bottom_1_large_section_partially 3,727 3,747 20 0.54% x 0.99 87 | render_v_center_1_large_section_partially 3,727 3,726 -1 -0.03% x 1.00 88 | ``` 89 | 90 | # 0.12 91 | * Layout code rework to a much cleaner implementation of layered iterators (#28) 92 | - Fixes issues with varied sections having inherent soft-breaks between `SectionText`s. 93 | - Remove built in unicode normalization 94 | - **Breaks** `GlyphPositioner` implementations _(not much implemented outside this crate afaik)_. But is now simpler to implement. 95 | - Add `VerticalAlign::Bottom`, `VerticalAlign::Center` (#33) 96 | - Fix single word larger than bounds issue (#34) 97 | * Fix `BuiltInLineBreaker::AnyCharLineBreaker` mishandling byte-indices in some cases. 98 | * Remove deprecated functions. 99 | * Support raw gfx render & depth views (#30) 100 | * Use generic section hashing for `GlyphBrush` & `GlyphCalculator` caches. This means the default section hashing can be overridden to a different algorithm if desired _(similarly to `HashMap`)_. 101 | * Use `seahash` by default for section hashing. Previously this was done with an xxHash. Seahash is a little slower for large sections, but faster for small ones. General usage see many small sections & few large ones so seahash seems a better default. 102 | 103 | ## Performance 104 | Worst-case _(cache miss)_ benchmark performance, which is the most important area to improve, is **hugely** improved by the layout rework. **1.55-2.16x** faster than `0.11`. 105 | 106 | ``` 107 | name control ns/iter change ns/iter diff ns/iter diff % speedup 108 | no_cache_render_100_small_sections_fully 7,267,231 4,691,164 -2,576,067 -35.45% x 1.55 109 | no_cache_render_1_large_section_partially 1,566,127 725,086 -841,041 -53.70% x 2.16 110 | no_cache_render_3_medium_sections_fully 4,051,124 1,963,114 -2,088,010 -51.54% x 2.06 111 | ``` 112 | 113 | Best-case _(cached)_ performance changes are generally less important, but the affect of moving from xxHash to seahash can be seen. 114 | ``` 115 | name control ns/iter change ns/iter diff ns/iter diff % speedup 116 | render_100_small_sections_fully 34,219 24,757 -9,462 -27.65% x 1.38 117 | render_1_large_section_partially 2,634 3,972 1,338 50.80% x 0.66 118 | render_3_medium_sections_fully 1,584 1,504 -80 -5.05% x 1.05 119 | ``` 120 | 121 | # 0.11 122 | * Optimise vertex generation using instanced rendering. Improves worst-case performance by 18-50%. 123 | * Update rusttype -> `0.6` including large texture cache performance improvements. 124 | 125 | Overall worst-case _(cache miss)_ benchmark performance is improved by **42-74%** compared with gfx-glyph `0.10.2`. 126 | 127 | ``` 128 | name control ns/iter change ns/iter diff ns/iter diff % speedup 129 | no_cache_render_100_small_sections_fully 13,989,975 8,051,112 -5,938,863 -42.45% x 1.74 130 | no_cache_render_1_large_section_partially 2,377,767 1,643,650 -734,117 -30.87% x 1.45 131 | no_cache_render_3_medium_sections_fully 6,116,924 4,318,639 -1,798,285 -29.40% x 1.42 132 | ``` 133 | 134 | # 0.10.2 135 | * Add `GlyphBrush::add_font` & `GlyphBrush::add_font_bytes` 136 | 137 | # 0.10.1 138 | * Use rusttype gpu-cache glyph padding to avoid glyph texture artifacts after transforms 139 | * Use default bilinear filtering to improve transformed glyph rendering 140 | * Remove unused dependencies 141 | 142 | # 0.10 143 | * Update rusttype -> `0.5`, see [rusttype changelog](https://github.com/redox-os/rusttype/blob/master/CHANGELOG.md#050). 144 | Brings performance improvements. 145 | ``` 146 | name control ns/iter change ns/iter diff ns/iter diff % speedup 147 | no_cache_render_100_small_sections_fully 16,510,001 16,022,255 -487,746 -2.95% x 1.03 148 | no_cache_render_1_large_section_partially 4,404,936 4,381,983 -22,953 -0.52% x 1.01 149 | no_cache_render_3_medium_sections_fully 11,041,238 10,963,063 -78,175 -0.71% x 1.01 150 | ``` 151 | 152 | # 0.9.1 153 | * Switch to xxHashing for section caches 154 | * Use upstream rusttype::gpu_cache _(All changes are upstreamed and released)_ 155 | 156 | _Bench change since 0.9.0_ 157 | ``` 158 | name control.stdout ns/iter change.stdout ns/iter diff ns/iter diff % speedup 159 | render_100_small_sections_fully 34,236 33,354 -882 -2.58% x 1.03 160 | render_1_large_section_partially 6,970 2,535 -4,435 -63.63% x 2.75 161 | render_3_medium_sections_fully 2,165 1,549 -616 -28.45% x 1.40 162 | 163 | ``` 164 | 165 | # 0.9 166 | * Fix backtraces in warn logging when re-sizing the glyph texture cache 167 | * Update rusttype 0.4 168 | 169 | # 0.8.2 170 | * Support multi-byte unicode characters in built-in layouts _(only partially supported before)_. 171 | * Optimise vertex generation allocation; gives a small worst-case performance boost. 172 | ``` 173 | name control.stdout ns/iter change.stdout ns/iter diff ns/iter diff % speedup 174 | no_cache_render_100_small_sections_fully 19,016,459 17,711,135 -1,305,324 -6.86% x 1.07 175 | no_cache_render_3_medium_sections_fully 12,896,250 12,053,503 -842,747 -6.53% x 1.07 176 | no_cache_render_1_large_section_partially 4,897,027 4,705,228 -191,799 -3.92% x 1.04 177 | ``` 178 | 179 | # 0.8.1 180 | * Improve GPU texture cache performance by partitioning the glyph lookup table by font & glyph id. 181 | ``` 182 | name control.stdout ns/iter change.stdout ns/iter diff ns/iter diff % speedup 183 | gpu_cache::cache_bench_tests::cache_bench_tolerance_1 3,004,912 2,502,683 -502,229 -16.71% x 1.20 184 | gpu_cache::cache_bench_tests::cache_bench_tolerance_p1 5,081,960 4,638,536 -443,424 -8.73% x 1.10 185 | ``` 186 | 187 | # 0.8 188 | * Update to gfx `0.17` 189 | * Update to log `0.4` 190 | 191 | # 0.7 192 | * `GlyphCalculator` allows font calculations / pixel bounds etc without actually being able to draw anything, or the need for gfx objects. Using a scoped caching system. 193 | * Update to rusttype `0.3.0` 194 | * Cache / font lifetime changes made to rusttype allow removing the `.standalone()` calls when adding glyphs to the gpu-cache. This and other rusttype optimisations can result in **up to ~25% faster worst case performance** (worst case being no position/draw caching / text changes every frame). 195 | * `OwnedVariedSection` & `OwnedSectionText` to help some edge cases where borrowing is annoying. 196 | * Simple `Debug` implementations to allow end users to derive more easily. 197 | 198 | # 0.6.4 199 | * Switch to OpenGL 3.2 / glsl 150 requirement to fix MacOS issues with glsl 130 200 | 201 | I'm publishing as a minor increment even though this _may_ break your setup if you relied on OpenGL < 3.2 support, but I don't think anyone actually does. **Please get into contact if this broke your setup.** 202 | 203 | # 0.6.3 204 | * Fix `GlyphBrush` being able to use cached vertices after a render resolution change. 205 | * When dynamically increasing the gpu glyph texture warn and show a backtrace to allow this to be addressed when using many `GlyphBrush` instances. 206 | 207 | # 0.6.2 208 | * Fix `VariedSection`s with multiple `SectionText` parts ignoring end-of-line hard breaks. 209 | 210 | # 0.6.1 211 | * Add `GlyphBrush#glyphs` & `#glyphs_custom_layout` methods to allow access to the `PositionedGlyphs` of a section. 212 | 213 | # 0.6 214 | * `GlyphBrushBuilder` supports `Font` types with `using_font`/`add_font` 215 | * Renamed existing `GlyphBrushBuilder` methods -> `using_font_bytes`/`add_font_bytes` 216 | 217 | # 0.5.1 218 | * Fix rare glyph ordering panic in gpu cache code 219 | 220 | # 0.5 221 | * Allow sections with multiple diverse font/scale/color parts within the same layout & bounds. 222 | * New `VariedSection` available, see *varied* example. 223 | * A single `GlyphBrush` can now support multiple fonts in a single GPU texture-cache. 224 | * Improve `Layout` to be more flexible to change when using non-default. 225 | * E.g. `let layout = Layout::default().h_align(HorizontalAlign::Right)` 226 | * Remove generics from `Section`. 227 | * Improve glyph fragment shader to discard when alpha is zero. This improves out-of-order transparency of glyph holes, demonstrated in the *depth* example. 228 | * Remove changelog from readme. 229 | 230 | # 0.4.2 231 | * Accept generic gfx depth formats, e.g `DepthStencil` 232 | 233 | # 0.4 234 | * Support depth testing with configurable gfx depth test (via `GlyphBrushBuilder::depth_test`). 235 | * `Section`s now have a `z` value to indicate the depth. 236 | * Actual depth testing is disabled by default, but a reference to the depth buffer is now required to draw. 237 | * Streamline API for use with built-in `Layout`s, while still allowing custom layouts. 238 | * Built-in layouts are now a member of `Section`. 239 | * Custom layouts can still be used by using `GlyphBrush::queue_custom_layout` method instead of `queue`. 240 | * `Section<'a, L>` are now generic to allow pluggable `LineBreaker` logic in the layout. This is a little unfortunate for the API surface. 241 | * Remove unnecessary `OwnedSection` and `StaticSection` to simplify the API. 242 | * `pixel_bounding_box` renamed to `pixel_bounds` & `pixel_bounds_custom_layout` 243 | * These now return `Option<_>` to handle the bounds of 'nothing' properly 244 | * `GlyphBrushBuilder` `gpu_cache_position_tolerance` default reduced to 0.1 (from 1.0) 245 | 246 | # 0.3.3 247 | * Fix another GPU caching issue that could cause missing glyphs 248 | * Fix a layout issue that could miss a character immediately preceding EOF 249 | * Optimise GPU cache sorting performance 250 | 251 | # 0.3.2 252 | * Move fixed GPU caching logic into crate replacing `rusttype::gpu_cache` 253 | * `Section` & `StaticSection` implement `Copy` 254 | 255 | # 0.3 256 | * Use `Into` instead of explicit `&[u8]` for font byte input to improve flexibility. 257 | 258 | # 0.2 259 | * Adopt default line breaking logic according to the Unicode Standard Annex \#14 with `StandardLineBreaker` (included in `Layout::default()`). A `LineBreaker` implementation can be provided instead of using one of these. 260 | 261 | # 0.1 262 | * Initial release. 263 | -------------------------------------------------------------------------------- /gfx-glyph/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gfx_glyph" 3 | version = "0.17.1" 4 | authors = ["Alex Butler "] 5 | edition = "2021" 6 | description = "Fast GPU cached text rendering using gfx-rs & ab_glyph" 7 | repository = "https://github.com/alexheretic/glyph-brush" 8 | keywords = ["font", "ttf", "truetype", "gfx", "text"] 9 | license = "Apache-2.0" 10 | readme = "README.md" 11 | 12 | [dependencies] 13 | backtrace = "0.3" 14 | gfx = "0.18.3" 15 | gfx_core = "0.9" 16 | glyph_brush = { version = "0.7", path = "../glyph-brush" } 17 | log = "0.4" 18 | 19 | [dev-dependencies] 20 | cgmath = "0.18" 21 | env_logger = { version = "0.11", default-features = false } 22 | gfx_device_gl = "0.16" 23 | glutin = "0.32" 24 | glutin-winit = "0.5" 25 | old_school_gfx_glutin_ext = "0.34" 26 | spin_sleep_util = "0.1" 27 | winit = "0.30" 28 | -------------------------------------------------------------------------------- /gfx-glyph/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /gfx-glyph/README.md: -------------------------------------------------------------------------------- 1 | gfx_glyph 2 | [![crates.io](https://img.shields.io/crates/v/gfx_glyph.svg)](https://crates.io/crates/gfx_glyph) 3 | [![Documentation](https://docs.rs/gfx_glyph/badge.svg)](https://docs.rs/gfx_glyph) 4 | ================ 5 | 6 | Fast GPU cached text rendering using [gfx-rs v0.18](https://github.com/gfx-rs/gfx/tree/pre-ll) & [glyph-brush](https://github.com/alexheretic/glyph-brush/tree/master/glyph-brush). 7 | 8 | ```rust 9 | use gfx_glyph::{ab_glyph::FontArc, GlyphBrushBuilder, Section, Text}; 10 | 11 | let dejavu = FontArc::try_from_slice(include_bytes!("../../fonts/DejaVuSans.ttf"))?; 12 | let mut glyph_brush = GlyphBrushBuilder::using_font(dejavu).build(gfx_factory.clone()); 13 | 14 | // set the text scale, font, color, position, etc 15 | let section = Section::default() 16 | .add_text(Text::new("Hello gfx_glyph")); 17 | 18 | glyph_brush.queue(section); 19 | glyph_brush.queue(some_other_section); 20 | 21 | glyph_brush.use_queue().draw(&mut gfx_encoder, &gfx_color)?; 22 | ``` 23 | 24 | ## Examples 25 | Have a look at 26 | * `cargo run --example paragraph --release` 27 | * `cargo run --example performance --release` 28 | * `cargo run --example varied --release` 29 | * `cargo run --example depth --release` 30 | 31 | 32 | ## Limitations 33 | The current implementation supports OpenGL *(3.2 or later)* only. Use [glyph-brush](https://github.com/alexheretic/glyph-brush/tree/master/glyph-brush) directly if this is an issue. 34 | -------------------------------------------------------------------------------- /gfx-glyph/examples/depth.rs: -------------------------------------------------------------------------------- 1 | mod init; 2 | 3 | use gfx::{ 4 | format::{Depth, Srgba8}, 5 | Device, 6 | }; 7 | use gfx_glyph::{ab_glyph::*, *}; 8 | use glutin::surface::GlSurface; 9 | use glutin_winit::GlWindow; 10 | use init::init_example; 11 | use std::{error::Error, time::Duration}; 12 | use winit::{ 13 | dpi::PhysicalSize, 14 | event::{KeyEvent, WindowEvent}, 15 | event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, 16 | keyboard::{Key, NamedKey}, 17 | }; 18 | 19 | const TITLE: &str = "gfx_glyph example - resize to see multi-text layout"; 20 | 21 | fn main() -> Result<(), Box> { 22 | init_example("depth"); 23 | Ok(EventLoop::new()?.run_app(&mut WinitApp(None))?) 24 | } 25 | 26 | struct WinitApp(Option); 27 | 28 | impl winit::application::ApplicationHandler for WinitApp { 29 | fn resumed(&mut self, events: &ActiveEventLoop) { 30 | events.set_control_flow(ControlFlow::Poll); 31 | *self = Self(Some(App::new(events).unwrap())); 32 | } 33 | 34 | fn window_event( 35 | &mut self, 36 | events: &ActiveEventLoop, 37 | _: winit::window::WindowId, 38 | event: WindowEvent, 39 | ) { 40 | if let Self(Some(app)) = self { 41 | app.window_event(events, event); 42 | } 43 | } 44 | 45 | fn about_to_wait(&mut self, _events: &ActiveEventLoop) { 46 | if let Self(Some(App { window, .. })) = self { 47 | window.request_redraw(); 48 | }; 49 | } 50 | } 51 | 52 | struct App { 53 | device: gfx_device_gl::Device, 54 | encoder: gfx::Encoder, 55 | color_view: gfx::handle::RenderTargetView, 56 | depth_view: gfx::handle::DepthStencilView, 57 | glyph_brush: GlyphBrush, 58 | view_size: PhysicalSize, 59 | interval: spin_sleep_util::Interval, 60 | reporter: spin_sleep_util::RateReporter, 61 | gl_surface: glutin::surface::Surface, 62 | gl_context: glutin::context::PossiblyCurrentContext, 63 | window: winit::window::Window, 64 | } 65 | 66 | impl App { 67 | fn new(events: &ActiveEventLoop) -> Result> { 68 | let window_attrs = winit::window::Window::default_attributes() 69 | .with_title(TITLE) 70 | .with_inner_size(winit::dpi::PhysicalSize::new(1024, 576)); 71 | 72 | let old_school_gfx_glutin_ext::Init { 73 | window, 74 | gl_surface, 75 | gl_context, 76 | device, 77 | mut factory, 78 | color_view, 79 | depth_view, 80 | .. 81 | } = old_school_gfx_glutin_ext::window_builder(events, window_attrs) 82 | .build::()?; 83 | 84 | let fonts = vec![ 85 | FontArc::try_from_slice(include_bytes!("../../fonts/DejaVuSans.ttf"))?, 86 | FontArc::try_from_slice(include_bytes!("../../fonts/OpenSans-Italic.ttf"))?, 87 | ]; 88 | 89 | let glyph_brush = GlyphBrushBuilder::using_fonts(fonts) 90 | .initial_cache_size((512, 512)) 91 | .build(factory.clone()); 92 | 93 | let encoder: gfx::Encoder<_, _> = factory.create_command_buffer().into(); 94 | 95 | let view_size = window.inner_size(); 96 | 97 | Ok(Self { 98 | window, 99 | gl_surface, 100 | gl_context, 101 | device, 102 | encoder, 103 | color_view, 104 | depth_view, 105 | glyph_brush, 106 | interval: spin_sleep_util::interval(Duration::from_secs(1) / 250), 107 | reporter: spin_sleep_util::RateReporter::new(Duration::from_secs(1)), 108 | view_size, 109 | }) 110 | } 111 | 112 | fn window_event(&mut self, events: &ActiveEventLoop, event: WindowEvent) { 113 | const ITALIC_FONT: FontId = FontId(1); 114 | 115 | let Self { 116 | window, 117 | gl_surface, 118 | gl_context, 119 | device, 120 | encoder, 121 | color_view, 122 | depth_view, 123 | glyph_brush, 124 | view_size, 125 | interval, 126 | reporter, 127 | } = self; 128 | 129 | match event { 130 | WindowEvent::CloseRequested 131 | | WindowEvent::KeyboardInput { 132 | event: 133 | KeyEvent { 134 | logical_key: Key::Named(NamedKey::Escape), 135 | .. 136 | }, 137 | .. 138 | } => events.exit(), 139 | WindowEvent::RedrawRequested => { 140 | // handle resizes 141 | let w_size = window.inner_size(); 142 | if *view_size != w_size { 143 | window.resize_surface(gl_surface, gl_context); 144 | old_school_gfx_glutin_ext::resize_views(w_size, color_view, depth_view); 145 | *view_size = w_size; 146 | } 147 | 148 | encoder.clear(color_view, [0.02, 0.02, 0.02, 1.0]); 149 | encoder.clear_depth(depth_view, 1.0); 150 | 151 | let (width, height) = (w_size.width as f32, w_size.height as f32); 152 | 153 | // first section is queued, and therefore drawn, first with lower z 154 | glyph_brush.queue( 155 | Section::default() 156 | .add_text( 157 | Text::new("On top") 158 | .with_scale(95.0) 159 | .with_color([0.8, 0.8, 0.8, 1.0]) 160 | .with_z(0.2) 161 | .with_font_id(ITALIC_FONT), 162 | ) 163 | .with_screen_position((width / 2.0, 100.0)) 164 | .with_bounds((width, height - 100.0)) 165 | .with_layout(Layout::default().h_align(HorizontalAlign::Center)), 166 | ); 167 | 168 | // 2nd section is drawn last but with higher z, 169 | // draws are subject to depth testing 170 | glyph_brush.queue( 171 | Section::default() 172 | .add_text( 173 | Text::new(&include_str!("lipsum.txt").replace("\n\n", "").repeat(10)) 174 | .with_scale(30.0) 175 | .with_color([0.05, 0.05, 0.1, 1.0]) 176 | .with_z(1.0), 177 | ) 178 | .with_bounds((width, height)), 179 | ); 180 | 181 | glyph_brush 182 | .use_queue() 183 | // Enable depth testing with default less-equal drawing and update the depth buffer 184 | .depth_target(depth_view) 185 | .draw(encoder, color_view) 186 | .unwrap(); 187 | 188 | encoder.flush(device); 189 | gl_surface.swap_buffers(gl_context).unwrap(); 190 | device.cleanup(); 191 | 192 | if let Some(rate) = reporter.increment_and_report() { 193 | window.set_title(&format!("{TITLE} - {rate:.0} FPS")); 194 | } 195 | interval.tick(); 196 | } 197 | _ => (), 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /gfx-glyph/examples/init.rs: -------------------------------------------------------------------------------- 1 | //! Shared example initialisation logic. 2 | #![allow(unused)] 3 | use glutin::{ 4 | context::PossiblyCurrentContext, 5 | surface::{GlSurface, Surface, SurfaceAttributes, SurfaceAttributesBuilder, WindowSurface}, 6 | }; 7 | use std::{env, num::NonZeroU32}; 8 | use winit::window::Window; 9 | 10 | /// Setup env vars, init logging & notify about --release performance. 11 | pub fn init_example(example_name: &str) { 12 | if env::var_os("RUST_LOG").is_none() { 13 | env::set_var("RUST_LOG", "gfx_glyph=warn"); 14 | } 15 | env_logger::init(); 16 | 17 | if cfg!(debug_assertions) && env::var_os("yes_i_really_want_debug_mode").is_none() { 18 | eprintln!( 19 | "Note: Release mode will improve performance greatly.\n \ 20 | e.g. use `cargo run --example {example_name} --release`" 21 | ); 22 | } 23 | 24 | // disables vsync maybe 25 | if env::var_os("vblank_mode").is_none() { 26 | env::set_var("vblank_mode", "0"); 27 | } 28 | } 29 | 30 | fn main() { 31 | eprintln!("\"init\" isn't an example. Try: cargo run --example paragraph"); 32 | } 33 | -------------------------------------------------------------------------------- /gfx-glyph/examples/lipsum.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, ferri simul omittantur eam eu, no debet doming dolorem ius. Iriure vocibus est te, natum delicata dignissim pri ea. Purto docendi definitiones no qui. Vel ridens instructior ad, vidisse percipitur et eos. Alienum ocurreret laboramus mei cu, usu ne meliore nostrum, usu tritani luptatum electram ad. 2 | 3 | Vis oratio tantas prodesset et, id stet inermis mea, at his copiosae accusata. Mel diam accusata argumentum cu, ut agam consul invidunt est. Ocurreret appellantur deterruisset no vis, his alia postulant inciderint no. Has albucius offendit at. An has noluisse comprehensam, vel veri dicit blandit ea, per paulo noluisse reformidans no. Nec ad sale illum soleat, agam scriptorem ad per. 4 | 5 | An cum odio mucius apeirian, labores conceptam ex nec, eruditi habemus qualisque eam an. Eu facilisi maluisset eos, fabulas apeirian ut qui, no atqui blandit vix. Apeirian phaedrum pri ex, vel hinc omnes sapientem et, vim vocibus legendos disputando ne. Et vel semper nominati rationibus, eum lorem causae scripta no. 6 | 7 | Ut quo elitr viderer constituam, pro omnesque forensibus at. Timeam scaevola mediocrem ut pri, te pro congue delicatissimi. Mei wisi nostro imperdiet ea, ridens salutatus per no, ut viris partem disputationi sit. Exerci eripuit referrentur vix at, sale mediocrem repudiare per te, modus admodum an eam. No vocent indoctum vis, ne quodsi patrioque vix. Vocent labores omittam et usu. 8 | 9 | Democritum signiferumque id nam, enim idque facilis at his. Inermis percipitur scriptorem sea cu, est ne error ludus option. Graecis expetenda contentiones cum et, ius nullam impetus suscipit ex. Modus clita corrumpit mel te, qui at lorem harum, primis cetero habemus sea id. Ei mutat affert dolorum duo, eum dissentias voluptatibus te, libris theophrastus duo id. 10 | -------------------------------------------------------------------------------- /gfx-glyph/examples/paragraph.rs: -------------------------------------------------------------------------------- 1 | //! An example of paragraph rendering 2 | //! Controls 3 | //! 4 | //! * Resize window to adjust layout 5 | //! * Scroll to modify font size 6 | //! * Type to add/remove text 7 | //! * Ctrl-Scroll to zoom in/out using a transform, this is cheap but notice how ab_glyph can't 8 | //! render at full quality without the correct pixel information. 9 | mod init; 10 | 11 | use cgmath::{Matrix4, Rad, Transform, Vector3}; 12 | use gfx::{ 13 | format::{Depth, Srgba8}, 14 | Device, 15 | }; 16 | use gfx_glyph::{ab_glyph, GlyphBrush}; 17 | use glutin::surface::GlSurface; 18 | use glutin_winit::GlWindow; 19 | use init::init_example; 20 | use std::{ 21 | error::Error, 22 | f32::consts::PI as PI32, 23 | io::{self, Write}, 24 | time::Duration, 25 | }; 26 | use winit::{ 27 | dpi::PhysicalSize, 28 | event::{ElementState, KeyEvent, Modifiers, MouseScrollDelta, WindowEvent}, 29 | event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, 30 | keyboard::{Key, NamedKey}, 31 | }; 32 | 33 | const MAX_FONT_SIZE: f32 = 2000.0; 34 | const TITLE: &str = "gfx_glyph example - scroll to size, type to modify, ctrl-scroll \ 35 | to gpu zoom, ctrl-shift-scroll to gpu rotate"; 36 | 37 | fn main() -> Result<(), Box> { 38 | init_example("paragraph"); 39 | Ok(EventLoop::new()?.run_app(&mut WinitApp(None))?) 40 | } 41 | 42 | struct WinitApp(Option); 43 | 44 | impl winit::application::ApplicationHandler for WinitApp { 45 | fn resumed(&mut self, events: &ActiveEventLoop) { 46 | events.set_control_flow(ControlFlow::Poll); 47 | *self = Self(Some(App::new(events).unwrap())); 48 | } 49 | 50 | fn window_event( 51 | &mut self, 52 | events: &ActiveEventLoop, 53 | _: winit::window::WindowId, 54 | event: WindowEvent, 55 | ) { 56 | if let Self(Some(app)) = self { 57 | app.window_event(events, event); 58 | } 59 | } 60 | 61 | fn about_to_wait(&mut self, _events: &ActiveEventLoop) { 62 | if let Self(Some(App { window, .. })) = self { 63 | window.request_redraw(); 64 | }; 65 | } 66 | } 67 | 68 | struct App { 69 | device: gfx_device_gl::Device, 70 | encoder: gfx::Encoder, 71 | color_view: gfx::handle::RenderTargetView, 72 | depth_view: gfx::handle::DepthStencilView, 73 | glyph_brush: GlyphBrush, 74 | view_size: PhysicalSize, 75 | interval: spin_sleep_util::Interval, 76 | reporter: spin_sleep_util::RateReporter, 77 | modifiers: Modifiers, 78 | text: String, 79 | font_size: f32, 80 | zoom: f32, 81 | angle: f32, 82 | gl_surface: glutin::surface::Surface, 83 | gl_context: glutin::context::PossiblyCurrentContext, 84 | window: winit::window::Window, 85 | } 86 | 87 | impl App { 88 | fn new(events: &ActiveEventLoop) -> Result> { 89 | let window_attrs = winit::window::Window::default_attributes() 90 | .with_title(TITLE) 91 | .with_inner_size(winit::dpi::PhysicalSize::new(1024, 576)); 92 | 93 | let old_school_gfx_glutin_ext::Init { 94 | window, 95 | gl_surface, 96 | gl_context, 97 | device, 98 | mut factory, 99 | color_view, 100 | depth_view, 101 | .. 102 | } = old_school_gfx_glutin_ext::window_builder(events, window_attrs) 103 | .build::()?; 104 | 105 | let font = 106 | ab_glyph::FontArc::try_from_slice(include_bytes!("../../fonts/OpenSans-Light.ttf"))?; 107 | let glyph_brush = gfx_glyph::GlyphBrushBuilder::using_font(font) 108 | .initial_cache_size((1024, 1024)) 109 | .build(factory.clone()); 110 | 111 | let encoder: gfx::Encoder<_, _> = factory.create_command_buffer().into(); 112 | let view_size = window.inner_size(); 113 | 114 | Ok(Self { 115 | window, 116 | gl_surface, 117 | gl_context, 118 | device, 119 | encoder, 120 | color_view, 121 | depth_view, 122 | glyph_brush, 123 | interval: spin_sleep_util::interval(Duration::from_secs(1) / 250), 124 | reporter: spin_sleep_util::RateReporter::new(Duration::from_secs(1)), 125 | view_size, 126 | modifiers: <_>::default(), 127 | text: include_str!("lipsum.txt").into(), 128 | font_size: 18.0, 129 | zoom: 1.0, 130 | angle: 0.0, 131 | }) 132 | } 133 | 134 | fn window_event(&mut self, events: &ActiveEventLoop, event: WindowEvent) { 135 | let Self { 136 | modifiers, 137 | text, 138 | font_size, 139 | zoom, 140 | angle, 141 | .. 142 | } = self; 143 | 144 | match event { 145 | WindowEvent::ModifiersChanged(new_mods) => *modifiers = new_mods, 146 | WindowEvent::CloseRequested => events.exit(), 147 | WindowEvent::KeyboardInput { 148 | event: 149 | KeyEvent { 150 | logical_key, 151 | state: ElementState::Pressed, 152 | .. 153 | }, 154 | .. 155 | } => match logical_key { 156 | Key::Named(NamedKey::Escape) => events.exit(), 157 | Key::Named(NamedKey::Backspace) => { 158 | text.pop(); 159 | } 160 | key => { 161 | if let Some(str) = key.to_text() { 162 | text.push_str(str); 163 | } 164 | } 165 | }, 166 | WindowEvent::MouseWheel { 167 | delta: MouseScrollDelta::LineDelta(_, y), 168 | .. 169 | } => { 170 | let ctrl = modifiers.state().control_key(); 171 | let shift = modifiers.state().shift_key(); 172 | if ctrl && shift { 173 | if y > 0.0 { 174 | *angle += 0.02 * PI32; 175 | } else { 176 | *angle -= 0.02 * PI32; 177 | } 178 | if (*angle % (PI32 * 2.0)).abs() < 0.01 { 179 | *angle = 0.0; 180 | } 181 | print!("\r \r"); 182 | print!("transform-angle -> {:.2} * π", *angle / PI32); 183 | let _ = io::stdout().flush(); 184 | } else if ctrl && !shift { 185 | let old_zoom = *zoom; 186 | // increase/decrease zoom 187 | if y > 0.0 { 188 | *zoom += 0.1; 189 | } else { 190 | *zoom -= 0.1; 191 | } 192 | *zoom = zoom.clamp(0.1, 1.0); 193 | if (*zoom - old_zoom).abs() > 1e-2 { 194 | print!("\r \r"); 195 | print!("transform-zoom -> {zoom:.1}"); 196 | let _ = io::stdout().flush(); 197 | } 198 | } else { 199 | // increase/decrease font size 200 | let old_size = *font_size; 201 | let mut size = *font_size; 202 | if y > 0.0 { 203 | size += (size / 4.0).max(2.0) 204 | } else { 205 | size *= 4.0 / 5.0 206 | }; 207 | *font_size = size.clamp(1.0, MAX_FONT_SIZE); 208 | if (*font_size - old_size).abs() > 1e-2 { 209 | print!("\r \r"); 210 | print!("font-size -> {font_size:.1}"); 211 | let _ = io::stdout().flush(); 212 | } 213 | } 214 | } 215 | WindowEvent::RedrawRequested => self.draw(), 216 | _ => (), 217 | } 218 | } 219 | 220 | fn draw(&mut self) { 221 | let Self { 222 | window, 223 | gl_surface, 224 | gl_context, 225 | device, 226 | encoder, 227 | color_view, 228 | depth_view, 229 | glyph_brush, 230 | view_size, 231 | interval, 232 | reporter, 233 | text, 234 | font_size, 235 | zoom, 236 | angle, 237 | .. 238 | } = self; 239 | 240 | // handle resizes 241 | let w_size = window.inner_size(); 242 | if *view_size != w_size { 243 | window.resize_surface(gl_surface, gl_context); 244 | old_school_gfx_glutin_ext::resize_views(w_size, color_view, depth_view); 245 | *view_size = w_size; 246 | } 247 | 248 | encoder.clear(color_view, [0.02, 0.02, 0.02, 1.0]); 249 | 250 | let (width, height, ..) = color_view.get_dimensions(); 251 | let (width, height) = (f32::from(width), f32::from(height)); 252 | let scale = *font_size * window.scale_factor() as f32; 253 | 254 | // The section is all the info needed for the glyph brush to render a 'section' of text. 255 | let section = gfx_glyph::Section::default() 256 | .add_text( 257 | Text::new(text) 258 | .with_scale(scale) 259 | .with_color([0.9, 0.3, 0.3, 1.0]), 260 | ) 261 | .with_bounds((width / 3.15, height)); 262 | 263 | // Adds a section & layout to the queue for the next call to `use_queue().draw(..)`, 264 | // this can be called multiple times for different sections that want to use the 265 | // same font and gpu cache. 266 | // This step computes the glyph positions, this is cached to avoid unnecessary 267 | // recalculation. 268 | glyph_brush.queue(§ion); 269 | 270 | use gfx_glyph::*; 271 | glyph_brush.queue( 272 | Section::default() 273 | .add_text( 274 | Text::new(text) 275 | .with_scale(scale) 276 | .with_color([0.3, 0.9, 0.3, 1.0]), 277 | ) 278 | .with_screen_position((width / 2.0, height / 2.0)) 279 | .with_bounds((width / 3.15, height)) 280 | .with_layout( 281 | Layout::default() 282 | .h_align(HorizontalAlign::Center) 283 | .v_align(VerticalAlign::Center), 284 | ), 285 | ); 286 | 287 | glyph_brush.queue( 288 | Section::default() 289 | .add_text( 290 | Text::new(text) 291 | .with_scale(scale) 292 | .with_color([0.3, 0.3, 0.9, 1.0]), 293 | ) 294 | .with_screen_position((width, height)) 295 | .with_bounds((width / 3.15, height)) 296 | .with_layout( 297 | Layout::default() 298 | .h_align(HorizontalAlign::Right) 299 | .v_align(VerticalAlign::Bottom), 300 | ), 301 | ); 302 | 303 | // Rotation 304 | let offset = Matrix4::from_translation(Vector3::new(-width / 2.0, -height / 2.0, 0.0)); 305 | let rotation = 306 | offset.inverse_transform().unwrap() * Matrix4::from_angle_z(Rad(*angle)) * offset; 307 | 308 | // Default projection 309 | let projection: Matrix4 = gfx_glyph::default_transform(&*color_view).into(); 310 | 311 | // Here an example transform is used as a cheap zoom out (controlled with ctrl-scroll) 312 | let zoom = Matrix4::from_scale(*zoom); 313 | 314 | // Combined transform 315 | let transform = zoom * projection * rotation; 316 | 317 | // Finally once per frame you want to actually draw all the sections you've submitted 318 | // with `queue` calls. 319 | // 320 | // Note: Drawing in the case the text is unchanged from the previous frame (a common case) 321 | // is essentially free as the vertices are reused & gpu cache updating interaction 322 | // can be skipped. 323 | glyph_brush 324 | .use_queue() 325 | .transform(transform) 326 | .draw(encoder, color_view) 327 | .unwrap(); 328 | 329 | encoder.flush(device); 330 | gl_surface.swap_buffers(gl_context).unwrap(); 331 | device.cleanup(); 332 | 333 | if let Some(rate) = reporter.increment_and_report() { 334 | window.set_title(&format!("{TITLE} - {rate:.0} FPS")); 335 | } 336 | interval.tick(); 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /gfx-glyph/examples/performance.rs: -------------------------------------------------------------------------------- 1 | mod init; 2 | 3 | use gfx::{ 4 | format::{Depth, Srgba8}, 5 | Device, 6 | }; 7 | use gfx_glyph::{ab_glyph::*, *}; 8 | use glutin::surface::GlSurface; 9 | use glutin_winit::GlWindow; 10 | use init::init_example; 11 | use std::{env, error::Error, time::Duration}; 12 | use winit::{ 13 | dpi::PhysicalSize, 14 | event::{ElementState, KeyEvent, MouseScrollDelta, WindowEvent}, 15 | event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, 16 | keyboard::{Key, NamedKey}, 17 | }; 18 | 19 | const MAX_FONT_SIZE: f32 = 4000.0; 20 | const TITLE: &str = "gfx_glyph rendering 30,000 glyphs - scroll to size, type to modify"; 21 | 22 | fn main() -> Result<(), Box> { 23 | init_example("performance"); 24 | if cfg!(debug_assertions) && env::var("yes_i_really_want_debug_mode").is_err() { 25 | eprintln!( 26 | "You should probably run an example called 'performance' in release mode, \ 27 | don't you think?\n \ 28 | If you really want to see debug performance set env var `yes_i_really_want_debug_mode`" 29 | ); 30 | return Ok(()); 31 | } 32 | 33 | Ok(EventLoop::new()?.run_app(&mut WinitApp(None))?) 34 | } 35 | 36 | struct WinitApp(Option); 37 | 38 | impl winit::application::ApplicationHandler for WinitApp { 39 | fn resumed(&mut self, events: &ActiveEventLoop) { 40 | events.set_control_flow(ControlFlow::Poll); 41 | *self = Self(Some(App::new(events).unwrap())); 42 | } 43 | 44 | fn window_event( 45 | &mut self, 46 | events: &ActiveEventLoop, 47 | _: winit::window::WindowId, 48 | event: WindowEvent, 49 | ) { 50 | if let Self(Some(app)) = self { 51 | app.window_event(events, event); 52 | } 53 | } 54 | 55 | fn about_to_wait(&mut self, _events: &ActiveEventLoop) { 56 | if let Self(Some(App { window, .. })) = self { 57 | window.request_redraw(); 58 | }; 59 | } 60 | } 61 | 62 | struct App { 63 | device: gfx_device_gl::Device, 64 | encoder: gfx::Encoder, 65 | color_view: gfx::handle::RenderTargetView, 66 | depth_view: gfx::handle::DepthStencilView, 67 | glyph_brush: GlyphBrush>, 68 | view_size: PhysicalSize, 69 | reporter: spin_sleep_util::RateReporter, 70 | text: String, 71 | font_size: f32, 72 | gl_surface: glutin::surface::Surface, 73 | gl_context: glutin::context::PossiblyCurrentContext, 74 | window: winit::window::Window, 75 | } 76 | 77 | impl App { 78 | fn new(events: &ActiveEventLoop) -> Result> { 79 | let window_attrs = winit::window::Window::default_attributes() 80 | .with_title(TITLE) 81 | .with_inner_size(winit::dpi::PhysicalSize::new(1024, 576)); 82 | 83 | let old_school_gfx_glutin_ext::Init { 84 | window, 85 | gl_surface, 86 | gl_context, 87 | device, 88 | mut factory, 89 | color_view, 90 | depth_view, 91 | .. 92 | } = old_school_gfx_glutin_ext::window_builder(events, window_attrs) 93 | .build::()?; 94 | 95 | let dejavu = FontRef::try_from_slice(include_bytes!("../../fonts/DejaVuSans.ttf"))?; 96 | let glyph_brush = GlyphBrushBuilder::using_font(dejavu) 97 | .initial_cache_size((2048, 2048)) 98 | .draw_cache_position_tolerance(1.0) 99 | .build(factory.clone()); 100 | 101 | let encoder: gfx::Encoder<_, _> = factory.create_command_buffer().into(); 102 | let view_size = window.inner_size(); 103 | 104 | Ok(Self { 105 | window, 106 | gl_surface, 107 | gl_context, 108 | device, 109 | encoder, 110 | color_view, 111 | depth_view, 112 | glyph_brush, 113 | reporter: spin_sleep_util::RateReporter::new(Duration::from_secs(1)), 114 | view_size, 115 | text: include_str!("loads-of-unicode.txt").into(), 116 | font_size: 25.0, 117 | }) 118 | } 119 | 120 | fn window_event(&mut self, events: &ActiveEventLoop, event: WindowEvent) { 121 | let Self { 122 | text, font_size, .. 123 | } = self; 124 | 125 | match event { 126 | WindowEvent::CloseRequested => events.exit(), 127 | WindowEvent::KeyboardInput { 128 | event: 129 | KeyEvent { 130 | logical_key, 131 | state: ElementState::Pressed, 132 | .. 133 | }, 134 | .. 135 | } => match logical_key { 136 | Key::Named(NamedKey::Escape) => events.exit(), 137 | Key::Named(NamedKey::Backspace) => { 138 | text.pop(); 139 | } 140 | key => { 141 | if let Some(str) = key.to_text() { 142 | text.push_str(str); 143 | } 144 | } 145 | }, 146 | WindowEvent::MouseWheel { 147 | delta: MouseScrollDelta::LineDelta(_, y), 148 | .. 149 | } => { 150 | // increase/decrease font size with mouse wheel 151 | if y > 0.0 { 152 | *font_size += (*font_size / 4.0).max(2.0) 153 | } else { 154 | *font_size *= 4.0 / 5.0 155 | }; 156 | *font_size = font_size.clamp(1.0, MAX_FONT_SIZE); 157 | } 158 | WindowEvent::RedrawRequested => self.draw(), 159 | _ => (), 160 | } 161 | } 162 | 163 | fn draw(&mut self) { 164 | let Self { 165 | window, 166 | gl_surface, 167 | gl_context, 168 | device, 169 | encoder, 170 | color_view, 171 | depth_view, 172 | glyph_brush, 173 | view_size, 174 | reporter, 175 | text, 176 | font_size, 177 | .. 178 | } = self; 179 | 180 | // handle resizes 181 | let w_size = window.inner_size(); 182 | if *view_size != w_size { 183 | window.resize_surface(gl_surface, gl_context); 184 | old_school_gfx_glutin_ext::resize_views(w_size, color_view, depth_view); 185 | *view_size = w_size; 186 | } 187 | 188 | encoder.clear(color_view, [0.02, 0.02, 0.02, 1.0]); 189 | 190 | let (width, height, ..) = color_view.get_dimensions(); 191 | let (width, height) = (f32::from(width), f32::from(height)); 192 | let scale = PxScale::from(*font_size * window.scale_factor() as f32); 193 | 194 | // The section is all the info needed for the glyph brush to render a 'section' of text. 195 | let section = Section::default() 196 | .add_text( 197 | Text::new(text) 198 | .with_scale(scale) 199 | .with_color([0.8, 0.8, 0.8, 1.0]), 200 | ) 201 | .with_bounds((width, height)) 202 | .with_layout(Layout::default().line_breaker(BuiltInLineBreaker::AnyCharLineBreaker)); 203 | 204 | // Adds a section & layout to the queue for the next call to `use_queue().draw(..)`, 205 | // this can be called multiple times for different sections that want to use the 206 | // same font and gpu cache. 207 | // This step computes the glyph positions, this is cached to avoid unnecessary 208 | // recalculation. 209 | glyph_brush.queue(§ion); 210 | 211 | // Finally once per frame you want to actually draw all the sections you've 212 | // submitted with `queue` calls. 213 | // 214 | // Note: Drawing in the case the text is unchanged from the previous frame 215 | // (a common case) is essentially free as the vertices are reused & gpu cache 216 | // updating interaction can be skipped. 217 | glyph_brush.use_queue().draw(encoder, color_view).unwrap(); 218 | 219 | encoder.flush(device); 220 | gl_surface.swap_buffers(gl_context).unwrap(); 221 | device.cleanup(); 222 | 223 | if let Some(rate) = reporter.increment_and_report() { 224 | window.set_title(&format!("{TITLE} - {rate:.0} FPS")); 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /gfx-glyph/examples/pre_positioned.rs: -------------------------------------------------------------------------------- 1 | //! `queue_pre_positioned` example 2 | mod init; 3 | 4 | use gfx::{ 5 | format::{Depth, Srgba8}, 6 | Device, 7 | }; 8 | use gfx_glyph::{ab_glyph::*, *}; 9 | use glutin::surface::GlSurface; 10 | use glutin_winit::GlWindow; 11 | use init::init_example; 12 | use std::{error::Error, time::Duration}; 13 | use winit::{ 14 | dpi::PhysicalSize, 15 | event::{KeyEvent, WindowEvent}, 16 | event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, 17 | keyboard::{Key, NamedKey}, 18 | }; 19 | 20 | const COLOR: [f32; 4] = [0.8, 0.8, 0.8, 1.0]; 21 | const TITLE: &str = "gfx_glyph example"; 22 | 23 | fn main() -> Result<(), Box> { 24 | init_example("pre_positioned"); 25 | Ok(EventLoop::new()?.run_app(&mut WinitApp(None))?) 26 | } 27 | 28 | struct WinitApp(Option); 29 | 30 | impl winit::application::ApplicationHandler for WinitApp { 31 | fn resumed(&mut self, events: &ActiveEventLoop) { 32 | events.set_control_flow(ControlFlow::Poll); 33 | *self = Self(Some(App::new(events).unwrap())); 34 | } 35 | 36 | fn window_event( 37 | &mut self, 38 | events: &ActiveEventLoop, 39 | _: winit::window::WindowId, 40 | event: WindowEvent, 41 | ) { 42 | if let Self(Some(app)) = self { 43 | app.window_event(events, event); 44 | } 45 | } 46 | 47 | fn about_to_wait(&mut self, _events: &ActiveEventLoop) { 48 | if let Self(Some(App { window, .. })) = self { 49 | window.request_redraw(); 50 | }; 51 | } 52 | } 53 | 54 | struct App { 55 | device: gfx_device_gl::Device, 56 | encoder: gfx::Encoder, 57 | color_view: gfx::handle::RenderTargetView, 58 | depth_view: gfx::handle::DepthStencilView, 59 | glyph_brush: GlyphBrush>, 60 | view_size: PhysicalSize, 61 | interval: spin_sleep_util::Interval, 62 | reporter: spin_sleep_util::RateReporter, 63 | glyphs: Vec, 64 | width: f32, 65 | height: f32, 66 | gl_surface: glutin::surface::Surface, 67 | gl_context: glutin::context::PossiblyCurrentContext, 68 | window: winit::window::Window, 69 | } 70 | 71 | impl App { 72 | fn new(events: &ActiveEventLoop) -> Result> { 73 | let window_attrs = winit::window::Window::default_attributes() 74 | .with_title(TITLE) 75 | .with_inner_size(winit::dpi::PhysicalSize::new(1024, 576)); 76 | 77 | let old_school_gfx_glutin_ext::Init { 78 | window, 79 | gl_surface, 80 | gl_context, 81 | device, 82 | mut factory, 83 | color_view, 84 | depth_view, 85 | .. 86 | } = old_school_gfx_glutin_ext::window_builder(events, window_attrs) 87 | .build::()?; 88 | 89 | let font = FontRef::try_from_slice(include_bytes!("../../fonts/OpenSans-Light.ttf"))?; 90 | let glyph_brush = gfx_glyph::GlyphBrushBuilder::using_font(font.clone()) 91 | .initial_cache_size((1024, 1024)) 92 | .build(factory.clone()); 93 | 94 | let encoder: gfx::Encoder<_, _> = factory.create_command_buffer().into(); 95 | 96 | let view_size = window.inner_size(); 97 | let (width, height, ..) = color_view.get_dimensions(); 98 | let (width, height) = (f32::from(width), f32::from(height)); 99 | 100 | let glyphs = gfx_glyph::Layout::default().calculate_glyphs( 101 | &[font], 102 | &gfx_glyph::SectionGeometry { 103 | screen_position: (0.0, 0.0), 104 | bounds: (width, height), 105 | }, 106 | &[gfx_glyph::SectionText { 107 | text: include_str!("lipsum.txt"), 108 | scale: PxScale::from(30.0), 109 | font_id: FontId(0), 110 | }], 111 | ); 112 | 113 | Ok(Self { 114 | window, 115 | gl_surface, 116 | gl_context, 117 | device, 118 | encoder, 119 | color_view, 120 | depth_view, 121 | glyph_brush, 122 | interval: spin_sleep_util::interval(Duration::from_secs(1) / 250), 123 | reporter: spin_sleep_util::RateReporter::new(Duration::from_secs(1)), 124 | view_size, 125 | glyphs, 126 | width, 127 | height, 128 | }) 129 | } 130 | 131 | fn window_event(&mut self, events: &ActiveEventLoop, event: WindowEvent) { 132 | let Self { 133 | window, 134 | gl_surface, 135 | gl_context, 136 | device, 137 | encoder, 138 | color_view, 139 | depth_view, 140 | glyph_brush, 141 | view_size, 142 | interval, 143 | reporter, 144 | glyphs, 145 | width, 146 | height, 147 | } = self; 148 | 149 | match event { 150 | WindowEvent::CloseRequested 151 | | WindowEvent::KeyboardInput { 152 | event: 153 | KeyEvent { 154 | logical_key: Key::Named(NamedKey::Escape), 155 | .. 156 | }, 157 | .. 158 | } => events.exit(), 159 | WindowEvent::RedrawRequested => { 160 | // handle resizes 161 | let w_size = window.inner_size(); 162 | if *view_size != w_size { 163 | window.resize_surface(gl_surface, gl_context); 164 | old_school_gfx_glutin_ext::resize_views(w_size, color_view, depth_view); 165 | *view_size = w_size; 166 | } 167 | 168 | encoder.clear(color_view, [0.02, 0.02, 0.02, 1.0]); 169 | 170 | glyph_brush.queue_pre_positioned( 171 | glyphs.clone(), 172 | vec![Extra { 173 | color: COLOR, 174 | z: 0.0, 175 | }], 176 | Rect { 177 | min: point(0.0, 0.0), 178 | max: point(*width, *height), 179 | }, 180 | ); 181 | 182 | glyph_brush.use_queue().draw(encoder, color_view).unwrap(); 183 | 184 | encoder.flush(device); 185 | gl_surface.swap_buffers(gl_context).unwrap(); 186 | device.cleanup(); 187 | 188 | if let Some(rate) = reporter.increment_and_report() { 189 | window.set_title(&format!("{TITLE} - {rate:.0} FPS")); 190 | } 191 | interval.tick(); 192 | } 193 | _ => (), 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /gfx-glyph/examples/varied.rs: -------------------------------------------------------------------------------- 1 | //! An example of rendering multiple fonts, sizes & colours within a single layout 2 | //! Controls 3 | //! 4 | //! * Resize window to adjust layout 5 | mod init; 6 | 7 | use gfx::{ 8 | format::{Depth, Srgba8}, 9 | Device, 10 | }; 11 | use gfx_glyph::{ab_glyph::*, *}; 12 | use glutin::surface::GlSurface; 13 | use glutin_winit::GlWindow; 14 | use init::init_example; 15 | use std::{error::Error, time::Duration}; 16 | use winit::{ 17 | dpi::PhysicalSize, 18 | event::{KeyEvent, WindowEvent}, 19 | event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, 20 | keyboard::{Key, NamedKey}, 21 | }; 22 | 23 | const TITLE: &str = "gfx_glyph example - resize to see multi-text layout"; 24 | 25 | fn main() -> Result<(), Box> { 26 | init_example("varied"); 27 | Ok(EventLoop::new()?.run_app(&mut WinitApp(None))?) 28 | } 29 | 30 | struct WinitApp(Option); 31 | 32 | impl winit::application::ApplicationHandler for WinitApp { 33 | fn resumed(&mut self, events: &ActiveEventLoop) { 34 | events.set_control_flow(ControlFlow::Poll); 35 | *self = Self(Some(App::new(events).unwrap())); 36 | } 37 | 38 | fn window_event( 39 | &mut self, 40 | events: &ActiveEventLoop, 41 | _: winit::window::WindowId, 42 | event: WindowEvent, 43 | ) { 44 | if let Self(Some(app)) = self { 45 | app.window_event(events, event); 46 | } 47 | } 48 | 49 | fn about_to_wait(&mut self, _events: &ActiveEventLoop) { 50 | if let Self(Some(App { window, .. })) = self { 51 | window.request_redraw(); 52 | }; 53 | } 54 | } 55 | 56 | struct App { 57 | device: gfx_device_gl::Device, 58 | encoder: gfx::Encoder, 59 | color_view: gfx::handle::RenderTargetView, 60 | depth_view: gfx::handle::DepthStencilView, 61 | glyph_brush: GlyphBrush, 62 | view_size: PhysicalSize, 63 | interval: spin_sleep_util::Interval, 64 | reporter: spin_sleep_util::RateReporter, 65 | sans_font: FontId, 66 | italic_font: FontId, 67 | serif_font: FontId, 68 | mono_font: FontId, 69 | gl_surface: glutin::surface::Surface, 70 | gl_context: glutin::context::PossiblyCurrentContext, 71 | window: winit::window::Window, 72 | } 73 | 74 | impl App { 75 | fn new(events: &ActiveEventLoop) -> Result> { 76 | let window_attrs = winit::window::Window::default_attributes() 77 | .with_title(TITLE) 78 | .with_inner_size(winit::dpi::PhysicalSize::new(1024, 576)); 79 | 80 | let old_school_gfx_glutin_ext::Init { 81 | window, 82 | gl_surface, 83 | gl_context, 84 | device, 85 | mut factory, 86 | color_view, 87 | depth_view, 88 | .. 89 | } = old_school_gfx_glutin_ext::window_builder(events, window_attrs) 90 | .build::()?; 91 | 92 | let font_0 = FontArc::try_from_slice(include_bytes!("../../fonts/DejaVuSans.ttf"))?; 93 | 94 | let mut builder = GlyphBrushBuilder::using_font(font_0).initial_cache_size((512, 512)); 95 | let sans_font = FontId::default(); 96 | 97 | let italic_font = builder.add_font(FontArc::try_from_slice(include_bytes!( 98 | "../../fonts/OpenSans-Italic.ttf" 99 | ))?); 100 | let serif_font = builder.add_font(FontArc::try_from_slice(include_bytes!( 101 | "../../fonts/GaramondNo8-Reg.ttf" 102 | ))?); 103 | let mono_font = builder.add_font(FontArc::try_from_slice(include_bytes!( 104 | "../../fonts/DejaVuSansMono.ttf" 105 | ))?); 106 | 107 | let glyph_brush = builder.build(factory.clone()); 108 | let encoder: gfx::Encoder<_, _> = factory.create_command_buffer().into(); 109 | let view_size = window.inner_size(); 110 | 111 | Ok(Self { 112 | window, 113 | gl_surface, 114 | gl_context, 115 | device, 116 | encoder, 117 | color_view, 118 | depth_view, 119 | glyph_brush, 120 | interval: spin_sleep_util::interval(Duration::from_secs(1) / 250), 121 | reporter: spin_sleep_util::RateReporter::new(Duration::from_secs(1)), 122 | view_size, 123 | sans_font, 124 | italic_font, 125 | serif_font, 126 | mono_font, 127 | }) 128 | } 129 | 130 | fn window_event(&mut self, events: &ActiveEventLoop, event: WindowEvent) { 131 | let Self { 132 | window, 133 | gl_surface, 134 | gl_context, 135 | device, 136 | encoder, 137 | color_view, 138 | depth_view, 139 | glyph_brush, 140 | view_size, 141 | interval, 142 | reporter, 143 | sans_font, 144 | italic_font, 145 | serif_font, 146 | mono_font, 147 | } = self; 148 | 149 | match event { 150 | WindowEvent::CloseRequested 151 | | WindowEvent::KeyboardInput { 152 | event: 153 | KeyEvent { 154 | logical_key: Key::Named(NamedKey::Escape), 155 | .. 156 | }, 157 | .. 158 | } => events.exit(), 159 | WindowEvent::RedrawRequested => { 160 | // handle resizes 161 | let w_size = window.inner_size(); 162 | if *view_size != w_size { 163 | window.resize_surface(gl_surface, gl_context); 164 | old_school_gfx_glutin_ext::resize_views(w_size, color_view, depth_view); 165 | *view_size = w_size; 166 | } 167 | 168 | encoder.clear(color_view, [0.02, 0.02, 0.02, 1.0]); 169 | 170 | let (width, height, ..) = color_view.get_dimensions(); 171 | let (width, height) = (f32::from(width), f32::from(height)); 172 | 173 | glyph_brush.queue(Section { 174 | screen_position: (0.0, height / 2.0), 175 | bounds: (width * 0.49, height), 176 | text: vec![ 177 | Text { 178 | text: "Lorem ipsum dolor sit amet, ferri simul omittantur eam eu, ", 179 | scale: PxScale::from(45.0), 180 | font_id: *sans_font, 181 | extra: Extra { 182 | color: [0.9, 0.3, 0.3, 1.0], 183 | z: 0.0, 184 | }, 185 | }, 186 | Text { 187 | text: "dolorem", 188 | scale: PxScale::from(150.0), 189 | font_id: *serif_font, 190 | extra: Extra { 191 | color: [0.3, 0.9, 0.3, 1.0], 192 | z: 0.0, 193 | }, 194 | }, 195 | Text { 196 | text: " Iriure vocibus est te, natum delicata dignissim pri ea.", 197 | scale: PxScale::from(25.0), 198 | font_id: *sans_font, 199 | extra: Extra { 200 | color: [0.3, 0.3, 0.9, 1.0], 201 | z: 0.0, 202 | }, 203 | }, 204 | ], 205 | layout: Layout::default().v_align(VerticalAlign::Center), 206 | }); 207 | 208 | glyph_brush.queue(Section { 209 | screen_position: (width, height / 2.0), 210 | bounds: (width * 0.49, height), 211 | text: vec![ 212 | Text { 213 | text: "foo += bar;", 214 | scale: PxScale::from(45.0), 215 | font_id: *mono_font, 216 | extra: Extra { 217 | color: [0.3, 0.3, 0.9, 1.0], 218 | z: 0.0, 219 | }, 220 | }, 221 | Text { 222 | text: " eruditi habemus qualisque eam an. No atqui apeirian phaedrum pri ex, hinc omnes sapientem. ", 223 | scale: PxScale::from(30.0), 224 | font_id: *italic_font, 225 | extra: Extra { 226 | color: [0.9, 0.3, 0.3, 1.0], 227 | z: 0.0, 228 | }, 229 | }, 230 | Text { 231 | text: "Eu facilisi maluisset eos.", 232 | scale: PxScale::from(55.0), 233 | font_id: *sans_font, 234 | extra: Extra { 235 | color: [0.3, 0.9, 0.3, 1.0], 236 | z: 0.0, 237 | }, 238 | }, 239 | Text { 240 | text: " ius nullam impetus. ", 241 | scale: PxScale { x: 25.0, y: 45.0 }, 242 | font_id: *serif_font, 243 | extra: Extra { 244 | color: [0.9, 0.9, 0.3, 1.0], 245 | z: 0.0, 246 | }, 247 | }, 248 | Text { 249 | text: "Ut quo elitr viderer constituam, pro omnesque forensibus at. Timeam scaevola mediocrem ut pri, te pro congue delicatissimi. Mei wisi nostro imperdiet ea, ridens salutatus per no, ut viris partem disputationi sit. Exerci eripuit referrentur vix at, sale mediocrem repudiare per te, modus admodum an eam. No vocent indoctum vis, ne quodsi patrioque vix. Vocent labores omittam et usu.", 250 | scale: PxScale::from(22.0), 251 | font_id: *italic_font, 252 | extra: Extra { 253 | color: [0.8, 0.3, 0.5, 1.0], 254 | z: 0.0, 255 | }, 256 | }, 257 | ], 258 | layout: Layout::default().h_align(HorizontalAlign::Right).v_align(VerticalAlign::Center), 259 | }); 260 | 261 | glyph_brush.use_queue().draw(encoder, color_view).unwrap(); 262 | 263 | encoder.flush(device); 264 | gl_surface.swap_buffers(gl_context).unwrap(); 265 | device.cleanup(); 266 | 267 | if let Some(rate) = reporter.increment_and_report() { 268 | window.set_title(&format!("{TITLE} - {rate:.0} FPS")); 269 | } 270 | interval.tick(); 271 | } 272 | _ => (), 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /gfx-glyph/src/builder.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use glyph_brush::delegate_glyph_brush_builder_fns; 3 | 4 | /// Builder for a [`GlyphBrush`](struct.GlyphBrush.html). 5 | /// 6 | /// # Example 7 | /// 8 | /// ```no_run 9 | /// use gfx_glyph::{ab_glyph::FontArc, GlyphBrushBuilder}; 10 | /// # let gfx_factory: gfx_device_gl::Factory = unimplemented!(); 11 | /// 12 | /// let dejavu = FontArc::try_from_slice(include_bytes!("../../fonts/DejaVuSans.ttf")).unwrap(); 13 | /// let mut glyph_brush = GlyphBrushBuilder::using_font(dejavu).build(gfx_factory.clone()); 14 | /// ``` 15 | pub struct GlyphBrushBuilder { 16 | inner: glyph_brush::GlyphBrushBuilder, 17 | depth_test: gfx::state::Depth, 18 | texture_filter_method: texture::FilterMethod, 19 | } 20 | 21 | impl GlyphBrushBuilder<()> { 22 | /// Specifies the default font used to render glyphs. 23 | /// Referenced with `FontId(0)`, which is default. 24 | #[inline] 25 | pub fn using_font(font_0: F) -> GlyphBrushBuilder { 26 | Self::using_fonts(vec![font_0]) 27 | } 28 | 29 | pub fn using_fonts>>(fonts: V) -> GlyphBrushBuilder { 30 | Self::without_fonts().replace_fonts(|_| fonts) 31 | } 32 | 33 | /// Create a new builder without any fonts. 34 | pub fn without_fonts() -> Self { 35 | GlyphBrushBuilder { 36 | inner: glyph_brush::GlyphBrushBuilder::without_fonts(), 37 | depth_test: gfx::preset::depth::LESS_EQUAL_WRITE, 38 | texture_filter_method: texture::FilterMethod::Bilinear, 39 | } 40 | } 41 | } 42 | 43 | impl GlyphBrushBuilder { 44 | /// Consume all builder fonts a replace with new fonts returned by the input function. 45 | /// 46 | /// Generally only makes sense when wanting to change fonts after calling 47 | /// [`GlyphBrush::to_builder`](struct.GlyphBrush.html#method.to_builder). Or on 48 | /// a `GlyphBrushBuilder<()>` built using `without_fonts()`. 49 | pub fn replace_fonts(self, font_fn: NF) -> GlyphBrushBuilder 50 | where 51 | V: Into>, 52 | NF: FnOnce(Vec) -> V, 53 | { 54 | let new_inner = self.inner.replace_fonts(font_fn); 55 | GlyphBrushBuilder { 56 | inner: new_inner, 57 | depth_test: self.depth_test, 58 | texture_filter_method: self.texture_filter_method, 59 | } 60 | } 61 | } 62 | 63 | impl GlyphBrushBuilder 64 | where 65 | F: Font, 66 | H: BuildHasher, 67 | { 68 | delegate_glyph_brush_builder_fns!(inner); 69 | 70 | /// Sets the depth test to use on the text section **z** values. 71 | /// 72 | /// Defaults to: *Only draw when the fragment's output depth is less than or equal to 73 | /// the current depth buffer value, and update the buffer*. 74 | /// 75 | /// # Example 76 | /// 77 | /// ```no_run 78 | /// # use gfx_glyph::GlyphBrushBuilder; 79 | /// # let some_font: gfx_glyph::ab_glyph::FontArc = unimplemented!(); 80 | /// GlyphBrushBuilder::using_font(some_font).depth_test(gfx::preset::depth::PASS_WRITE) 81 | /// // ... 82 | /// # ; 83 | /// ``` 84 | pub fn depth_test(mut self, depth_test: gfx::state::Depth) -> Self { 85 | self.depth_test = depth_test; 86 | self 87 | } 88 | 89 | /// Sets the texture filtering method. 90 | /// 91 | /// Defaults to `Bilinear` 92 | /// 93 | /// # Example 94 | /// ```no_run 95 | /// # use gfx_glyph::GlyphBrushBuilder; 96 | /// # let some_font: gfx_glyph::ab_glyph::FontArc = unimplemented!(); 97 | /// GlyphBrushBuilder::using_font(some_font) 98 | /// .texture_filter_method(gfx::texture::FilterMethod::Scale) 99 | /// // ... 100 | /// # ; 101 | /// ``` 102 | pub fn texture_filter_method(mut self, filter_method: texture::FilterMethod) -> Self { 103 | self.texture_filter_method = filter_method; 104 | self 105 | } 106 | 107 | /// Sets the section hasher. `GlyphBrush` cannot handle absolute section hash collisions 108 | /// so use a good hash algorithm. 109 | /// 110 | /// This hasher is used to distinguish sections, rather than for hashmap internal use. 111 | /// 112 | /// Defaults to [xxHash](https://docs.rs/twox-hash). 113 | /// 114 | /// # Example 115 | /// ```no_run 116 | /// # use gfx_glyph::GlyphBrushBuilder; 117 | /// # let some_font: gfx_glyph::ab_glyph::FontArc = unimplemented!(); 118 | /// # type SomeOtherBuildHasher = std::collections::hash_map::RandomState; 119 | /// GlyphBrushBuilder::using_font(some_font).section_hasher(SomeOtherBuildHasher::default()) 120 | /// // ... 121 | /// # ; 122 | /// ``` 123 | pub fn section_hasher(self, section_hasher: T) -> GlyphBrushBuilder { 124 | GlyphBrushBuilder { 125 | inner: self.inner.section_hasher(section_hasher), 126 | depth_test: self.depth_test, 127 | texture_filter_method: self.texture_filter_method, 128 | } 129 | } 130 | 131 | /// Builds a `GlyphBrush` using the input gfx factory 132 | pub fn build(self, mut factory: GF) -> GlyphBrush 133 | where 134 | R: gfx::Resources, 135 | GF: gfx::Factory, 136 | { 137 | let inner = self.inner.build(); 138 | let (cache_width, cache_height) = inner.texture_dimensions(); 139 | let font_cache_tex = create_texture(&mut factory, cache_width, cache_height).unwrap(); 140 | let program = factory 141 | .link_program( 142 | include_bytes!("shader/vert.glsl"), 143 | include_bytes!("shader/frag.glsl"), 144 | ) 145 | .unwrap(); 146 | 147 | GlyphBrush { 148 | font_cache_tex, 149 | texture_filter_method: self.texture_filter_method, 150 | glyph_brush: inner, 151 | 152 | factory, 153 | program, 154 | draw_cache: None, 155 | 156 | depth_test: self.depth_test, 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /gfx-glyph/src/draw_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | default_transform, GlyphBrush, RawAndFormat, RawDepthStencilView, RawRenderTargetView, 3 | }; 4 | use glyph_brush::ab_glyph::*; 5 | use std::{hash::BuildHasher, marker::PhantomData}; 6 | 7 | /// Short-lived builder for drawing glyphs, constructed from [`GlyphBrush::use_queue`](struct.GlyphBrush.html#method.use_queue). 8 | /// 9 | /// # Example 10 | /// 11 | /// ```no_run 12 | /// # fn main() -> Result<(), String> { 13 | /// # let glyph_brush: gfx_glyph::GlyphBrush = unimplemented!(); 14 | /// # let gfx_color: gfx_core::handle::RenderTargetView = unimplemented!(); 15 | /// # let gfx_depth: gfx_core::handle::DepthStencilView = unimplemented!(); 16 | /// # let factory: gfx_device_gl::Factory = unimplemented!(); 17 | /// # let gfx_encoder: gfx::Encoder<_, _> = factory.create_command_buffer().into(); 18 | /// 19 | /// glyph_brush 20 | /// .use_queue() 21 | /// .depth_target(&gfx_depth) 22 | /// .draw(&mut gfx_encoder, &gfx_color)?; 23 | /// # Ok(()) } 24 | /// ``` 25 | #[must_use] 26 | pub struct DrawBuilder<'a, F, R: gfx::Resources, GF: gfx::Factory, H, DV> { 27 | pub(crate) brush: &'a mut GlyphBrush, 28 | pub(crate) transform: Option<[[f32; 4]; 4]>, 29 | pub(crate) depth_target: Option<&'a DV>, 30 | } 31 | 32 | impl<'a, F, R, GF, H, DV> DrawBuilder<'a, F, R, GF, H, DV> 33 | where 34 | F: Font, 35 | R: gfx::Resources, 36 | GF: gfx::Factory, 37 | H: BuildHasher, 38 | { 39 | /// Use a custom position transform (e.g. a projection) replacing the [`default_transform`](fn.default_transform.html). 40 | /// 41 | /// # Example 42 | /// ```no_run 43 | /// # fn main() -> Result<(), String> { 44 | /// # let glyph_brush: gfx_glyph::GlyphBrush = unimplemented!(); 45 | /// # let gfx_color: gfx_core::handle::RenderTargetView = unimplemented!(); 46 | /// let projection = gfx_glyph::default_transform(&gfx_color); 47 | /// 48 | /// glyph_brush.use_queue().transform(projection) 49 | /// # ; 50 | /// # Ok(()) } 51 | /// ``` 52 | #[inline] 53 | pub fn transform>(mut self, transform: M) -> Self { 54 | self.transform = Some(transform.into()); 55 | self 56 | } 57 | 58 | /// Set a depth buffer target to perform depth testing against. 59 | /// 60 | /// # Example 61 | /// ```no_run 62 | /// # fn main() -> Result<(), String> { 63 | /// # let glyph_brush: gfx_glyph::GlyphBrush = unimplemented!(); 64 | /// # let gfx_depth: gfx_core::handle::DepthStencilView = unimplemented!(); 65 | /// glyph_brush.use_queue().depth_target(&gfx_depth) 66 | /// # ; 67 | /// # Ok(()) } 68 | /// ``` 69 | /// 70 | /// # Raw usage 71 | /// Can also be used with gfx raw depth views if necessary. The `Format` must also be provided. 72 | /// ```no_run 73 | /// # use gfx::format::{self, Formatted}; 74 | /// # use gfx::memory::Typed; 75 | /// # fn main() -> Result<(), String> { 76 | /// # let glyph_brush: gfx_glyph::GlyphBrush = unimplemented!(); 77 | /// # let gfx_depth: gfx_core::handle::DepthStencilView = unimplemented!(); 78 | /// # let raw_depth_view = gfx_depth.raw(); 79 | /// glyph_brush 80 | /// .use_queue() 81 | /// .depth_target(&(raw_depth_view, format::Depth::get_format())) 82 | /// # ; 83 | /// # Ok(()) } 84 | /// ``` 85 | #[inline] 86 | pub fn depth_target(self, depth: &'a D) -> DrawBuilder<'a, F, R, GF, H, D> { 87 | let Self { 88 | brush, transform, .. 89 | } = self; 90 | DrawBuilder { 91 | depth_target: Some(depth), 92 | brush, 93 | transform, 94 | } 95 | } 96 | } 97 | 98 | impl DrawBuilder<'_, F, R, GF, H, DV> 99 | where 100 | F: Font + Sync, 101 | R: gfx::Resources, 102 | GF: gfx::Factory, 103 | H: BuildHasher, 104 | DV: RawAndFormat>, 105 | { 106 | /// Draws all queued sections onto a render target. 107 | /// See [`queue`](struct.GlyphBrush.html#method.queue). 108 | /// 109 | /// Trims the cache, see [caching behaviour](#caching-behaviour). 110 | /// 111 | /// # Example 112 | /// ```no_run 113 | /// # fn main() -> Result<(), String> { 114 | /// # let glyph_brush: gfx_glyph::GlyphBrush = unimplemented!(); 115 | /// # let gfx_color: gfx_core::handle::RenderTargetView = unimplemented!(); 116 | /// # let factory: gfx_device_gl::Factory = unimplemented!(); 117 | /// # let gfx_encoder: gfx::Encoder<_, _> = factory.create_command_buffer().into(); 118 | /// glyph_brush.use_queue().draw(&mut gfx_encoder, &gfx_color)?; 119 | /// # Ok(()) } 120 | /// ``` 121 | /// 122 | /// # Raw usage 123 | /// Can also be used with gfx raw render views if necessary. The `Format` must also be provided. 124 | /// ```no_run 125 | /// # use gfx::format::{self, Formatted}; 126 | /// # use gfx::memory::Typed; 127 | /// # fn main() -> Result<(), String> { 128 | /// # let glyph_brush: gfx_glyph::GlyphBrush = unimplemented!(); 129 | /// # let gfx_color: gfx_core::handle::RenderTargetView = unimplemented!();; 130 | /// # let factory: gfx_device_gl::Factory = unimplemented!(); 131 | /// # let gfx_encoder: gfx::Encoder<_, _> = factory.create_command_buffer().into(); 132 | /// # let raw_render_view = gfx_color.raw(); 133 | /// glyph_brush.use_queue().draw( 134 | /// &mut gfx_encoder, 135 | /// &(raw_render_view, format::Srgba8::get_format()), 136 | /// )?; 137 | /// # Ok(()) } 138 | /// ``` 139 | #[inline] 140 | pub fn draw(self, encoder: &mut gfx::Encoder, target: &CV) -> Result<(), String> 141 | where 142 | C: gfx::CommandBuffer, 143 | CV: RawAndFormat>, 144 | { 145 | let Self { 146 | brush, 147 | transform, 148 | depth_target, 149 | } = self; 150 | let transform = transform.unwrap_or_else(|| default_transform(target)); 151 | brush.draw(transform, encoder, target, depth_target) 152 | } 153 | } 154 | 155 | struct NoDepth(PhantomData); 156 | impl RawAndFormat for NoDepth { 157 | type Raw = RawDepthStencilView; 158 | fn as_raw(&self) -> &Self::Raw { 159 | unreachable!() 160 | } 161 | fn format(&self) -> gfx::format::Format { 162 | unreachable!() 163 | } 164 | } 165 | 166 | impl DrawBuilder<'_, F, R, GF, H, ()> 167 | where 168 | F: Font + Sync, 169 | R: gfx::Resources, 170 | GF: gfx::Factory, 171 | H: BuildHasher, 172 | { 173 | #[inline] 174 | pub fn draw(self, encoder: &mut gfx::Encoder, target: &CV) -> Result<(), String> 175 | where 176 | C: gfx::CommandBuffer, 177 | CV: RawAndFormat>, 178 | { 179 | let Self { 180 | brush, transform, .. 181 | } = self; 182 | let transform = transform.unwrap_or_else(|| default_transform(target)); 183 | brush.draw::>(transform, encoder, target, None) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /gfx-glyph/src/pipe.rs: -------------------------------------------------------------------------------- 1 | use super::TexFormView; 2 | use gfx::{ 3 | self, 4 | format::{Format, Formatted}, 5 | handle::{DepthStencilView, RawDepthStencilView, RawRenderTargetView, RenderTargetView}, 6 | memory::Typed, 7 | pso::*, 8 | *, 9 | }; 10 | use gfx_core::pso; 11 | 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 13 | pub struct RawDepthTarget { 14 | active: bool, 15 | } 16 | 17 | impl DataLink<'_> for RawDepthTarget { 18 | type Init = Option<(format::Format, state::Depth)>; 19 | fn new() -> Self { 20 | RawDepthTarget { active: false } 21 | } 22 | fn is_active(&self) -> bool { 23 | self.active 24 | } 25 | fn link_depth_stencil(&mut self, init: &Self::Init) -> Option { 26 | self.active = init.is_some(); 27 | 28 | init.map(|(format, depth)| (format, depth.into())) 29 | } 30 | } 31 | 32 | impl DataBind for RawDepthTarget { 33 | type Data = Option>; 34 | 35 | fn bind_to( 36 | &self, 37 | out: &mut RawDataSet, 38 | data: &Self::Data, 39 | man: &mut handle::Manager, 40 | _: &mut AccessInfo, 41 | ) { 42 | if let Some(dsv) = data { 43 | out.pixel_targets.add_depth_stencil( 44 | man.ref_dsv(dsv), 45 | true, 46 | false, 47 | dsv.get_dimensions(), 48 | ); 49 | } 50 | } 51 | } 52 | 53 | gfx_defines! { 54 | vertex GlyphVertex { 55 | /// screen position 56 | left_top: [f32; 3] = "left_top", 57 | right_bottom: [f32; 2] = "right_bottom", 58 | /// texture position 59 | tex_left_top: [f32; 2] = "tex_left_top", 60 | tex_right_bottom: [f32; 2] = "tex_right_bottom", 61 | /// text color 62 | color: [f32; 4] = "color", 63 | } 64 | } 65 | 66 | gfx_pipeline_base!( glyph_pipe { 67 | vbuf: InstanceBuffer, 68 | font_tex: gfx::pso::resource::TextureSampler, 69 | transform: Global<[[f32; 4]; 4]>, 70 | out: RawRenderTarget, 71 | out_depth: RawDepthTarget, 72 | }); 73 | impl Eq for glyph_pipe::Meta {} 74 | 75 | impl glyph_pipe::Init<'_> { 76 | pub fn new( 77 | color_format: format::Format, 78 | depth_format: Option, 79 | depth_test: state::Depth, 80 | ) -> Self { 81 | glyph_pipe::Init { 82 | vbuf: (), 83 | font_tex: "font_tex", 84 | transform: "transform", 85 | out: ( 86 | "Target0", 87 | color_format, 88 | state::ColorMask::all(), 89 | Some(preset::blend::ALPHA), 90 | ), 91 | out_depth: depth_format.map(|d| (d, depth_test)), 92 | } 93 | } 94 | } 95 | 96 | /// A view that can produce an inner "raw" view & a `Format`. 97 | pub trait RawAndFormat { 98 | type Raw; 99 | fn as_raw(&self) -> &Self::Raw; 100 | fn format(&self) -> Format; 101 | } 102 | 103 | impl RawAndFormat for RenderTargetView { 104 | type Raw = RawRenderTargetView; 105 | #[inline] 106 | fn as_raw(&self) -> &Self::Raw { 107 | self.raw() 108 | } 109 | #[inline] 110 | fn format(&self) -> Format { 111 | T::get_format() 112 | } 113 | } 114 | 115 | impl RawAndFormat for DepthStencilView { 116 | type Raw = RawDepthStencilView; 117 | #[inline] 118 | fn as_raw(&self) -> &Self::Raw { 119 | self.raw() 120 | } 121 | #[inline] 122 | fn format(&self) -> Format { 123 | T::get_format() 124 | } 125 | } 126 | 127 | impl RawAndFormat for (&R, Format) { 128 | type Raw = R; 129 | #[inline] 130 | fn as_raw(&self) -> &Self::Raw { 131 | self.0 132 | } 133 | #[inline] 134 | fn format(&self) -> Format { 135 | self.1 136 | } 137 | } 138 | 139 | pub trait IntoDimensions { 140 | /// Returns (width, height) 141 | fn into_dimensions(self) -> (f32, f32); 142 | } 143 | 144 | impl IntoDimensions for &CV 145 | where 146 | R: Resources, 147 | CV: RawAndFormat>, 148 | { 149 | #[inline] 150 | fn into_dimensions(self) -> (f32, f32) { 151 | let (width, height, ..) = self.as_raw().get_dimensions(); 152 | (f32::from(width), f32::from(height)) 153 | } 154 | } 155 | 156 | impl> IntoDimensions for [T; 2] { 157 | #[inline] 158 | fn into_dimensions(self) -> (f32, f32) { 159 | let [w, h] = self; 160 | (w.into(), h.into()) 161 | } 162 | } 163 | 164 | impl> IntoDimensions for (T, T) { 165 | #[inline] 166 | fn into_dimensions(self) -> (f32, f32) { 167 | (self.0.into(), self.1.into()) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /gfx-glyph/src/shader/frag.glsl: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | uniform sampler2D font_tex; 4 | 5 | in vec2 f_tex_pos; 6 | in vec4 f_color; 7 | 8 | out vec4 Target0; 9 | 10 | void main() { 11 | float alpha = texture(font_tex, f_tex_pos).r; 12 | if (alpha <= 0.0) { 13 | discard; 14 | } 15 | Target0 = f_color * vec4(1.0, 1.0, 1.0, alpha); 16 | } 17 | -------------------------------------------------------------------------------- /gfx-glyph/src/shader/vert.glsl: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | const mat4 INVERT_Y_AXIS = mat4( 4 | vec4(1.0, 0.0, 0.0, 0.0), 5 | vec4(0.0, -1.0, 0.0, 0.0), 6 | vec4(0.0, 0.0, 1.0, 0.0), 7 | vec4(0.0, 0.0, 0.0, 1.0) 8 | ); 9 | 10 | uniform mat4 transform; 11 | 12 | in vec3 left_top; 13 | in vec2 right_bottom; 14 | in vec2 tex_left_top; 15 | in vec2 tex_right_bottom; 16 | in vec4 color; 17 | 18 | out vec2 f_tex_pos; 19 | out vec4 f_color; 20 | 21 | // generate positional data based on vertex ID 22 | void main() { 23 | vec2 pos = vec2(0.0); 24 | float left = left_top.x; 25 | float right = right_bottom.x; 26 | float top = left_top.y; 27 | float bottom = right_bottom.y; 28 | 29 | switch (gl_VertexID) { 30 | case 0: 31 | pos = vec2(left, top); 32 | f_tex_pos = tex_left_top; 33 | break; 34 | case 1: 35 | pos = vec2(right, top); 36 | f_tex_pos = vec2(tex_right_bottom.x, tex_left_top.y); 37 | break; 38 | case 2: 39 | pos = vec2(left, bottom); 40 | f_tex_pos = vec2(tex_left_top.x, tex_right_bottom.y); 41 | break; 42 | case 3: 43 | pos = vec2(right, bottom); 44 | f_tex_pos = tex_right_bottom; 45 | break; 46 | } 47 | 48 | f_color = color; 49 | gl_Position = INVERT_Y_AXIS * transform * vec4(pos, left_top.z, 1.0); 50 | } 51 | -------------------------------------------------------------------------------- /gfx-glyph/src/trace.rs: -------------------------------------------------------------------------------- 1 | /// Returns a `String` backtrace from just after the `gfx_glyph` bits outwards 2 | macro_rules! outer_backtrace { 3 | () => {{ 4 | use backtrace; 5 | use std::fmt::Write; 6 | 7 | let mut on_lib = false; 8 | let mut outside_lib = false; 9 | let mut trace = String::new(); 10 | backtrace::trace(|frame| { 11 | let ip = frame.ip(); 12 | backtrace::resolve(ip, |symbol| { 13 | if let Some(name) = symbol.name() { 14 | let name = format!("{}", name); 15 | if !outside_lib && !on_lib { 16 | if name.contains("gfx_glyph") { 17 | on_lib = true; 18 | } 19 | } else if on_lib { 20 | if !name.contains("gfx_glyph") { 21 | outside_lib = true; 22 | } 23 | } 24 | 25 | if outside_lib { 26 | if !trace.is_empty() { 27 | writeln!(trace).unwrap(); 28 | } 29 | write!(trace, " - {}", name).unwrap(); 30 | if let (Some(file), Some(lineno)) = (symbol.filename(), symbol.lineno()) { 31 | write!(trace, " at {:?}:{}", file, lineno).unwrap(); 32 | } 33 | } 34 | } 35 | }); 36 | true 37 | }); 38 | 39 | trace 40 | }}; 41 | } 42 | -------------------------------------------------------------------------------- /glyph-brush/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.7.12 2 | * Update _ordered-float_ to 5. 3 | 4 | # 0.7.11 5 | * Update _twox-hash_ to 2. 6 | 7 | # 0.7.10 8 | * Fix conversions from OwnedSection -> Section with non-default extra type. 9 | 10 | # 0.7.9 11 | * Add `GlyphCruncher::glyphs` doc example of using `section_index` & `byte_index`. 12 | * Update _rustc-hash_ to 2. 13 | 14 | # v0.7.8 15 | * Update _ordered-float_ to 4. 16 | 17 | # v0.7.7 18 | * Allow `Text::new` to work with any `X` type. **This may break usage**, however it will hopefully be non-breaking in practice as the compiler should always be able to infer this. 19 | * Add `Section::builder` for more flexible `X`/"extra" type usage than `Section::default` & for owned text too. 20 | * Add more flexible `X` type usage to `GlyphBrush::keep_cached`. 21 | * Add `Section::from(text)` & `Section::from(vec![text])` conversions. 22 | * Update `GlyphCruncher::glyphs`, `GlyphCruncher::glyph_bounds` docs. 23 | 24 | # v0.7.6 25 | * Removed _log_ dependency. 26 | 27 | # 0.7.5 28 | * Implement `Eq` for `BrushError`. 29 | 30 | # 0.7.4 31 | * Update _ordered-float_ to 3. 32 | 33 | # 0.7.3 34 | * Add documentation of `GlyphBrush` generic types & workarounds when `.build()` type inference fails. 35 | * wasm: Fix _twox-hash_ compilation. 36 | 37 | # 0.7.2 38 | * Add `GlyphBrushBuilder::multithread` to allow setting the (default on) draw-cache multithreading. 39 | 40 | # 0.7.1 41 | * Update _ordered-float_ to 2. 42 | 43 | # 0.7 44 | * **OpenType (.otf) fonts are now supported** in addition to .ttf fonts. They're just as fast as .ttf fonts too. 45 | * Rework crate switching from _rusttype_ to _ab_glyph_. ab_glyph is re-exported to `glyph_brush::ab_glyph`, 46 | rusttype types are gone, e.g. all usage of `rusttype::Scale` is replaced with `ab_glyph::PxScale`. 47 | * New `Section` struct redesign replaces `VariedSection` & the old `Section` supporting generic extra data & builder syntax. 48 | ```rust 49 | // 0.7: Optional builder style, one API for simple & varied sections 50 | Section::default().add_text(Text::new("Hello").with_scale(25.0)) 51 | ``` 52 | ```rust 53 | // 0.6 54 | Section { text: "Hello", scale: Scale::uniform(25.0), ..<_>::default() } 55 | ``` 56 | * Section `color` & `z` are now part of an `Extra` struct which is the default `extra` type. This allows previous usage 57 | to continue but also downstream users to use different `Extra` data. 58 | This data is provided to the vertex generator function. 59 | * To aid with refactoring from the previous version some equivalent versions of legacy structs are available 60 | `legacy::{VariedSection, Section, SectionText}`. So in some cases you can just slap `legacy::` in front of the old 61 | code and fix some `PxScale` usages and away you go. I'll deprecate and eventually remove this module. 62 | ```rust 63 | // 0.7 64 | legacy::Section { text: "Hello", scale: PxScale::from(25.0), ..<_>::default() } 65 | ``` 66 | * `pixel_bounds` has been removed, use `glyph_bounds` instead. Pixel bound info is not available for OpenType glyphs 67 | without calculating outlines. I've found it's almost always better to use `glyph_bounds` instead anyway, 68 | if not please open an issue with your use case. 69 | * Rename `gpu_cache_*` methods to `draw_cache_*`. Also note, overriding the default _position_tolerance_ may be less necessary as the new 70 | draw cache features subpixel position error correction. 71 | ```rust 72 | // 0.7 73 | GlyphBrushBuilder::using_font(font).draw_cache_position_tolerance(0.1).build() 74 | ``` 75 | ```rust 76 | // 0.6 77 | GlyphBrushBuilder::using_font(font).gpu_cache_position_tolerance(0.1).build() 78 | ``` 79 | * Rename `cache_glyph_drawing` to `cache_redraws` for clarity. 80 | * New crate _glyph_brush_draw_cache_ takes rusttype's `gpu_cache` module into the ab_glyph world 81 | and starts to improve upon it. 82 | * New _glyph_brush_layout_ now providers `section_index` & `byte_index` for all laid out glyphs. It no longer 83 | relies on any pixel bounding box info, which isn't fast to query with .otf fonts. This also means invisible glyphs, like ' ', are now generally included and hence will be returned by `GlyphCruncher::glyphs`. 84 | * Add `GlyphBrush::is_draw_cached` which can be used to tell if a given queued & processed glyph is visible or not. 85 | * It's faster. **~1.07-1.64x** faster than `0.6` (particularly in the worst case full-layout performance). 86 | Rasterization is also **~2-7x** faster using _ab_glyph_rasterizer_. 87 | 88 | # 0.6.3 89 | * Fix section color & alpha frame to frame changes to be incorrectly optimised as alpha only changes. 90 | 91 | # 0.6.2 92 | * Add `GlyphBrushBuilder::without_fonts()` for creating a brush without any fonts. 93 | 94 | # 0.6.1 95 | * Require _glyph_brush_layout_ 0.1.8 to help ensure _rusttype_ dependency convergence. 96 | 97 | # 0.6 98 | * `GlyphBrushBuilder` removed `initial_cache_size`, `gpu_cache_scale_tolerance`, `gpu_cache_position_tolerance`, `gpu_cache_align_4x4` public fields replaced by `gpu_cache_builder` field. This change allows the following changes. 99 | * Add `GlyphBrush::to_builder` method to construct `GlyphBrushBuilder`s from `GlyphBrush`. 100 | * Add `GlyphBrushBuilder::replace_fonts`, `GlyphBrushBuilder::rebuild` methods. Along with `to_builder` these may be used to rebuild a `GlyphBrush` with different fonts more conveniently. 101 | * Replace `hashbrown` with `rustc-hash` + `std::collections` these are the same in 1.36. 102 | * Update _rusttype_ -> `0.8`. _Compatible with rusttype `0.6.5` & `0.7.9`._ 103 | 104 | # 0.5.4 105 | * Semver trick re-export glyph_brush `0.6` without `GlyphBrushBuilder`. 106 | * Export `GlyphBrushBuilderNext` returned by `GlyphBrush::to_builder`. 107 | 108 | # 0.5.3 109 | * Fix `queue_pre_positioned` cache check for position & scale changes. 110 | 111 | # 0.5.2 112 | * Removed screen dimensions from `process_queued` arguments. `to_vertex` function arguments also no longer include screen dimensions. Vertices should now be given pixel coordinates and use an appropriate projection matrix as a transform. 113 |

This approach simplifies glyph_brush code & allows the vertex cache to survive screen resolution changes. It also makes pre-projection custom transforms much easier to use. See usage changes in the opengl example & gfx_glyph. 114 | * Add `GlyphCruncher::glyph_bounds` & `glyph_bounds_custom_layout` functions. These return section bounding boxes in terms of the font & glyph's size metrics, which can be more useful than the pixel rendering bounds provided by `pixel_bounds`. 115 | * Add `GlyphBrushBuilder::gpu_cache_align_4x4` for rusttype gpu_cache `align_4x4` option. `delegate_glyph_brush_builder_fns!` includes this for downstream builders. 116 | * Disallow `GlyphBrushBuilder` direct construction. 117 | * Update hashbrown -> `0.5`. 118 | 119 | # 0.5, 0.5.1 120 | _yanked_ 121 | 122 | # 0.4.3 123 | * Fix cached vertices erroneously remaining valid after screen dimension change. 124 | * Update hashbrown -> `0.3`. 125 | 126 | # 0.4.2 127 | * Wasm32: Avoid using random state in the default hasher. 128 | 129 | # 0.4.1 130 | * Change default section hasher to xxHash as seahash has been shown to collide easily in 32bit environments. 131 | 132 | # 0.4 133 | * Use queue call counting & fine grained hashing to match up previous calls with current calls figuring out what has changed allowing optimised use of `recalculate_glyphs` for fast layouts. 134 | - Compute if just geometry (ie section position) has changed -> `GlyphChange::Geometry`. 135 | - Compute if just color has changed -> `GlyphChange::Color`. 136 | - Compute if just alpha has changed -> `GlyphChange::Alpha`. 137 | 138 | Provides much faster re-layout performance in these common change scenarios. 139 | * `GlyphBrush` now generates & caches vertices avoiding regeneration of individual unchanged sections, when another section change forces regeneration of the complete vertex array. The user vertex type `V` is now in the struct signature. 140 | ```rust 141 | pub struct DownstreamGlyphBrush<'font, H = DefaultSectionHasher> { 142 | // previously: glyph_brush::GlyphBrush<'font, H>, 143 | inner: glyph_brush::GlyphBrush<'font, DownstreamGlyphVertex, H>, 144 | ... 145 | } 146 | ``` 147 | 148 | These changes result in a big performance improvement for changes to sections amongst other unchanging sections, which is a fairly common thing to want to do. Fully cached (everything unchanging) & worst-case (everything changing/new) are not significantly affected. 149 | 150 | # 0.3 151 | * Add `GlyphCruncher::fonts()` to common trait, hoisted from direct implementations. Add something like the following if you implement `GlyphCruncher`. 152 | ```rust 153 | impl GlyphCruncher for Foo { 154 | // new 155 | #[inline] 156 | fn fonts(&self) -> &[Font<'font>] { 157 | self.glyph_brush.fonts() 158 | } 159 | } 160 | ``` 161 | * Fix 2-draw style causing texture cache thrashing. _Probably a very rare bug_. 162 | * Require log `0.4.4` to fix compile issue with older versions. 163 | * Improve documentation. 164 | * Implement `PartialEq` for `*Section`s 165 | * Implement `Clone`, `PartialEq` for `BrushError` 166 | * Implement `Debug`, `Clone` for other public things. 167 | * Add `GlyphBrush::queue_pre_positioned` for fully custom glyph positioning. 168 | 169 | # 0.2.4 170 | * Add `GlyphBrush::keep_cached`. 171 | 172 | # 0.2.3 173 | * Use hashbrown map & sets improves some benchmarks by 1-4%. 174 | 175 | # 0.2.2 176 | * Add `GlyphCalculator::fonts` & `GlyphCalculatorGuard::fonts` methods. 177 | 178 | # 0.2.1 179 | * Fix on-off single section cache clearing. 180 | * Fix double initial draw. 181 | 182 | # 0.2 183 | * Add public `DefaultSectionHasher`. 184 | * Add `GlyphBrush::texture_dimensions`. 185 | * Remove leaked public `GlyphedSection`. 186 | * Remove `current` from `TextureTooSmall`, replaced by using `texture_dimensions()`. 187 | * Improve some documentation using gfx-glyph specific terminology. 188 | * Fix `pixel_bounds` returning `None` for some infinite bound + non-default layout cases. 189 | * Optimise calculate_glyph_cache trimming using intermediate fx-hashing. 190 | ``` 191 | name control ns/iter change ns/iter diff ns/iter diff % speedup 192 | render_100_small_sections_fully 25,412 20,844 -4,568 -17.98% x 1.22 193 | ``` 194 | * Add example usage using gl-rs. 195 | 196 | # 0.1 197 | * Initial release. 198 | -------------------------------------------------------------------------------- /glyph-brush/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "glyph_brush" 3 | version = "0.7.12" 4 | authors = ["Alex Butler "] 5 | edition = "2021" 6 | description = "Fast cached text render library using ab_glyph" 7 | repository = "https://github.com/alexheretic/glyph-brush" 8 | keywords = ["font", "ttf", "truetype", "text"] 9 | license = "Apache-2.0" 10 | readme = "README.md" 11 | 12 | [dependencies] 13 | glyph_brush_draw_cache = { version = "0.1.1", path = "../draw-cache" } 14 | glyph_brush_layout = { version = "0.2.3", path = "../layout" } 15 | ordered-float = "5" 16 | rustc-hash = "2" 17 | twox-hash = { version = "2", default-features = false, features = ["xxhash64"] } 18 | 19 | # enable twox-hash rand everywhere except wasm 20 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash] 21 | version = "2" 22 | default-features = false 23 | features = ["random"] 24 | 25 | [dev-dependencies] 26 | approx = "0.5" 27 | criterion = "0.6" 28 | env_logger = { version = "0.11", default-features = false } 29 | gl = "0.14" 30 | glutin = "0.32" 31 | glutin-winit = "0.5" 32 | raw-window-handle = "0.6" 33 | spin_sleep_util = "0.1" 34 | winit = "0.30" 35 | 36 | [[bench]] 37 | name = "glyph_brush" 38 | harness = false 39 | -------------------------------------------------------------------------------- /glyph-brush/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /glyph-brush/README.md: -------------------------------------------------------------------------------- 1 | glyph_brush 2 | [![crates.io](https://img.shields.io/crates/v/glyph_brush.svg)](https://crates.io/crates/glyph_brush) 3 | [![Documentation](https://docs.rs/glyph_brush/badge.svg)](https://docs.rs/glyph_brush) 4 | ================ 5 | Fast caching text render library using [ab_glyph](https://github.com/alexheretic/ab-glyph). Provides render API agnostic rasterization & draw caching logic. 6 | 7 | Makes extensive use of caching to optimise frame performance. 8 | 9 | * GPU texture cache logic to dynamically maintain a GPU texture of rendered glyphs. 10 | * Caching of glyph layout output to avoid repeated cost of identical text rendering on sequential frames. 11 | * Layouts are re-used to optimise similar layout calculation after a change. 12 | * Vertex generation is cached per section and re-assembled into the total vertex array on change. 13 | * Avoids any layout or vertex calculations when identical text is rendered on sequential frames. 14 | 15 | The crate is designed to be easily wrapped to create a convenient render API specific version, for example [gfx-glyph](https://github.com/alexheretic/gfx-glyph/tree/master/gfx-glyph). 16 | 17 | ```rust 18 | use glyph_brush::{ab_glyph::FontArc, BrushAction, BrushError, GlyphBrushBuilder, Section, Text}; 19 | 20 | let dejavu = FontArc::try_from_slice(include_bytes!("../../fonts/DejaVuSans.ttf"))?; 21 | let mut glyph_brush = GlyphBrushBuilder::using_font(dejavu).build(); 22 | 23 | glyph_brush.queue(Section::default().add_text(Text::new("Hello glyph_brush"))); 24 | glyph_brush.queue(some_other_section); 25 | 26 | match glyph_brush.process_queued( 27 | |rect, tex_data| update_texture(rect, tex_data), 28 | |vertex_data| into_vertex(vertex_data), 29 | ) { 30 | Ok(BrushAction::Draw(vertices)) => { 31 | // Draw new vertices. 32 | } 33 | Ok(BrushAction::ReDraw) => { 34 | // Re-draw last frame's vertices unmodified. 35 | } 36 | Err(BrushError::TextureTooSmall { suggested }) => { 37 | // Enlarge texture + glyph_brush texture cache and retry. 38 | } 39 | } 40 | ``` 41 | 42 | ## Examples 43 | Have a look at 44 | * `cargo run --example opengl --release` 45 | -------------------------------------------------------------------------------- /glyph-brush/benches/lipsum.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, ferri simul omittantur eam eu, no debet doming dolorem ius. Iriure vocibus est te, natum delicata dignissim pri ea. Purto docendi definitiones no qui. Vel ridens instructior ad, vidisse percipitur et eos. Alienum ocurreret laboramus mei cu, usu ne meliore nostrum, usu tritani luptatum electram ad. 2 | 3 | Vis oratio tantas prodesset et, id stet inermis mea, at his copiosae accusata. Mel diam accusata argumentum cu, ut agam consul invidunt est. Ocurreret appellantur deterruisset no vis, his alia postulant inciderint no. Has albucius offendit at. An has noluisse comprehensam, vel veri dicit blandit ea, per paulo noluisse reformidans no. Nec ad sale illum soleat, agam scriptorem ad per. 4 | 5 | An cum odio mucius apeirian, labores conceptam ex nec, eruditi habemus qualisque eam an. Eu facilisi maluisset eos, fabulas apeirian ut qui, no atqui blandit vix. Apeirian phaedrum pri ex, vel hinc omnes sapientem et, vim vocibus legendos disputando ne. Et vel semper nominati rationibus, eum lorem causae scripta no. 6 | 7 | Ut quo elitr viderer constituam, pro omnesque forensibus at. Timeam scaevola mediocrem ut pri, te pro congue delicatissimi. Mei wisi nostro imperdiet ea, ridens salutatus per no, ut viris partem disputationi sit. Exerci eripuit referrentur vix at, sale mediocrem repudiare per te, modus admodum an eam. No vocent indoctum vis, ne quodsi patrioque vix. Vocent labores omittam et usu. 8 | 9 | Democritum signiferumque id nam, enim idque facilis at his. Inermis percipitur scriptorem sea cu, est ne error ludus option. Graecis expetenda contentiones cum et, ius nullam impetus suscipit ex. Modus clita corrumpit mel te, qui at lorem harum, primis cetero habemus sea id. Ei mutat affert dolorum duo, eum dissentias voluptatibus te, libris theophrastus duo id. 10 | -------------------------------------------------------------------------------- /glyph-brush/benches/small_lipsum.txt: -------------------------------------------------------------------------------- 1 | Eam in impetus consulatu, sit eros mundi cu. Oporteat expetendis vim te, per in verear delicata, ex his rebum minimum molestie. 2 | -------------------------------------------------------------------------------- /glyph-brush/examples/shader/img.fs: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | uniform sampler2D font_tex; 4 | 5 | in vec2 f_tex_pos; 6 | 7 | out vec4 out_color; 8 | 9 | void main() { 10 | out_color = texture(font_tex, f_tex_pos); 11 | } 12 | -------------------------------------------------------------------------------- /glyph-brush/examples/shader/img.vs: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | const mat4 INVERT_Y_AXIS = mat4( 4 | vec4(1.0, 0.0, 0.0, 0.0), 5 | vec4(0.0, -1.0, 0.0, 0.0), 6 | vec4(0.0, 0.0, 1.0, 0.0), 7 | vec4(0.0, 0.0, 0.0, 1.0) 8 | ); 9 | 10 | uniform mat4 transform; 11 | 12 | in vec3 left_top; 13 | in vec2 right_bottom; 14 | in vec4 color; 15 | 16 | out vec2 f_tex_pos; 17 | 18 | // generate positional data based on vertex ID 19 | void main() { 20 | vec2 pos = vec2(0.0); 21 | float left = left_top.x; 22 | float right = right_bottom.x; 23 | float top = left_top.y; 24 | float bottom = right_bottom.y; 25 | 26 | switch (gl_VertexID) { 27 | case 0: 28 | pos = vec2(left, top); 29 | f_tex_pos = vec2(0.0, 1.0); 30 | break; 31 | case 1: 32 | pos = vec2(right, top); 33 | f_tex_pos = vec2(1.0, 1.0); 34 | break; 35 | case 2: 36 | pos = vec2(left, bottom); 37 | f_tex_pos = vec2(0.0, 0.0); 38 | break; 39 | case 3: 40 | pos = vec2(right, bottom); 41 | f_tex_pos = vec2(1.0, 0.0); 42 | break; 43 | } 44 | 45 | gl_Position = INVERT_Y_AXIS * transform * vec4(pos, left_top.z, 1.0); 46 | } 47 | -------------------------------------------------------------------------------- /glyph-brush/examples/shader/text.fs: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | uniform sampler2D font_tex; 4 | 5 | in vec2 f_tex_pos; 6 | in vec4 f_color; 7 | 8 | out vec4 out_color; 9 | 10 | void main() { 11 | float alpha = texture(font_tex, f_tex_pos).r; 12 | if (alpha <= 0.0) { 13 | discard; 14 | } 15 | out_color = f_color * vec4(1.0, 1.0, 1.0, alpha); 16 | } 17 | -------------------------------------------------------------------------------- /glyph-brush/examples/shader/text.vs: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | const mat4 INVERT_Y_AXIS = mat4( 4 | vec4(1.0, 0.0, 0.0, 0.0), 5 | vec4(0.0, -1.0, 0.0, 0.0), 6 | vec4(0.0, 0.0, 1.0, 0.0), 7 | vec4(0.0, 0.0, 0.0, 1.0) 8 | ); 9 | 10 | uniform mat4 transform; 11 | 12 | in vec3 left_top; 13 | in vec2 right_bottom; 14 | in vec2 tex_left_top; 15 | in vec2 tex_right_bottom; 16 | in vec4 color; 17 | 18 | out vec2 f_tex_pos; 19 | out vec4 f_color; 20 | 21 | // generate positional data based on vertex ID 22 | void main() { 23 | vec2 pos = vec2(0.0); 24 | float left = left_top.x; 25 | float right = right_bottom.x; 26 | float top = left_top.y; 27 | float bottom = right_bottom.y; 28 | 29 | switch (gl_VertexID) { 30 | case 0: 31 | pos = vec2(left, top); 32 | f_tex_pos = tex_left_top; 33 | break; 34 | case 1: 35 | pos = vec2(right, top); 36 | f_tex_pos = vec2(tex_right_bottom.x, tex_left_top.y); 37 | break; 38 | case 2: 39 | pos = vec2(left, bottom); 40 | f_tex_pos = vec2(tex_left_top.x, tex_right_bottom.y); 41 | break; 42 | case 3: 43 | pos = vec2(right, bottom); 44 | f_tex_pos = tex_right_bottom; 45 | break; 46 | } 47 | 48 | f_color = color; 49 | gl_Position = INVERT_Y_AXIS * transform * vec4(pos, left_top.z, 1.0); 50 | } 51 | -------------------------------------------------------------------------------- /glyph-brush/examples/text/lipsum.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, ferri simul omittantur eam eu, no debet doming dolorem ius. Iriure vocibus est te, natum delicata dignissim pri ea. Purto docendi definitiones no qui. Vel ridens instructior ad, vidisse percipitur et eos. Alienum ocurreret laboramus mei cu, usu ne meliore nostrum, usu tritani luptatum electram ad. 2 | 3 | Vis oratio tantas prodesset et, id stet inermis mea, at his copiosae accusata. Mel diam accusata argumentum cu, ut agam consul invidunt est. Ocurreret appellantur deterruisset no vis, his alia postulant inciderint no. Has albucius offendit at. An has noluisse comprehensam, vel veri dicit blandit ea, per paulo noluisse reformidans no. Nec ad sale illum soleat, agam scriptorem ad per. 4 | 5 | An cum odio mucius apeirian, labores conceptam ex nec, eruditi habemus qualisque eam an. Eu facilisi maluisset eos, fabulas apeirian ut qui, no atqui blandit vix. Apeirian phaedrum pri ex, vel hinc omnes sapientem et, vim vocibus legendos disputando ne. Et vel semper nominati rationibus, eum lorem causae scripta no. 6 | 7 | Ut quo elitr viderer constituam, pro omnesque forensibus at. Timeam scaevola mediocrem ut pri, te pro congue delicatissimi. Mei wisi nostro imperdiet ea, ridens salutatus per no, ut viris partem disputationi sit. Exerci eripuit referrentur vix at, sale mediocrem repudiare per te, modus admodum an eam. No vocent indoctum vis, ne quodsi patrioque vix. Vocent labores omittam et usu. 8 | 9 | Democritum signiferumque id nam, enim idque facilis at his. Inermis percipitur scriptorem sea cu, est ne error ludus option. Graecis expetenda contentiones cum et, ius nullam impetus suscipit ex. Modus clita corrumpit mel te, qui at lorem harum, primis cetero habemus sea id. Ei mutat affert dolorum duo, eum dissentias voluptatibus te, libris theophrastus duo id. 10 | -------------------------------------------------------------------------------- /glyph-brush/src/extra.rs: -------------------------------------------------------------------------------- 1 | use ordered_float::OrderedFloat; 2 | use std::hash::{Hash, Hasher}; 3 | 4 | pub type Color = [f32; 4]; 5 | 6 | /// Default `extra` field type. Non-layout data for vertex generation. 7 | #[derive(Debug, Clone, Copy)] 8 | pub struct Extra { 9 | pub color: Color, 10 | pub z: f32, 11 | } 12 | 13 | impl Hash for Extra { 14 | #[inline] 15 | fn hash(&self, state: &mut H) { 16 | [ 17 | OrderedFloat::from(self.color[0]), 18 | OrderedFloat::from(self.color[1]), 19 | OrderedFloat::from(self.color[2]), 20 | OrderedFloat::from(self.color[3]), 21 | OrderedFloat::from(self.z), 22 | ] 23 | .hash(state) 24 | } 25 | } 26 | 27 | impl PartialEq for Extra { 28 | #[inline] 29 | fn eq(&self, other: &Self) -> bool { 30 | self.color == other.color && self.z == other.z 31 | } 32 | } 33 | 34 | impl Default for Extra { 35 | #[inline] 36 | fn default() -> Self { 37 | Self { 38 | color: [0.0, 0.0, 0.0, 1.0], 39 | z: 0.0, 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /glyph-brush/src/glyph_brush/builder.rs: -------------------------------------------------------------------------------- 1 | use crate::{DefaultSectionHasher, Font, FontId, GlyphBrush}; 2 | use glyph_brush_draw_cache::*; 3 | use glyph_brush_layout::ab_glyph::*; 4 | use std::hash::BuildHasher; 5 | 6 | /// Builder for a [`GlyphBrush`]. 7 | /// 8 | /// # Example 9 | /// ``` 10 | /// use glyph_brush::{ab_glyph::FontArc, GlyphBrush, GlyphBrushBuilder}; 11 | /// # type Vertex = (); 12 | /// 13 | /// let dejavu = FontArc::try_from_slice(include_bytes!("../../../fonts/DejaVuSans.ttf")).unwrap(); 14 | /// let mut glyph_brush: GlyphBrush = GlyphBrushBuilder::using_font(dejavu).build(); 15 | /// ``` 16 | #[non_exhaustive] 17 | pub struct GlyphBrushBuilder { 18 | pub font_data: Vec, 19 | pub cache_glyph_positioning: bool, 20 | pub cache_redraws: bool, 21 | pub section_hasher: H, 22 | pub draw_cache_builder: DrawCacheBuilder, 23 | } 24 | 25 | impl GlyphBrushBuilder<()> { 26 | /// Create a new builder with multiple fonts. 27 | pub fn using_fonts(fonts: Vec) -> GlyphBrushBuilder { 28 | Self::without_fonts().replace_fonts(|_| fonts) 29 | } 30 | 31 | /// Create a new builder with multiple fonts. 32 | #[inline] 33 | pub fn using_font(font: F) -> GlyphBrushBuilder { 34 | Self::using_fonts(vec![font]) 35 | } 36 | 37 | /// Create a new builder without any fonts. 38 | pub fn without_fonts() -> GlyphBrushBuilder<()> { 39 | GlyphBrushBuilder { 40 | font_data: Vec::new(), 41 | cache_glyph_positioning: true, 42 | cache_redraws: true, 43 | section_hasher: DefaultSectionHasher::default(), 44 | draw_cache_builder: DrawCache::builder() 45 | .dimensions(256, 256) 46 | .scale_tolerance(0.5) 47 | .position_tolerance(0.1) 48 | .align_4x4(false), 49 | } 50 | } 51 | } 52 | 53 | impl GlyphBrushBuilder { 54 | /// Consume all builder fonts a replace with new fonts returned by the input function. 55 | /// 56 | /// Generally only makes sense when wanting to change fonts after calling 57 | /// [`GlyphBrush::to_builder`](struct.GlyphBrush.html#method.to_builder). 58 | /// 59 | /// # Example 60 | /// ``` 61 | /// # use glyph_brush::{*, ab_glyph::*}; 62 | /// # type Vertex = (); 63 | /// # let open_sans = FontArc::try_from_slice(&include_bytes!("../../../fonts/DejaVuSans.ttf")[..]).unwrap(); 64 | /// # let deja_vu_sans = open_sans.clone(); 65 | /// let two_font_brush: GlyphBrush 66 | /// = GlyphBrushBuilder::using_fonts(vec![open_sans, deja_vu_sans]).build(); 67 | /// 68 | /// let one_font_brush: GlyphBrush, Vertex> = two_font_brush 69 | /// .to_builder() 70 | /// .replace_fonts(|mut fonts| { 71 | /// // remove open_sans, leaving just deja_vu as FontId(0) 72 | /// fonts.remove(0); 73 | /// fonts 74 | /// }) 75 | /// .build(); 76 | /// 77 | /// assert_eq!(one_font_brush.fonts().len(), 1); 78 | /// assert_eq!(two_font_brush.fonts().len(), 2); 79 | /// ``` 80 | pub fn replace_fonts(self, font_fn: NF) -> GlyphBrushBuilder 81 | where 82 | V: Into>, 83 | NF: FnOnce(Vec) -> V, 84 | { 85 | let font_data = font_fn(self.font_data).into(); 86 | GlyphBrushBuilder { 87 | font_data, 88 | cache_glyph_positioning: self.cache_glyph_positioning, 89 | cache_redraws: self.cache_redraws, 90 | section_hasher: self.section_hasher, 91 | draw_cache_builder: self.draw_cache_builder, 92 | } 93 | } 94 | } 95 | 96 | impl GlyphBrushBuilder { 97 | /// Adds additional fonts to the one added in [`using_font`](#method.using_font). 98 | /// Returns a [`FontId`](struct.FontId.html) to reference this font. 99 | pub fn add_font>(&mut self, font_data: I) -> FontId { 100 | self.font_data.push(font_data.into()); 101 | FontId(self.font_data.len() - 1) 102 | } 103 | 104 | /// Initial size of 2D texture used as a gpu cache, pixels (width, height). 105 | /// The GPU cache will dynamically quadruple in size whenever the current size 106 | /// is insufficient. 107 | /// 108 | /// Defaults to `(256, 256)` 109 | pub fn initial_cache_size(mut self, (w, h): (u32, u32)) -> Self { 110 | self.draw_cache_builder = self.draw_cache_builder.dimensions(w, h); 111 | self 112 | } 113 | 114 | /// Sets the maximum allowed difference in scale used for judging whether to reuse an 115 | /// existing glyph in the GPU cache. 116 | /// 117 | /// Defaults to `0.5` 118 | /// 119 | /// See docs for `glyph_brush_draw_cache::DrawCache` 120 | pub fn draw_cache_scale_tolerance(mut self, tolerance: f32) -> Self { 121 | self.draw_cache_builder = self.draw_cache_builder.scale_tolerance(tolerance); 122 | self 123 | } 124 | 125 | /// Sets the maximum allowed difference in subpixel position used for judging whether 126 | /// to reuse an existing glyph in the GPU cache. Anything greater than or equal to 127 | /// 1.0 means "don't care". 128 | /// 129 | /// Defaults to `0.1` 130 | /// 131 | /// See docs for `glyph_brush_draw_cache::DrawCache` 132 | pub fn draw_cache_position_tolerance(mut self, tolerance: f32) -> Self { 133 | self.draw_cache_builder = self.draw_cache_builder.position_tolerance(tolerance); 134 | self 135 | } 136 | 137 | /// When multiple CPU cores are available spread draw-cache work across all cores. 138 | /// 139 | /// Defaults to `true`. 140 | pub fn multithread(mut self, multithread: bool) -> Self { 141 | self.draw_cache_builder = self.draw_cache_builder.multithread(multithread); 142 | self 143 | } 144 | 145 | /// Align glyphs in texture cache to 4x4 texel boundaries. 146 | /// 147 | /// If your backend requires texture updates to be aligned to 4x4 texel 148 | /// boundaries (e.g. WebGL), this should be set to `true`. 149 | /// 150 | /// Defaults to `false` 151 | /// 152 | /// See docs for `glyph_brush_draw_cache::DrawCache` 153 | pub fn draw_cache_align_4x4(mut self, align: bool) -> Self { 154 | self.draw_cache_builder = self.draw_cache_builder.align_4x4(align); 155 | self 156 | } 157 | 158 | /// Sets whether perform the calculation of glyph positioning according to the layout 159 | /// every time, or use a cached result if the input `Section` and `GlyphPositioner` are the 160 | /// same hash as a previous call. 161 | /// 162 | /// Improves performance. Should only disable if using a custom GlyphPositioner that is 163 | /// impure according to it's inputs, so caching a previous call is not desired. Disabling 164 | /// also disables [`cache_redraws`](#method.cache_redraws). 165 | /// 166 | /// Defaults to `true` 167 | pub fn cache_glyph_positioning(mut self, cache: bool) -> Self { 168 | self.cache_glyph_positioning = cache; 169 | self 170 | } 171 | 172 | /// Sets optimising vertex drawing by reusing the last draw requesting an identical draw queue. 173 | /// Will result in the usage of [`BrushAction::ReDraw`](enum.BrushAction.html#variant.ReDraw). 174 | /// 175 | /// Improves performance. Is disabled if 176 | /// [`cache_glyph_positioning`](#method.cache_glyph_positioning) is disabled. 177 | /// 178 | /// Defaults to `true` 179 | pub fn cache_redraws(mut self, cache_redraws: bool) -> Self { 180 | self.cache_redraws = cache_redraws; 181 | self 182 | } 183 | 184 | /// Sets the section hasher. [`GlyphBrush`] cannot handle absolute section hash collisions 185 | /// so use a good hash algorithm. 186 | /// 187 | /// This hasher is used to distinguish sections, rather than for hashmap internal use. 188 | /// 189 | /// Defaults to [xxHash](https://docs.rs/twox-hash). 190 | /// 191 | /// # Example 192 | /// ``` 193 | /// # use glyph_brush::{ab_glyph::*, GlyphBrushBuilder}; 194 | /// # let some_font = FontArc::try_from_slice(include_bytes!("../../../fonts/DejaVuSans.ttf")).unwrap(); 195 | /// # type SomeOtherBuildHasher = glyph_brush::DefaultSectionHasher; 196 | /// GlyphBrushBuilder::using_font(some_font) 197 | /// .section_hasher(SomeOtherBuildHasher::default()) 198 | /// // ... 199 | /// # ; 200 | /// ``` 201 | pub fn section_hasher(self, section_hasher: T) -> GlyphBrushBuilder { 202 | GlyphBrushBuilder { 203 | section_hasher, 204 | font_data: self.font_data, 205 | cache_glyph_positioning: self.cache_glyph_positioning, 206 | cache_redraws: self.cache_redraws, 207 | draw_cache_builder: self.draw_cache_builder, 208 | } 209 | } 210 | 211 | /// Builds a [`GlyphBrush`]. 212 | /// 213 | /// If type inference fails try declaring the types `V` & `X`. 214 | /// See [`GlyphBrush` generic types](struct.GlyphBrush.html#generic-types). 215 | /// ``` 216 | /// # use glyph_brush::{ab_glyph::*, GlyphBrushBuilder}; 217 | /// # let some_font = FontArc::try_from_slice(include_bytes!("../../../fonts/DejaVuSans.ttf")).unwrap(); 218 | /// # type SomeOtherBuildHasher = glyph_brush::DefaultSectionHasher; 219 | /// # type Vertex = (); 220 | /// let glyph_brush = GlyphBrushBuilder::using_font(some_font) 221 | /// .build::(); 222 | /// ``` 223 | pub fn build(self) -> GlyphBrush { 224 | GlyphBrush { 225 | fonts: self.font_data, 226 | texture_cache: self.draw_cache_builder.build(), 227 | 228 | last_draw: <_>::default(), 229 | section_buffer: <_>::default(), 230 | calculate_glyph_cache: <_>::default(), 231 | 232 | last_frame_seq_id_sections: <_>::default(), 233 | frame_seq_id_sections: <_>::default(), 234 | 235 | keep_in_cache: <_>::default(), 236 | 237 | cache_glyph_positioning: self.cache_glyph_positioning, 238 | cache_redraws: self.cache_redraws && self.cache_glyph_positioning, 239 | 240 | section_hasher: self.section_hasher, 241 | 242 | last_pre_positioned: <_>::default(), 243 | pre_positioned: <_>::default(), 244 | } 245 | } 246 | 247 | /// Rebuilds an existing [`GlyphBrush`] with this builder's properties. This will clear all 248 | /// caches and queues. 249 | /// 250 | /// # Example 251 | /// ``` 252 | /// # use glyph_brush::{*, ab_glyph::*}; 253 | /// # let sans = FontArc::try_from_slice(include_bytes!("../../../fonts/DejaVuSans.ttf")).unwrap(); 254 | /// # type Vertex = (); 255 | /// let mut glyph_brush: GlyphBrush = GlyphBrushBuilder::using_font(sans).build(); 256 | /// assert_eq!(glyph_brush.texture_dimensions(), (256, 256)); 257 | /// 258 | /// // Use a new builder to rebuild the brush with a smaller initial cache size 259 | /// glyph_brush.to_builder().initial_cache_size((64, 64)).rebuild(&mut glyph_brush); 260 | /// assert_eq!(glyph_brush.texture_dimensions(), (64, 64)); 261 | /// ``` 262 | pub fn rebuild(self, brush: &mut GlyphBrush) { 263 | *brush = self.build(); 264 | } 265 | } 266 | 267 | /// Macro to delegate builder methods to an inner `glyph_brush::GlyphBrushBuilder` 268 | /// 269 | /// Implements: 270 | /// * `add_font_bytes` 271 | /// * `add_font` 272 | /// * `initial_cache_size` 273 | /// * `draw_cache_scale_tolerance` 274 | /// * `draw_cache_position_tolerance` 275 | /// * `draw_cache_align_4x4` 276 | /// * `cache_glyph_positioning` 277 | /// * `cache_redraws` 278 | /// 279 | /// # Example 280 | /// ``` 281 | /// use glyph_brush::{ab_glyph::*, *}; 282 | /// use std::hash::BuildHasher; 283 | /// 284 | /// # pub struct DownstreamGlyphBrush; 285 | /// pub struct DownstreamGlyphBrushBuilder { 286 | /// inner: glyph_brush::GlyphBrushBuilder, 287 | /// some_config: bool, 288 | /// } 289 | /// 290 | /// impl DownstreamGlyphBrushBuilder { 291 | /// delegate_glyph_brush_builder_fns!(inner); 292 | /// 293 | /// /// Sets some downstream configuration 294 | /// pub fn some_config(mut self, some_config: bool) -> Self { 295 | /// self.some_config = some_config; 296 | /// self 297 | /// } 298 | /// 299 | /// // Must be manually delegated 300 | /// pub fn section_hasher( 301 | /// self, 302 | /// section_hasher: T, 303 | /// ) -> DownstreamGlyphBrushBuilder { 304 | /// DownstreamGlyphBrushBuilder { 305 | /// inner: self.inner.section_hasher(section_hasher), 306 | /// some_config: self.some_config, 307 | /// } 308 | /// } 309 | /// 310 | /// pub fn build(self) -> DownstreamGlyphBrush { 311 | /// // ... 312 | /// # DownstreamGlyphBrush 313 | /// } 314 | /// } 315 | /// # fn main() {} 316 | /// ``` 317 | #[macro_export] 318 | macro_rules! delegate_glyph_brush_builder_fns { 319 | ($inner:ident) => { 320 | /// Adds additional fonts to the one added in [`using_font`](#method.using_font). 321 | /// Returns a [`FontId`](struct.FontId.html) to reference this font. 322 | pub fn add_font(&mut self, font_data: F) -> $crate::FontId { 323 | self.$inner.add_font(font_data) 324 | } 325 | 326 | /// Initial size of 2D texture used as a gpu cache, pixels (width, height). 327 | /// The GPU cache will dynamically quadruple in size whenever the current size 328 | /// is insufficient. 329 | /// 330 | /// Defaults to `(256, 256)` 331 | pub fn initial_cache_size(mut self, size: (u32, u32)) -> Self { 332 | self.$inner = self.$inner.initial_cache_size(size); 333 | self 334 | } 335 | 336 | /// Sets the maximum allowed difference in scale used for judging whether to reuse an 337 | /// existing glyph in the GPU cache. 338 | /// 339 | /// Defaults to `0.5` 340 | /// 341 | /// See docs for `glyph_brush_draw_cache::DrawCache` 342 | pub fn draw_cache_scale_tolerance(mut self, tolerance: f32) -> Self { 343 | self.$inner = self.$inner.draw_cache_scale_tolerance(tolerance); 344 | self 345 | } 346 | 347 | /// Sets the maximum allowed difference in subpixel position used for judging whether 348 | /// to reuse an existing glyph in the GPU cache. Anything greater than or equal to 349 | /// 1.0 means "don't care". 350 | /// 351 | /// Defaults to `0.1` 352 | /// 353 | /// See docs for `glyph_brush_draw_cache::DrawCache` 354 | pub fn draw_cache_position_tolerance(mut self, tolerance: f32) -> Self { 355 | self.$inner = self.$inner.draw_cache_position_tolerance(tolerance); 356 | self 357 | } 358 | 359 | /// Align glyphs in texture cache to 4x4 texel boundaries. 360 | /// 361 | /// If your backend requires texture updates to be aligned to 4x4 texel 362 | /// boundaries (e.g. WebGL), this should be set to `true`. 363 | /// 364 | /// Defaults to `false` 365 | /// 366 | /// See docs for `glyph_brush_draw_cache::DrawCache` 367 | pub fn draw_cache_align_4x4(mut self, b: bool) -> Self { 368 | self.$inner = self.$inner.draw_cache_align_4x4(b); 369 | self 370 | } 371 | 372 | /// Sets whether perform the calculation of glyph positioning according to the layout 373 | /// every time, or use a cached result if the input `Section` and `GlyphPositioner` are the 374 | /// same hash as a previous call. 375 | /// 376 | /// Improves performance. Should only disable if using a custom GlyphPositioner that is 377 | /// impure according to it's inputs, so caching a previous call is not desired. Disabling 378 | /// also disables [`cache_redraws`](#method.cache_redraws). 379 | /// 380 | /// Defaults to `true` 381 | pub fn cache_glyph_positioning(mut self, cache: bool) -> Self { 382 | self.$inner = self.$inner.cache_glyph_positioning(cache); 383 | self 384 | } 385 | 386 | /// Sets optimising drawing by reusing the last draw requesting an identical draw queue. 387 | /// 388 | /// Improves performance. Is disabled if 389 | /// [`cache_glyph_positioning`](#method.cache_glyph_positioning) is disabled. 390 | /// 391 | /// Defaults to `true` 392 | pub fn cache_redraws(mut self, cache: bool) -> Self { 393 | self.$inner = self.$inner.cache_redraws(cache); 394 | self 395 | } 396 | }; 397 | } 398 | -------------------------------------------------------------------------------- /glyph-brush/src/legacy.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use ab_glyph::PxScale; 3 | use ordered_float::OrderedFloat; 4 | use std::{borrow::Cow, f32, hash::*}; 5 | 6 | #[derive(Debug, Clone, Copy, PartialEq)] 7 | pub struct SectionText<'a> { 8 | /// Text to render 9 | pub text: &'a str, 10 | /// Pixel scale of text. Defaults to 16. 11 | pub scale: PxScale, 12 | /// Rgba color of rendered text. Defaults to black. 13 | pub color: Color, 14 | /// Font id to use for this section. 15 | /// 16 | /// It must be known to the `GlyphBrush` it is being used with, 17 | /// either `FontId::default()` or the return of 18 | /// [`add_font`](struct.GlyphBrushBuilder.html#method.add_font). 19 | pub font_id: FontId, 20 | } 21 | 22 | impl Default for SectionText<'static> { 23 | #[inline] 24 | fn default() -> Self { 25 | Self { 26 | text: "", 27 | scale: PxScale::from(16.0), 28 | color: [0.0, 0.0, 0.0, 1.0], 29 | font_id: <_>::default(), 30 | } 31 | } 32 | } 33 | 34 | impl<'a> SectionText<'a> { 35 | #[allow(clippy::wrong_self_convention)] // won't fix for backward compatibility 36 | #[inline] 37 | pub fn to_text(&self, z: f32) -> crate::Text<'a> { 38 | crate::Text::new(self.text) 39 | .with_scale(self.scale) 40 | .with_color(self.color) 41 | .with_font_id(self.font_id) 42 | .with_z(z) 43 | } 44 | } 45 | 46 | impl<'a> From<&crate::Text<'a>> for SectionText<'a> { 47 | #[inline] 48 | fn from(t: &crate::Text<'a>) -> Self { 49 | Self { 50 | text: t.text, 51 | scale: t.scale, 52 | color: t.extra.color, 53 | font_id: t.font_id, 54 | } 55 | } 56 | } 57 | 58 | impl<'a> From> for SectionText<'a> { 59 | #[inline] 60 | fn from(t: crate::Text<'a>) -> Self { 61 | Self::from(&t) 62 | } 63 | } 64 | 65 | /// An object that contains all the info to render a varied section of text. That is one including 66 | /// many parts with differing fonts/scales/colors bowing to a single layout. 67 | /// 68 | /// For single font/scale/color sections it may be simpler to use 69 | /// [`Section`](struct.Section.html). 70 | /// 71 | /// # Example 72 | /// 73 | /// ``` 74 | /// use glyph_brush::legacy::{SectionText, VariedSection}; 75 | /// 76 | /// let section = VariedSection { 77 | /// text: vec![ 78 | /// SectionText { 79 | /// text: "I looked around and it was ", 80 | /// ..<_>::default() 81 | /// }, 82 | /// SectionText { 83 | /// text: "RED", 84 | /// color: [1.0, 0.0, 0.0, 1.0], 85 | /// ..<_>::default() 86 | /// }, 87 | /// ], 88 | /// ..<_>::default() 89 | /// }; 90 | /// ``` 91 | #[derive(Debug, Clone, PartialEq)] 92 | pub struct VariedSection<'a> { 93 | /// Position on screen to render text, in pixels from top-left. Defaults to (0, 0). 94 | pub screen_position: (f32, f32), 95 | /// Max (width, height) bounds, in pixels from top-left. Defaults to unbounded. 96 | pub bounds: (f32, f32), 97 | /// Z values for use in depth testing. Defaults to 0.0 98 | pub z: f32, 99 | /// Built in layout, can be overridden with custom layout logic 100 | /// see [`queue_custom_layout`](struct.GlyphBrush.html#method.queue_custom_layout) 101 | pub layout: Layout, 102 | /// Text to render, rendered next to one another according the layout. 103 | pub text: Vec>, 104 | } 105 | 106 | impl Default for VariedSection<'static> { 107 | #[inline] 108 | fn default() -> Self { 109 | Self { 110 | screen_position: (0.0, 0.0), 111 | bounds: (f32::INFINITY, f32::INFINITY), 112 | z: 0.0, 113 | layout: Layout::default(), 114 | text: vec![], 115 | } 116 | } 117 | } 118 | 119 | impl<'a> From> for Cow<'a, VariedSection<'a>> { 120 | fn from(owned: VariedSection<'a>) -> Self { 121 | Cow::Owned(owned) 122 | } 123 | } 124 | 125 | impl<'a, 'b> From<&'b VariedSection<'a>> for Cow<'b, VariedSection<'a>> { 126 | fn from(owned: &'b VariedSection<'a>) -> Self { 127 | Cow::Borrowed(owned) 128 | } 129 | } 130 | 131 | impl<'a> From> for Cow<'a, crate::Section<'a>> { 132 | #[inline] 133 | fn from(s: VariedSection<'a>) -> Self { 134 | Cow::Owned(s.into()) 135 | } 136 | } 137 | 138 | impl<'a, 'b> From<&'b VariedSection<'a>> for Cow<'b, crate::Section<'a>> { 139 | #[inline] 140 | fn from(s: &'b VariedSection<'a>) -> Self { 141 | Cow::Owned(s.into()) 142 | } 143 | } 144 | 145 | impl<'a> From<&crate::Section<'a>> for VariedSection<'a> { 146 | #[inline] 147 | fn from(s: &crate::Section<'a>) -> Self { 148 | Self { 149 | text: s.text.iter().map(SectionText::from).collect(), 150 | bounds: s.bounds, 151 | screen_position: s.screen_position, 152 | layout: s.layout, 153 | // take the first z value, good enough for legacy compatibility 154 | z: s.text.first().map(|t| t.extra.z).unwrap_or(0.0), 155 | } 156 | } 157 | } 158 | 159 | impl Hash for VariedSection<'_> { 160 | fn hash(&self, state: &mut H) { 161 | let VariedSection { 162 | screen_position: (screen_x, screen_y), 163 | bounds: (bound_w, bound_h), 164 | z, 165 | layout, 166 | ref text, 167 | } = *self; 168 | 169 | let ord_floats: &[OrderedFloat<_>] = &[ 170 | screen_x.into(), 171 | screen_y.into(), 172 | bound_w.into(), 173 | bound_h.into(), 174 | z.into(), 175 | ]; 176 | 177 | layout.hash(state); 178 | 179 | hash_section_text(state, text); 180 | 181 | ord_floats.hash(state); 182 | } 183 | } 184 | 185 | #[inline] 186 | fn hash_section_text(state: &mut H, text: &[SectionText]) { 187 | for t in text { 188 | let SectionText { 189 | text, 190 | scale, 191 | color, 192 | font_id, 193 | } = *t; 194 | 195 | let ord_floats: &[OrderedFloat<_>] = &[ 196 | scale.x.into(), 197 | scale.y.into(), 198 | color[0].into(), 199 | color[1].into(), 200 | color[2].into(), 201 | color[3].into(), 202 | ]; 203 | 204 | (text, font_id, ord_floats).hash(state); 205 | } 206 | } 207 | 208 | impl VariedSection<'_> { 209 | pub fn to_owned(&self) -> OwnedVariedSection { 210 | OwnedVariedSection { 211 | screen_position: self.screen_position, 212 | bounds: self.bounds, 213 | z: self.z, 214 | layout: self.layout, 215 | text: self.text.iter().map(OwnedSectionText::from).collect(), 216 | } 217 | } 218 | } 219 | 220 | impl From<&VariedSection<'_>> for SectionGeometry { 221 | fn from(section: &VariedSection<'_>) -> Self { 222 | Self { 223 | bounds: section.bounds, 224 | screen_position: section.screen_position, 225 | } 226 | } 227 | } 228 | 229 | impl<'a> From<&VariedSection<'a>> for crate::Section<'a> { 230 | #[inline] 231 | fn from(s: &VariedSection<'a>) -> Self { 232 | crate::Section::builder() 233 | .with_layout(s.layout) 234 | .with_bounds(s.bounds) 235 | .with_screen_position(s.screen_position) 236 | .with_text(s.text.iter().map(|t| t.to_text(s.z)).collect()) 237 | } 238 | } 239 | 240 | impl<'a> From> for crate::Section<'a> { 241 | #[inline] 242 | fn from(s: VariedSection<'a>) -> Self { 243 | Self::from(&s) 244 | } 245 | } 246 | 247 | /// An object that contains all the info to render a section of text. 248 | /// 249 | /// For varied font/scale/color sections see [`VariedSection`](struct.VariedSection.html). 250 | /// 251 | /// # Example 252 | /// 253 | /// ``` 254 | /// use glyph_brush::legacy::Section; 255 | /// 256 | /// let section = Section { 257 | /// text: "Hello glyph_brush", 258 | /// ..Section::default() 259 | /// }; 260 | /// ``` 261 | #[derive(Debug, Clone, Copy, PartialEq)] 262 | pub struct Section<'a> { 263 | /// Text to render 264 | pub text: &'a str, 265 | /// Position on screen to render text, in pixels from top-left. Defaults to (0, 0). 266 | pub screen_position: (f32, f32), 267 | /// Max (width, height) bounds, in pixels from top-left. Defaults to unbounded. 268 | pub bounds: (f32, f32), 269 | /// Pixel scale of text. Defaults to 16. 270 | pub scale: PxScale, 271 | /// Rgba color of rendered text. Defaults to black. 272 | pub color: [f32; 4], 273 | /// Z values for use in depth testing. Defaults to 0.0 274 | pub z: f32, 275 | /// Built in layout, can overridden with custom layout logic 276 | /// see [`queue_custom_layout`](struct.GlyphBrush.html#method.queue_custom_layout) 277 | pub layout: Layout, 278 | /// Font id to use for this section. 279 | /// 280 | /// It must be known to the `GlyphBrush` it is being used with, 281 | /// either `FontId::default()` or the return of 282 | /// [`add_font`](struct.GlyphBrushBuilder.html#method.add_font). 283 | pub font_id: FontId, 284 | } 285 | 286 | impl Default for Section<'static> { 287 | #[inline] 288 | fn default() -> Self { 289 | Self { 290 | text: "", 291 | screen_position: (0.0, 0.0), 292 | bounds: (f32::INFINITY, f32::INFINITY), 293 | scale: PxScale::from(16.0), 294 | color: [0.0, 0.0, 0.0, 1.0], 295 | z: 0.0, 296 | layout: Layout::default(), 297 | font_id: FontId::default(), 298 | } 299 | } 300 | } 301 | 302 | impl<'a> From<&Section<'a>> for VariedSection<'a> { 303 | fn from(s: &Section<'a>) -> Self { 304 | let Section { 305 | text, 306 | scale, 307 | color, 308 | screen_position, 309 | bounds, 310 | z, 311 | layout, 312 | font_id, 313 | } = *s; 314 | 315 | VariedSection { 316 | text: vec![SectionText { 317 | text, 318 | scale, 319 | color, 320 | font_id, 321 | }], 322 | screen_position, 323 | bounds, 324 | z, 325 | layout, 326 | } 327 | } 328 | } 329 | 330 | impl<'a> From> for VariedSection<'a> { 331 | fn from(s: Section<'a>) -> Self { 332 | VariedSection::from(&s) 333 | } 334 | } 335 | 336 | impl<'a> From> for Cow<'a, VariedSection<'a>> { 337 | fn from(section: Section<'a>) -> Self { 338 | Cow::Owned(VariedSection::from(section)) 339 | } 340 | } 341 | 342 | impl<'a> From<&Section<'a>> for Cow<'a, VariedSection<'a>> { 343 | fn from(section: &Section<'a>) -> Self { 344 | Cow::Owned(VariedSection::from(section)) 345 | } 346 | } 347 | 348 | impl<'a> From<&Section<'a>> for crate::Section<'a> { 349 | fn from(s: &Section<'a>) -> Self { 350 | let Section { 351 | text, 352 | scale, 353 | color, 354 | screen_position, 355 | bounds, 356 | z, 357 | layout, 358 | font_id, 359 | } = *s; 360 | 361 | crate::Section::default() 362 | .add_text( 363 | Text::new(text) 364 | .with_scale(scale) 365 | .with_color(color) 366 | .with_z(z) 367 | .with_font_id(font_id), 368 | ) 369 | .with_screen_position(screen_position) 370 | .with_bounds(bounds) 371 | .with_layout(layout) 372 | } 373 | } 374 | 375 | impl<'a> From> for crate::Section<'a> { 376 | fn from(s: Section<'a>) -> Self { 377 | crate::Section::from(&s) 378 | } 379 | } 380 | 381 | impl<'a> From> for Cow<'a, crate::Section<'a>> { 382 | fn from(section: Section<'a>) -> Self { 383 | Cow::Owned(crate::Section::from(section)) 384 | } 385 | } 386 | 387 | impl<'a> From<&Section<'a>> for Cow<'a, crate::Section<'a>> { 388 | fn from(section: &Section<'a>) -> Self { 389 | Cow::Owned(crate::Section::from(section)) 390 | } 391 | } 392 | 393 | #[derive(Debug, Clone, PartialEq)] 394 | pub struct OwnedVariedSection { 395 | /// Position on screen to render text, in pixels from top-left. Defaults to (0, 0). 396 | pub screen_position: (f32, f32), 397 | /// Max (width, height) bounds, in pixels from top-left. Defaults to unbounded. 398 | pub bounds: (f32, f32), 399 | /// Z values for use in depth testing. Defaults to 0.0 400 | pub z: f32, 401 | /// Built in layout, can be overridden with custom layout logic 402 | /// see [`queue_custom_layout`](struct.GlyphBrush.html#method.queue_custom_layout) 403 | pub layout: Layout, 404 | /// Text to render, rendered next to one another according the layout. 405 | pub text: Vec, 406 | } 407 | 408 | impl Default for OwnedVariedSection { 409 | fn default() -> Self { 410 | Self { 411 | screen_position: (0.0, 0.0), 412 | bounds: (f32::INFINITY, f32::INFINITY), 413 | z: 0.0, 414 | layout: Layout::default(), 415 | text: vec![], 416 | } 417 | } 418 | } 419 | 420 | impl OwnedVariedSection { 421 | #[inline] 422 | pub fn to_borrowed(&self) -> VariedSection<'_> { 423 | VariedSection { 424 | screen_position: self.screen_position, 425 | bounds: self.bounds, 426 | z: self.z, 427 | layout: self.layout, 428 | text: self.text.iter().map(|t| t.into()).collect(), 429 | } 430 | } 431 | } 432 | 433 | impl<'a> From<&'a OwnedVariedSection> for VariedSection<'a> { 434 | fn from(owned: &'a OwnedVariedSection) -> Self { 435 | owned.to_borrowed() 436 | } 437 | } 438 | 439 | impl<'a> From<&'a OwnedVariedSection> for Cow<'a, VariedSection<'a>> { 440 | fn from(owned: &'a OwnedVariedSection) -> Self { 441 | Cow::Owned(owned.to_borrowed()) 442 | } 443 | } 444 | 445 | impl<'a> From<&'a OwnedVariedSection> for crate::Section<'a> { 446 | #[inline] 447 | fn from(owned: &'a OwnedVariedSection) -> Self { 448 | owned.to_borrowed().into() 449 | } 450 | } 451 | 452 | impl<'a> From<&'a OwnedVariedSection> for Cow<'a, crate::Section<'a>> { 453 | #[inline] 454 | fn from(owned: &'a OwnedVariedSection) -> Self { 455 | Cow::Owned(owned.to_borrowed().into()) 456 | } 457 | } 458 | 459 | #[derive(Debug, Clone, PartialEq)] 460 | pub struct OwnedSectionText { 461 | /// Text to render 462 | pub text: String, 463 | /// Pixel scale of text. Defaults to 16. 464 | pub scale: PxScale, 465 | /// Rgba color of rendered text. Defaults to black. 466 | pub color: [f32; 4], 467 | /// Font id to use for this section. 468 | /// 469 | /// It must be known to the `GlyphBrush` it is being used with, 470 | /// either `FontId::default()` or the return of 471 | /// [`add_font`](struct.GlyphBrushBuilder.html#method.add_font). 472 | pub font_id: FontId, 473 | } 474 | 475 | impl Default for OwnedSectionText { 476 | fn default() -> Self { 477 | Self { 478 | text: String::new(), 479 | scale: PxScale::from(16.0), 480 | color: [0.0, 0.0, 0.0, 1.0], 481 | font_id: FontId::default(), 482 | } 483 | } 484 | } 485 | 486 | impl<'a> From<&'a OwnedSectionText> for SectionText<'a> { 487 | fn from(owned: &'a OwnedSectionText) -> Self { 488 | Self { 489 | text: owned.text.as_str(), 490 | scale: owned.scale, 491 | color: owned.color, 492 | font_id: owned.font_id, 493 | } 494 | } 495 | } 496 | 497 | impl From<&SectionText<'_>> for OwnedSectionText { 498 | fn from(st: &SectionText<'_>) -> Self { 499 | Self { 500 | text: st.text.into(), 501 | scale: st.scale, 502 | color: st.color, 503 | font_id: st.font_id, 504 | } 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /glyph-brush/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! ``` 2 | //! use glyph_brush::{ 3 | //! ab_glyph::FontArc, BrushAction, BrushError, GlyphBrushBuilder, Section, Text, 4 | //! }; 5 | //! 6 | //! # fn main() -> Result<(), Box> { 7 | //! let dejavu = FontArc::try_from_slice(include_bytes!("../../fonts/DejaVuSans.ttf"))?; 8 | //! let mut glyph_brush = GlyphBrushBuilder::using_font(dejavu).build(); 9 | //! # let some_other_section = Section::default(); 10 | //! 11 | //! glyph_brush.queue(Section::default().add_text(Text::new("Hello glyph_brush"))); 12 | //! glyph_brush.queue(some_other_section); 13 | //! 14 | //! # fn update_texture(_: glyph_brush::Rectangle, _: &[u8]) {} 15 | //! # fn into_vertex(v: glyph_brush::GlyphVertex) { () } 16 | //! match glyph_brush.process_queued( 17 | //! |rect, tex_data| update_texture(rect, tex_data), 18 | //! |vertex_data| into_vertex(vertex_data), 19 | //! ) { 20 | //! Ok(BrushAction::Draw(vertices)) => { 21 | //! // Draw new vertices. 22 | //! } 23 | //! Ok(BrushAction::ReDraw) => { 24 | //! // Re-draw last frame's vertices unmodified. 25 | //! } 26 | //! Err(BrushError::TextureTooSmall { suggested }) => { 27 | //! // Enlarge texture + glyph_brush texture cache and retry. 28 | //! } 29 | //! } 30 | //! # Ok(()) 31 | //! # } 32 | //! ``` 33 | mod extra; 34 | mod glyph_brush; 35 | mod glyph_calculator; 36 | mod section; 37 | 38 | pub mod legacy; 39 | 40 | pub use crate::{extra::*, glyph_brush::*, glyph_calculator::*, section::*}; 41 | pub use glyph_brush_draw_cache::Rectangle; 42 | pub use glyph_brush_layout::*; 43 | 44 | use glyph_brush_layout::ab_glyph::*; 45 | 46 | /// A "practically collision free" `Section` hasher 47 | #[cfg(not(target_arch = "wasm32"))] 48 | pub type DefaultSectionHasher = twox_hash::xxhash64::RandomState; 49 | // Work around for rand issues in wasm #61 50 | #[cfg(target_arch = "wasm32")] 51 | pub type DefaultSectionHasher = std::hash::BuildHasherDefault; 52 | 53 | #[test] 54 | fn default_section_hasher() { 55 | use std::hash::BuildHasher; 56 | 57 | let section_a = Section::default().add_text(Text::new("Hovered Tile: Some((0, 0))")); 58 | let section_b = Section::default().add_text(Text::new("Hovered Tile: Some((1, 0))")); 59 | let hash = |s: &Section| DefaultSectionHasher::default().hash_one(s); 60 | assert_ne!(hash(§ion_a), hash(§ion_b)); 61 | } 62 | -------------------------------------------------------------------------------- /glyph-brush/src/section.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod owned; 3 | mod refed; 4 | 5 | pub use builder::*; 6 | pub use owned::*; 7 | pub use refed::*; 8 | -------------------------------------------------------------------------------- /glyph-brush/src/section/builder.rs: -------------------------------------------------------------------------------- 1 | use crate::{OwnedSection, OwnedText, Section, Text}; 2 | use glyph_brush_layout::{BuiltInLineBreaker, Layout}; 3 | 4 | /// [`Section`] builder. 5 | /// 6 | /// Usage can avoid generic `X` type issues as it's not mentioned until text is involved. 7 | #[derive(Debug, Clone, Copy, PartialEq)] 8 | pub struct SectionBuilder { 9 | /// Position on screen to render text, in pixels from top-left. Defaults to (0, 0). 10 | pub screen_position: (f32, f32), 11 | /// Max (width, height) bounds, in pixels from top-left. Defaults to unbounded. 12 | pub bounds: (f32, f32), 13 | /// Built in layout, can be overridden with custom layout logic 14 | /// see [`queue_custom_layout`](struct.GlyphBrush.html#method.queue_custom_layout) 15 | pub layout: Layout, 16 | } 17 | 18 | impl Default for SectionBuilder { 19 | fn default() -> Self { 20 | Self { 21 | screen_position: (0.0, 0.0), 22 | bounds: (f32::INFINITY, f32::INFINITY), 23 | layout: Layout::default(), 24 | } 25 | } 26 | } 27 | 28 | impl SectionBuilder { 29 | #[inline] 30 | pub fn with_screen_position>(mut self, position: P) -> Self { 31 | self.screen_position = position.into(); 32 | self 33 | } 34 | 35 | #[inline] 36 | pub fn with_bounds>(mut self, bounds: P) -> Self { 37 | self.bounds = bounds.into(); 38 | self 39 | } 40 | 41 | #[inline] 42 | pub fn with_layout>>(mut self, layout: L) -> Self { 43 | self.layout = layout.into(); 44 | self 45 | } 46 | 47 | #[inline] 48 | pub fn add_text(self, text: Text<'_, X>) -> Section<'_, X> { 49 | self.with_text(vec![text]) 50 | } 51 | 52 | #[inline] 53 | pub fn with_text(self, text: Vec>) -> Section<'_, X> { 54 | Section { 55 | text, 56 | screen_position: self.screen_position, 57 | bounds: self.bounds, 58 | layout: self.layout, 59 | } 60 | } 61 | 62 | #[inline] 63 | pub fn add_owned_text(self, text: OwnedText) -> OwnedSection { 64 | self.with_owned_text(vec![text]) 65 | } 66 | 67 | #[inline] 68 | pub fn with_owned_text(self, text: Vec>) -> OwnedSection { 69 | OwnedSection { 70 | text, 71 | screen_position: self.screen_position, 72 | bounds: self.bounds, 73 | layout: self.layout, 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /glyph-brush/src/section/owned.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use std::{borrow::Cow, f32}; 3 | 4 | #[derive(Debug, Clone, PartialEq)] 5 | pub struct OwnedSection { 6 | /// Position on screen to render text, in pixels from top-left. Defaults to (0, 0). 7 | pub screen_position: (f32, f32), 8 | /// Max (width, height) bounds, in pixels from top-left. Defaults to unbounded. 9 | pub bounds: (f32, f32), 10 | /// Built in layout, can be overridden with custom layout logic 11 | /// see [`queue_custom_layout`](struct.GlyphBrush.html#method.queue_custom_layout) 12 | pub layout: Layout, 13 | /// Text to render, rendered next to one another according the layout. 14 | pub text: Vec>, 15 | } 16 | 17 | impl Default for OwnedSection { 18 | #[inline] 19 | fn default() -> Self { 20 | Self { 21 | screen_position: (0.0, 0.0), 22 | bounds: (f32::INFINITY, f32::INFINITY), 23 | layout: Layout::default(), 24 | text: vec![], 25 | } 26 | } 27 | } 28 | 29 | impl OwnedSection { 30 | #[inline] 31 | pub fn with_screen_position>(mut self, position: P) -> Self { 32 | self.screen_position = position.into(); 33 | self 34 | } 35 | 36 | #[inline] 37 | pub fn with_bounds>(mut self, bounds: P) -> Self { 38 | self.bounds = bounds.into(); 39 | self 40 | } 41 | 42 | #[inline] 43 | pub fn with_layout>>(mut self, layout: L) -> Self { 44 | self.layout = layout.into(); 45 | self 46 | } 47 | 48 | #[inline] 49 | pub fn add_text>>(mut self, text: T) -> Self { 50 | self.text.push(text.into()); 51 | self 52 | } 53 | 54 | #[inline] 55 | pub fn with_text(self, text: Vec>) -> OwnedSection { 56 | OwnedSection { 57 | text, 58 | screen_position: self.screen_position, 59 | bounds: self.bounds, 60 | layout: self.layout, 61 | } 62 | } 63 | } 64 | 65 | impl OwnedSection { 66 | pub fn to_borrowed(&self) -> Section<'_, X> { 67 | Section { 68 | screen_position: self.screen_position, 69 | bounds: self.bounds, 70 | layout: self.layout, 71 | text: self.text.iter().map(|t| t.into()).collect(), 72 | } 73 | } 74 | } 75 | 76 | impl<'a, X: Clone> From<&'a OwnedSection> for Section<'a, X> { 77 | /// ``` 78 | /// use glyph_brush::{OwnedSection, OwnedText, Section}; 79 | /// 80 | /// let section = OwnedSection::default().add_text(OwnedText::new("foo").with_extra(1usize)); 81 | /// let cow: Section<'_, usize> = (§ion).into(); 82 | /// ``` 83 | fn from(owned: &'a OwnedSection) -> Self { 84 | owned.to_borrowed() 85 | } 86 | } 87 | 88 | impl<'a, X: Clone> From<&'a OwnedSection> for Cow<'a, Section<'a, X>> { 89 | /// ``` 90 | /// use glyph_brush::{OwnedSection, OwnedText, Section}; 91 | /// 92 | /// let section = OwnedSection::default().add_text(OwnedText::new("foo").with_extra(1usize)); 93 | /// let cow: std::borrow::Cow<'_, Section<'_, usize>> = (§ion).into(); 94 | /// ``` 95 | fn from(owned: &'a OwnedSection) -> Self { 96 | Cow::Owned(owned.to_borrowed()) 97 | } 98 | } 99 | 100 | #[derive(Debug, Clone, PartialEq)] 101 | pub struct OwnedText { 102 | /// Text to render. 103 | pub text: String, 104 | /// Pixel scale of text. Defaults to 16. 105 | pub scale: PxScale, 106 | /// Font id to use for this section. 107 | /// 108 | /// It must be known to the `GlyphBrush` it is being used with, 109 | /// either `FontId::default()` or the return of 110 | /// [`add_font`](struct.GlyphBrushBuilder.html#method.add_font). 111 | pub font_id: FontId, 112 | // Extra stuff for vertex generation. 113 | pub extra: X, 114 | } 115 | 116 | impl OwnedText { 117 | #[inline] 118 | pub fn with_text>(mut self, text: T) -> Self { 119 | self.text = text.into(); 120 | self 121 | } 122 | 123 | #[inline] 124 | pub fn with_scale>(mut self, scale: S) -> Self { 125 | self.scale = scale.into(); 126 | self 127 | } 128 | 129 | #[inline] 130 | pub fn with_font_id>(mut self, font_id: F) -> Self { 131 | self.font_id = font_id.into(); 132 | self 133 | } 134 | 135 | #[inline] 136 | pub fn with_extra(self, extra: X2) -> OwnedText { 137 | OwnedText { 138 | text: self.text, 139 | scale: self.scale, 140 | font_id: self.font_id, 141 | extra, 142 | } 143 | } 144 | } 145 | 146 | impl OwnedText { 147 | #[inline] 148 | pub fn new>(text: T) -> Self { 149 | OwnedText::default().with_text(text) 150 | } 151 | 152 | #[inline] 153 | pub fn with_color>(mut self, color: C) -> Self { 154 | self.extra.color = color.into(); 155 | self 156 | } 157 | 158 | #[inline] 159 | pub fn with_z>(mut self, z: Z) -> Self { 160 | self.extra.z = z.into(); 161 | self 162 | } 163 | } 164 | 165 | impl Default for OwnedText { 166 | #[inline] 167 | fn default() -> Self { 168 | Self { 169 | text: String::new(), 170 | scale: PxScale::from(16.0), 171 | font_id: <_>::default(), 172 | extra: <_>::default(), 173 | } 174 | } 175 | } 176 | 177 | impl<'a, X: Clone> From<&'a OwnedText> for Text<'a, X> { 178 | #[inline] 179 | fn from(owned: &'a OwnedText) -> Self { 180 | Self { 181 | text: owned.text.as_str(), 182 | scale: owned.scale, 183 | font_id: owned.font_id, 184 | extra: owned.extra.clone(), 185 | } 186 | } 187 | } 188 | 189 | impl From<&Text<'_, X>> for OwnedText { 190 | #[inline] 191 | fn from(s: &Text<'_, X>) -> Self { 192 | Self { 193 | text: s.text.into(), 194 | scale: s.scale, 195 | font_id: s.font_id, 196 | extra: s.extra.clone(), 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /glyph-brush/src/section/refed.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use ordered_float::OrderedFloat; 3 | use std::{borrow::Cow, f32, hash::*}; 4 | 5 | /// An object that contains all the info to render a varied section of text. That is one including 6 | /// many parts with differing fonts/scales/colors bowing to a single layout. 7 | /// 8 | /// # Example 9 | /// ``` 10 | /// use glyph_brush::{HorizontalAlign, Layout, Section, Text}; 11 | /// 12 | /// let section = Section::default() 13 | /// .add_text(Text::new("The last word was ").with_color([0.0, 0.0, 0.0, 1.0])) 14 | /// .add_text(Text::new("RED").with_color([1.0, 0.0, 0.0, 1.0])) 15 | /// .with_layout(Layout::default().h_align(HorizontalAlign::Center)); 16 | /// ``` 17 | /// 18 | /// # Extra 19 | /// Extra text section data is stored in a generic type, default `Extra`. To use custom 20 | /// `extra` data ensure your custom type implements `Debug`, `Clone`, `PartialEq` & `Hash`. 21 | #[derive(Debug, Clone, PartialEq)] 22 | pub struct Section<'a, X = Extra> { 23 | /// Position on screen to render text, in pixels from top-left. Defaults to (0, 0). 24 | pub screen_position: (f32, f32), 25 | /// Max (width, height) bounds, in pixels from top-left. Defaults to unbounded. 26 | pub bounds: (f32, f32), 27 | /// Built in layout, can be overridden with custom layout logic 28 | /// see [`queue_custom_layout`](struct.GlyphBrush.html#method.queue_custom_layout) 29 | pub layout: Layout, 30 | /// Text to render, rendered next to one another according the layout. 31 | pub text: Vec>, 32 | } 33 | 34 | impl Section<'_, X> { 35 | #[inline] 36 | pub(crate) fn clone_extras(&self) -> Vec { 37 | self.text.iter().map(|t| &t.extra).cloned().collect() 38 | } 39 | } 40 | 41 | impl Default for Section<'static, Extra> { 42 | /// Note this only works for `X=Extra` for more flexible use see [`Section::builder`]. 43 | #[inline] 44 | fn default() -> Self { 45 | Section::new() 46 | } 47 | } 48 | 49 | impl Section<'_, X> { 50 | #[inline] 51 | pub fn new() -> Self { 52 | Section::builder().with_text(vec![]) 53 | } 54 | } 55 | 56 | impl Section<'_, ()> { 57 | /// Return a `SectionBuilder` to fluently build up a `Section`. 58 | #[inline] 59 | pub fn builder() -> SectionBuilder { 60 | <_>::default() 61 | } 62 | } 63 | 64 | impl<'a, X> From> for Section<'a, X> { 65 | #[inline] 66 | fn from(text: Text<'a, X>) -> Self { 67 | Section::builder().add_text(text) 68 | } 69 | } 70 | 71 | impl<'a, X> From>> for Section<'a, X> { 72 | #[inline] 73 | fn from(text: Vec>) -> Self { 74 | Section::builder().with_text(text) 75 | } 76 | } 77 | 78 | impl<'a, X> Section<'a, X> { 79 | #[inline] 80 | pub fn with_screen_position>(mut self, position: P) -> Self { 81 | self.screen_position = position.into(); 82 | self 83 | } 84 | 85 | #[inline] 86 | pub fn with_bounds>(mut self, bounds: P) -> Self { 87 | self.bounds = bounds.into(); 88 | self 89 | } 90 | 91 | #[inline] 92 | pub fn with_layout>>(mut self, layout: L) -> Self { 93 | self.layout = layout.into(); 94 | self 95 | } 96 | 97 | #[inline] 98 | pub fn add_text>>(mut self, text: T) -> Self { 99 | self.text.push(text.into()); 100 | self 101 | } 102 | 103 | #[inline] 104 | pub fn with_text(self, text: Vec>) -> Section<'_, X2> { 105 | Section { 106 | text, 107 | screen_position: self.screen_position, 108 | bounds: self.bounds, 109 | layout: self.layout, 110 | } 111 | } 112 | } 113 | 114 | impl<'a, X: Clone> From> for Cow<'a, Section<'a, X>> { 115 | #[inline] 116 | fn from(owned: Section<'a, X>) -> Self { 117 | Cow::Owned(owned) 118 | } 119 | } 120 | 121 | impl<'a, 'b, X: Clone> From<&'b Section<'a, X>> for Cow<'b, Section<'a, X>> { 122 | #[inline] 123 | fn from(owned: &'b Section<'a, X>) -> Self { 124 | Cow::Borrowed(owned) 125 | } 126 | } 127 | 128 | impl Hash for Section<'_, X> { 129 | #[inline] 130 | fn hash(&self, state: &mut H) { 131 | let Section { 132 | screen_position: (screen_x, screen_y), 133 | bounds: (bound_w, bound_h), 134 | layout, 135 | ref text, 136 | } = *self; 137 | 138 | let ord_floats: &[OrderedFloat<_>] = &[ 139 | screen_x.into(), 140 | screen_y.into(), 141 | bound_w.into(), 142 | bound_h.into(), 143 | ]; 144 | 145 | layout.hash(state); 146 | 147 | hash_section_text(state, text); 148 | 149 | ord_floats.hash(state); 150 | } 151 | } 152 | 153 | /// `SectionText` + extra. 154 | #[derive(Debug, Clone, Copy, PartialEq)] 155 | pub struct Text<'a, X = Extra> { 156 | /// Text to render. 157 | pub text: &'a str, 158 | /// Pixel scale of text. Defaults to 16. 159 | pub scale: PxScale, 160 | /// Font id to use for this section. 161 | /// 162 | /// It must be a valid id in the `FontMap` used for layout calls. 163 | /// The default `FontId(0)` should always be valid. 164 | pub font_id: FontId, 165 | /// Extra stuff for vertex generation. 166 | pub extra: X, 167 | } 168 | 169 | impl Default for Text<'static, X> { 170 | #[inline] 171 | fn default() -> Self { 172 | Self { 173 | text: "", 174 | scale: PxScale::from(16.0), 175 | font_id: <_>::default(), 176 | extra: <_>::default(), 177 | } 178 | } 179 | } 180 | 181 | impl<'a, X> Text<'a, X> { 182 | #[inline] 183 | pub fn with_text(self, text: &str) -> Text<'_, X> { 184 | Text { 185 | text, 186 | scale: self.scale, 187 | font_id: self.font_id, 188 | extra: self.extra, 189 | } 190 | } 191 | 192 | #[inline] 193 | pub fn with_scale>(mut self, scale: S) -> Self { 194 | self.scale = scale.into(); 195 | self 196 | } 197 | 198 | #[inline] 199 | pub fn with_font_id>(mut self, font_id: F) -> Self { 200 | self.font_id = font_id.into(); 201 | self 202 | } 203 | 204 | #[inline] 205 | pub fn with_extra(self, extra: X2) -> Text<'a, X2> { 206 | Text { 207 | text: self.text, 208 | scale: self.scale, 209 | font_id: self.font_id, 210 | extra, 211 | } 212 | } 213 | } 214 | 215 | impl<'a, X: Default> Text<'a, X> { 216 | #[inline] 217 | pub fn new(text: &'a str) -> Self { 218 | Text::default().with_text(text) 219 | } 220 | } 221 | 222 | impl Text<'_, Extra> { 223 | #[inline] 224 | pub fn with_color>(mut self, color: C) -> Self { 225 | self.extra.color = color.into(); 226 | self 227 | } 228 | 229 | #[inline] 230 | pub fn with_z>(mut self, z: Z) -> Self { 231 | self.extra.z = z.into(); 232 | self 233 | } 234 | } 235 | 236 | impl ToSectionText for Text<'_, X> { 237 | #[inline] 238 | fn to_section_text(&self) -> SectionText<'_> { 239 | SectionText { 240 | text: self.text, 241 | scale: self.scale, 242 | font_id: self.font_id, 243 | } 244 | } 245 | } 246 | 247 | #[inline] 248 | fn hash_section_text(state: &mut H, text: &[Text<'_, X>]) { 249 | for t in text { 250 | let Text { 251 | text, 252 | scale, 253 | font_id, 254 | ref extra, 255 | } = *t; 256 | 257 | let ord_floats: [OrderedFloat<_>; 2] = [scale.x.into(), scale.y.into()]; 258 | 259 | (text, font_id, extra, ord_floats).hash(state); 260 | } 261 | } 262 | 263 | impl Section<'_, X> { 264 | pub fn to_owned(&self) -> OwnedSection { 265 | OwnedSection { 266 | screen_position: self.screen_position, 267 | bounds: self.bounds, 268 | layout: self.layout, 269 | text: self.text.iter().map(OwnedText::from).collect(), 270 | } 271 | } 272 | 273 | #[inline] 274 | pub(crate) fn to_hashable_parts(&self) -> HashableSectionParts<'_, X> { 275 | let Section { 276 | screen_position: (screen_x, screen_y), 277 | bounds: (bound_w, bound_h), 278 | ref text, 279 | layout: _, 280 | } = *self; 281 | 282 | let geometry = [ 283 | screen_x.into(), 284 | screen_y.into(), 285 | bound_w.into(), 286 | bound_h.into(), 287 | ]; 288 | 289 | HashableSectionParts { geometry, text } 290 | } 291 | } 292 | 293 | impl From<&Section<'_, X>> for SectionGeometry { 294 | #[inline] 295 | fn from(section: &Section<'_, X>) -> Self { 296 | Self { 297 | bounds: section.bounds, 298 | screen_position: section.screen_position, 299 | } 300 | } 301 | } 302 | 303 | pub(crate) struct HashableSectionParts<'a, X> { 304 | geometry: [OrderedFloat; 4], 305 | text: &'a [Text<'a, X>], 306 | } 307 | 308 | impl HashableSectionParts<'_, X> { 309 | #[inline] 310 | pub fn hash_geometry(&self, state: &mut H) { 311 | self.geometry.hash(state); 312 | } 313 | 314 | #[inline] 315 | pub fn hash_text_no_extra(&self, state: &mut H) { 316 | for t in self.text { 317 | let Text { 318 | text, 319 | scale, 320 | font_id, 321 | .. 322 | } = *t; 323 | 324 | let ord_floats: &[OrderedFloat<_>] = &[scale.x.into(), scale.y.into()]; 325 | 326 | (text, font_id, ord_floats).hash(state); 327 | } 328 | } 329 | 330 | #[inline] 331 | pub fn hash_extra(&self, state: &mut H) { 332 | self.text.iter().for_each(|t| t.extra.hash(state)); 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /layout/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.2.4 2 | * Fix `SectionText::scale` docs. 3 | * Improve `SectionGlyph` docs. 4 | 5 | # 0.2.3 6 | * Default layouts: Keep word trailing space width if ending in a hard break or end of all glyphs _e.g. `"Foo \n"`_ _(This particularly changes the layout of right & centre aligned text ending in spaces)_. 7 | 8 | # 0.2.2 9 | * Update _approx_ to `0.5`. 10 | 11 | # 0.2.1 12 | * Update _approx_ to `0.4`. 13 | * Update _xi-unicode_ to `0.3`. 14 | 15 | # 0.2 16 | * Rework crate switching from _rusttype_ to _ab_glyph_. 17 | - Layout returns `SectionGlyph`s which contain `section_index` & string `byte_index`. 18 | - Drop support for `Color` which didn't affect layout & can now be associated to sections without built-in support. 19 | - Glyph bounding boxes are no longer used at all during layout. This means invisible glyphs, like `' '`, are now generally included. 20 | 21 | # 0.1.9 22 | * Fix consistency of section bounds by removing usage of glyph pixel bounds during word layout, instead always relying on advance-width. 23 | * Fix possible floating point errors when using section bounds that exactly bound the section. 24 | 25 | # 0.1.8 26 | * Update _rusttype_ to `0.8`. _Compatible with rusttype `0.6.5` & `0.7.9`._ 27 | 28 | # 0.1.7 29 | * Update _xi-unicode_ to `0.2`. 30 | 31 | # 0.1.6 32 | * Fix missing line breaks for multi-byte breaking chars like Chinese characters. 33 | 34 | # 0.1.5 35 | * Add `GlyphPositioner::recalculate_glyphs` with a default unoptimised implementation. Custom layouts won't be broken by this change, but will need to implement the new function to provide optimised behaviour. 36 | * Optimise built-in layout's recalculate_glyphs for screen position changes with `GlyphChange::Geometry`. 37 | * Optimise built-in layout's recalculate_glyphs for single color changes with `GlyphChange::Color`. 38 | * Optimise built-in layout's recalculate_glyphs for alpha changes with `GlyphChange::Alpha`. 39 | * Optimise layout re-positioning with `PositionedGlyph::set_position` usage. 40 | 41 | # 0.1.4 42 | * Implement `PartialEq` for `SectionGeometry` & `SectionText`. 43 | 44 | # 0.1.3 45 | * Implement `FontMap` for `AsRef<[Font]>` instead of `Index` to support arrays and slices. If this breaks your usage try implementing `FontMap` directly. 46 | 47 | # 0.1.2 48 | * Fix single-line vertical alignment y-adjustment for center & bottom. 49 | 50 | # 0.1.1 51 | * Re-export `rusttype::point`. 52 | * Fix `bounds_rect` implementation for some `f32::INFINITY` cases. 53 | * Handle zero & negative scale cases. 54 | 55 | # 0.1 56 | * Initial release. 57 | -------------------------------------------------------------------------------- /layout/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "glyph_brush_layout" 3 | version = "0.2.4" 4 | authors = ["Alex Butler "] 5 | edition = "2021" 6 | description = "Text layout for ab_glyph" 7 | repository = "https://github.com/alexheretic/glyph-brush" 8 | keywords = ["layout", "text"] 9 | license = "Apache-2.0" 10 | readme = "README.md" 11 | 12 | [dependencies] 13 | ab_glyph = "0.2.1" 14 | approx = "0.5" 15 | xi-unicode = "0.3" 16 | 17 | [dev-dependencies] 18 | ordered-float = "5" 19 | -------------------------------------------------------------------------------- /layout/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /layout/README.md: -------------------------------------------------------------------------------- 1 | glyph_brush_layout 2 | [![crates.io](https://img.shields.io/crates/v/glyph_brush_layout.svg)](https://crates.io/crates/glyph_brush_layout) 3 | [![Documentation](https://docs.rs/glyph_brush_layout/badge.svg)](https://docs.rs/glyph_brush_layout) 4 | ================== 5 | Text layout for [ab_glyph](https://github.com/alexheretic/ab-glyph). 6 | 7 | * Generic positioning & linebreaking traits. 8 | * Built-in layout logic: 9 | - Mixed font & scale sections in a single layout. 10 | - Horizontal align left/center/right. 11 | - Vertical align top/center/bottom. 12 | - Unicode line breaking. 13 | - Bounded layouts. 14 | 15 | ```rust 16 | use glyph_brush_layout::{ab_glyph::*, *}; 17 | 18 | let dejavu = FontRef::try_from_slice(include_bytes!("../../fonts/DejaVuSans.ttf"))?; 19 | let garamond = FontRef::try_from_slice(include_bytes!("../../fonts/GaramondNo8-Reg.ttf"))?; 20 | 21 | // Simple font mapping: FontId(0) -> deja vu sans, FontId(1) -> garamond 22 | let fonts = &[dejavu, garamond]; 23 | 24 | // Layout "hello glyph_brush_layout" on an unbounded line with the second 25 | // word suitably bigger, greener and serif-ier. 26 | let glyphs = Layout::default().calculate_glyphs( 27 | fonts, 28 | &SectionGeometry { 29 | screen_position: (150.0, 50.0), 30 | ..SectionGeometry::default() 31 | }, 32 | &[ 33 | SectionText { 34 | text: "hello ", 35 | scale: PxScale::from(20.0), 36 | font_id: FontId(0), 37 | }, 38 | SectionText { 39 | text: "glyph_brush_layout", 40 | scale: PxScale::from(25.0), 41 | font_id: FontId(1), 42 | }, 43 | ], 44 | ); 45 | 46 | assert_eq!(glyphs.len(), 24); 47 | 48 | let SectionGlyph { glyph, font_id, section_index, byte_index } = &glyphs[4]; 49 | assert_eq!(glyph.id, fonts[0].glyph_id('o')); 50 | assert_eq!(*font_id, FontId(0)); 51 | assert_eq!(*section_index, 0); 52 | assert_eq!(*byte_index, 4); 53 | 54 | let SectionGlyph { glyph, font_id, section_index, byte_index } = &glyphs[14]; 55 | assert_eq!(glyph.id, fonts[1].glyph_id('u')); 56 | assert_eq!(*font_id, FontId(1)); 57 | assert_eq!(*section_index, 1); 58 | assert_eq!(*byte_index, 8); 59 | ``` 60 | -------------------------------------------------------------------------------- /layout/src/characters.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | linebreak::{EolLineBreak, LineBreak, LineBreaker}, 3 | words::Words, 4 | FontId, SectionText, 5 | }; 6 | use ab_glyph::*; 7 | use std::{ 8 | iter::{Enumerate, FusedIterator, Iterator}, 9 | str::CharIndices, 10 | }; 11 | 12 | /// Single character info 13 | pub(crate) struct Character<'b, F: Font> { 14 | pub glyph: Glyph, 15 | pub scale_font: PxScaleFont<&'b F>, 16 | pub font_id: FontId, 17 | /// Line break proceeding this character. 18 | pub line_break: Option, 19 | /// Equivalent to `char::is_control()`. 20 | pub control: bool, 21 | /// Equivalent to `char::is_whitespace()`. 22 | pub whitespace: bool, 23 | /// Index of the `SectionText` this character is from. 24 | pub section_index: usize, 25 | /// Position of the char within the `SectionText` text. 26 | pub byte_index: usize, 27 | } 28 | 29 | /// `Character` iterator 30 | pub(crate) struct Characters<'a, 'b, L, F, S> 31 | where 32 | F: Font, 33 | L: LineBreaker, 34 | S: Iterator>, 35 | { 36 | fonts: &'b [F], 37 | section_text: Enumerate, 38 | line_breaker: L, 39 | part_info: Option>, 40 | } 41 | 42 | struct PartInfo<'a> { 43 | section_index: usize, 44 | section: SectionText<'a>, 45 | info_chars: CharIndices<'a>, 46 | line_breaks: Box + 'a>, 47 | next_break: Option, 48 | } 49 | 50 | impl<'a, 'b, L, F, S> Characters<'a, 'b, L, F, S> 51 | where 52 | L: LineBreaker, 53 | F: Font, 54 | S: Iterator>, 55 | { 56 | /// Returns a new `Characters` iterator. 57 | pub(crate) fn new(fonts: &'b [F], section_text: S, line_breaker: L) -> Self { 58 | Self { 59 | fonts, 60 | section_text: section_text.enumerate(), 61 | line_breaker, 62 | part_info: None, 63 | } 64 | } 65 | 66 | /// Wraps into a `Words` iterator. 67 | pub(crate) fn words(self) -> Words<'a, 'b, L, F, S> { 68 | Words { 69 | characters: self.peekable(), 70 | } 71 | } 72 | } 73 | 74 | impl<'a, 'b, L, F, S> Iterator for Characters<'a, 'b, L, F, S> 75 | where 76 | L: LineBreaker, 77 | F: Font, 78 | S: Iterator>, 79 | { 80 | type Item = Character<'b, F>; 81 | 82 | #[inline] 83 | fn next(&mut self) -> Option { 84 | if self.part_info.is_none() { 85 | let mut index_and_section; 86 | loop { 87 | index_and_section = self.section_text.next()?; 88 | if valid_section(&index_and_section.1) { 89 | break; 90 | } 91 | } 92 | let (section_index, section) = index_and_section; 93 | let line_breaks = self.line_breaker.line_breaks(section.text); 94 | self.part_info = Some(PartInfo { 95 | section_index, 96 | section, 97 | info_chars: index_and_section.1.text.char_indices(), 98 | line_breaks, 99 | next_break: None, 100 | }); 101 | } 102 | 103 | { 104 | let PartInfo { 105 | section_index, 106 | section: 107 | SectionText { 108 | scale, 109 | font_id, 110 | text, 111 | }, 112 | info_chars, 113 | line_breaks, 114 | next_break, 115 | } = self.part_info.as_mut().unwrap(); 116 | 117 | if let Some((byte_index, c)) = info_chars.next() { 118 | if next_break.is_none() || next_break.unwrap().offset() <= byte_index { 119 | loop { 120 | let next = line_breaks.next(); 121 | if next.is_none() || next.unwrap().offset() > byte_index { 122 | *next_break = next; 123 | break; 124 | } 125 | } 126 | } 127 | 128 | let scale_font: PxScaleFont<&'b F> = self.fonts[*font_id].as_scaled(*scale); 129 | 130 | let glyph = scale_font.scaled_glyph(c); 131 | 132 | let c_len = c.len_utf8(); 133 | let mut line_break = next_break.filter(|b| b.offset() == byte_index + c_len); 134 | if line_break.is_some() && byte_index + c_len == text.len() { 135 | // handle inherent end-of-str breaks 136 | line_break = line_break.and(c.eol_line_break(&self.line_breaker)); 137 | } 138 | 139 | return Some(Character { 140 | glyph, 141 | scale_font, 142 | font_id: *font_id, 143 | line_break, 144 | control: c.is_control(), 145 | whitespace: c.is_whitespace(), 146 | 147 | section_index: *section_index, 148 | byte_index, 149 | }); 150 | } 151 | } 152 | 153 | self.part_info = None; 154 | self.next() 155 | } 156 | } 157 | 158 | impl<'a, L, F, S> FusedIterator for Characters<'a, '_, L, F, S> 159 | where 160 | L: LineBreaker, 161 | F: Font, 162 | S: Iterator>, 163 | { 164 | } 165 | 166 | #[inline] 167 | fn valid_section(s: &SectionText<'_>) -> bool { 168 | let PxScale { x, y } = s.scale; 169 | x > 0.0 && y > 0.0 170 | } 171 | -------------------------------------------------------------------------------- /layout/src/font.rs: -------------------------------------------------------------------------------- 1 | use ab_glyph::Font; 2 | 3 | /// Id for a font. 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] 5 | pub struct FontId(pub usize); 6 | 7 | impl std::ops::Index for [F] 8 | where 9 | F: Font, 10 | { 11 | type Output = F; 12 | 13 | #[inline] 14 | fn index(&self, index: FontId) -> &Self::Output { 15 | self.index(index.0) 16 | } 17 | } 18 | impl std::ops::Index<&FontId> for [F] 19 | where 20 | F: Font, 21 | { 22 | type Output = F; 23 | 24 | #[inline] 25 | fn index(&self, index: &FontId) -> &Self::Output { 26 | self.index(index.0) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /layout/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Text layout for [ab_glyph](https://github.com/alexheretic/ab-glyph). 2 | //! 3 | //! # Example 4 | //! 5 | //! ``` 6 | //! use glyph_brush_layout::{ab_glyph::*, *}; 7 | //! # fn main() -> Result<(), InvalidFont> { 8 | //! 9 | //! let dejavu = FontRef::try_from_slice(include_bytes!("../../fonts/DejaVuSans.ttf"))?; 10 | //! let garamond = FontRef::try_from_slice(include_bytes!("../../fonts/GaramondNo8-Reg.ttf"))?; 11 | //! 12 | //! // Simple font mapping: FontId(0) -> deja vu sans, FontId(1) -> garamond 13 | //! let fonts = &[dejavu, garamond]; 14 | //! 15 | //! // Layout "hello glyph_brush_layout" on an unbounded line with the second 16 | //! // word suitably bigger, greener and serif-ier. 17 | //! let glyphs = Layout::default().calculate_glyphs( 18 | //! fonts, 19 | //! &SectionGeometry { 20 | //! screen_position: (150.0, 50.0), 21 | //! ..SectionGeometry::default() 22 | //! }, 23 | //! &[ 24 | //! SectionText { 25 | //! text: "hello ", 26 | //! scale: PxScale::from(20.0), 27 | //! font_id: FontId(0), 28 | //! }, 29 | //! SectionText { 30 | //! text: "glyph_brush_layout", 31 | //! scale: PxScale::from(25.0), 32 | //! font_id: FontId(1), 33 | //! }, 34 | //! ], 35 | //! ); 36 | //! 37 | //! assert_eq!(glyphs.len(), 24); 38 | //! 39 | //! let SectionGlyph { 40 | //! glyph, 41 | //! font_id, 42 | //! section_index, 43 | //! byte_index, 44 | //! } = &glyphs[4]; 45 | //! assert_eq!(glyph.id, fonts[0].glyph_id('o')); 46 | //! assert_eq!(*font_id, FontId(0)); 47 | //! assert_eq!(*section_index, 0); 48 | //! assert_eq!(*byte_index, 4); 49 | //! 50 | //! let SectionGlyph { 51 | //! glyph, 52 | //! font_id, 53 | //! section_index, 54 | //! byte_index, 55 | //! } = &glyphs[14]; 56 | //! assert_eq!(glyph.id, fonts[1].glyph_id('u')); 57 | //! assert_eq!(*font_id, FontId(1)); 58 | //! assert_eq!(*section_index, 1); 59 | //! assert_eq!(*byte_index, 8); 60 | //! 61 | //! # Ok(()) 62 | //! # } 63 | //! ``` 64 | mod builtin; 65 | mod characters; 66 | mod font; 67 | mod linebreak; 68 | mod lines; 69 | mod section; 70 | mod words; 71 | 72 | /// Re-exported ab_glyph types. 73 | pub mod ab_glyph { 74 | pub use ab_glyph::*; 75 | } 76 | pub use self::{builtin::*, font::*, linebreak::*, section::*}; 77 | 78 | use ::ab_glyph::*; 79 | use std::hash::Hash; 80 | 81 | /// Logic to calculate glyph positioning using [`Font`](struct.Font.html), 82 | /// [`SectionGeometry`](struct.SectionGeometry.html) and 83 | /// [`SectionText`](struct.SectionText.html). 84 | pub trait GlyphPositioner: Hash { 85 | /// Calculate a sequence of positioned glyphs to render. Custom implementations should 86 | /// return the same result when called with the same arguments to allow layout caching. 87 | fn calculate_glyphs( 88 | &self, 89 | fonts: &[F], 90 | geometry: &SectionGeometry, 91 | sections: &[S], 92 | ) -> Vec 93 | where 94 | F: Font, 95 | S: ToSectionText; 96 | 97 | /// Return a screen rectangle according to the requested render position and bounds 98 | /// appropriate for the glyph layout. 99 | fn bounds_rect(&self, geometry: &SectionGeometry) -> Rect; 100 | 101 | /// Recalculate a glyph sequence after a change. 102 | /// 103 | /// The default implementation simply calls `calculate_glyphs` so must be implemented 104 | /// to provide benefits as such benefits are specific to the internal layout logic. 105 | fn recalculate_glyphs( 106 | &self, 107 | previous: P, 108 | change: GlyphChange, 109 | fonts: &[F], 110 | geometry: &SectionGeometry, 111 | sections: &[S], 112 | ) -> Vec 113 | where 114 | F: Font, 115 | S: ToSectionText, 116 | P: IntoIterator, 117 | { 118 | let _ = (previous, change); 119 | self.calculate_glyphs(fonts, geometry, sections) 120 | } 121 | } 122 | 123 | #[derive(Debug)] 124 | #[non_exhaustive] 125 | pub enum GlyphChange { 126 | /// Only the geometry has changed, contains the old geometry 127 | Geometry(SectionGeometry), 128 | Unknown, 129 | } 130 | -------------------------------------------------------------------------------- /layout/src/linebreak.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | hash::Hash, 4 | iter::FusedIterator, 5 | str::{self, CharIndices}, 6 | }; 7 | 8 | /// Indicator that a character is a line break, soft or hard. Includes the offset (byte-index) 9 | /// position. 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 11 | pub enum LineBreak { 12 | /// Soft line break (offset). 13 | Soft(usize), 14 | /// Hard line break (offset). 15 | Hard(usize), 16 | } 17 | 18 | impl LineBreak { 19 | /// Returns the offset of the line break, the index after the breaking character. 20 | #[inline] 21 | pub fn offset(&self) -> usize { 22 | match *self { 23 | LineBreak::Soft(offset) | LineBreak::Hard(offset) => offset, 24 | } 25 | } 26 | } 27 | 28 | /// Producer of a [`LineBreak`](enum.LineBreak.html) iterator. Used to allow to the 29 | /// [`Layout`](enum.Layout.html) to be line break aware in a generic way. 30 | pub trait LineBreaker: fmt::Debug + Copy + Hash { 31 | fn line_breaks<'a>(&self, glyph_info: &'a str) -> Box + 'a>; 32 | } 33 | 34 | /// Built-in linebreaking logic. 35 | #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] 36 | pub enum BuiltInLineBreaker { 37 | /// LineBreaker that follows Unicode Standard Annex #14. That effectively means it 38 | /// wraps words in a way that should work for most cases. 39 | #[default] 40 | UnicodeLineBreaker, 41 | /// LineBreaker that soft breaks on any character, and hard breaks similarly to 42 | /// UnicodeLineBreaker. 43 | AnyCharLineBreaker, 44 | } 45 | 46 | // Iterator that indicates all characters are soft line breaks, except hard ones which are hard. 47 | struct AnyCharLineBreakerIter<'a> { 48 | chars: CharIndices<'a>, 49 | breaks: xi_unicode::LineBreakIterator<'a>, 50 | current_break: Option<(usize, bool)>, 51 | } 52 | 53 | impl Iterator for AnyCharLineBreakerIter<'_> { 54 | type Item = LineBreak; 55 | 56 | #[inline] 57 | fn next(&mut self) -> Option { 58 | let (b_index, c) = self.chars.next()?; 59 | let c_len = c.len_utf8(); 60 | while self.current_break.is_some() { 61 | if self.current_break.as_ref().unwrap().0 < b_index + c_len { 62 | self.current_break = self.breaks.next(); 63 | } else { 64 | break; 65 | } 66 | } 67 | if let Some((break_index, true)) = self.current_break { 68 | if break_index == b_index + c_len { 69 | return Some(LineBreak::Hard(break_index)); 70 | } 71 | } 72 | Some(LineBreak::Soft(b_index + c_len)) 73 | } 74 | } 75 | 76 | impl FusedIterator for AnyCharLineBreakerIter<'_> {} 77 | 78 | impl LineBreaker for BuiltInLineBreaker { 79 | #[inline] 80 | fn line_breaks<'a>(&self, text: &'a str) -> Box + 'a> { 81 | match *self { 82 | BuiltInLineBreaker::UnicodeLineBreaker => Box::new( 83 | xi_unicode::LineBreakIterator::new(text).map(|(offset, hard)| { 84 | if hard { 85 | LineBreak::Hard(offset) 86 | } else { 87 | LineBreak::Soft(offset) 88 | } 89 | }), 90 | ), 91 | BuiltInLineBreaker::AnyCharLineBreaker => { 92 | let mut unicode_breaker = xi_unicode::LineBreakIterator::new(text); 93 | let current_break = unicode_breaker.next(); 94 | 95 | Box::new(AnyCharLineBreakerIter { 96 | chars: text.char_indices(), 97 | breaks: unicode_breaker, 98 | current_break, 99 | }) 100 | } 101 | } 102 | } 103 | } 104 | 105 | /// Line breakers can't easily tell the difference between the end of a slice being a hard 106 | /// break and the last character being itself a hard or soft break. This trait allows testing 107 | /// of eol characters being "true" eol line breakers. 108 | pub(crate) trait EolLineBreak { 109 | fn eol_line_break(&self, line_breaker: &B) -> Option; 110 | } 111 | 112 | impl EolLineBreak for char { 113 | #[inline] 114 | fn eol_line_break(&self, line_breaker: &B) -> Option { 115 | // to check if the previous end char (say '$') should hard break construct 116 | // a str "$ " an check if the line break logic flags a hard break at index 1 117 | let mut last_end_bytes: [u8; 5] = [b' '; 5]; 118 | self.encode_utf8(&mut last_end_bytes); 119 | let len_utf8 = self.len_utf8(); 120 | if let Ok(last_end_padded) = str::from_utf8(&last_end_bytes[0..=len_utf8]) { 121 | match line_breaker.line_breaks(last_end_padded).next() { 122 | l @ Some(LineBreak::Soft(1)) | l @ Some(LineBreak::Hard(1)) => return l, 123 | _ => {} 124 | } 125 | } 126 | 127 | // check for soft breaks using str "$a" 128 | last_end_bytes[len_utf8] = b'a'; 129 | if let Ok(last_end_padded) = str::from_utf8(&last_end_bytes[0..=len_utf8]) { 130 | match line_breaker.line_breaks(last_end_padded).next() { 131 | l @ Some(LineBreak::Soft(1)) | l @ Some(LineBreak::Hard(1)) => return l, 132 | _ => {} 133 | } 134 | } 135 | 136 | None 137 | } 138 | } 139 | 140 | #[cfg(test)] 141 | mod eol_line_break { 142 | use super::*; 143 | 144 | #[test] 145 | fn hard_break_char() { 146 | assert_eq!( 147 | '\n'.eol_line_break(&BuiltInLineBreaker::default()), 148 | Some(LineBreak::Hard(1)) 149 | ); 150 | } 151 | 152 | #[test] 153 | fn soft_break_char() { 154 | assert_eq!( 155 | ' '.eol_line_break(&BuiltInLineBreaker::default()), 156 | Some(LineBreak::Soft(1)) 157 | ); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /layout/src/lines.rs: -------------------------------------------------------------------------------- 1 | use super::{HorizontalAlign, SectionGlyph, SectionText, VerticalAlign}; 2 | use crate::{linebreak::LineBreaker, words::*}; 3 | use ab_glyph::*; 4 | use std::iter::{FusedIterator, Iterator, Peekable}; 5 | 6 | /// A line of `Word`s limited to a max width bound. 7 | #[derive(Default)] 8 | pub(crate) struct Line { 9 | pub glyphs: Vec, 10 | pub max_v_metrics: VMetrics, 11 | pub rightmost: f32, 12 | } 13 | 14 | impl Line { 15 | #[inline] 16 | pub(crate) fn line_height(&self) -> f32 { 17 | self.max_v_metrics.ascent - self.max_v_metrics.descent + self.max_v_metrics.line_gap 18 | } 19 | 20 | /// Returns line glyphs positioned on the screen and aligned. 21 | pub fn aligned_on_screen( 22 | mut self, 23 | (screen_x, screen_y): (f32, f32), 24 | h_align: HorizontalAlign, 25 | v_align: VerticalAlign, 26 | ) -> Vec { 27 | if self.glyphs.is_empty() { 28 | return Vec::new(); 29 | } 30 | 31 | // implement v-aligns when they're are supported 32 | let screen_left = match h_align { 33 | HorizontalAlign::Left => point(screen_x, screen_y), 34 | // - Right alignment attained from left by shifting the line 35 | // leftwards by the rightmost x distance from render position 36 | // - Central alignment is attained from left by shifting the line 37 | // leftwards by half the rightmost x distance from render position 38 | HorizontalAlign::Center | HorizontalAlign::Right => { 39 | let mut shift_left = self.rightmost; 40 | if h_align == HorizontalAlign::Center { 41 | shift_left /= 2.0; 42 | } 43 | point(screen_x - shift_left, screen_y) 44 | } 45 | }; 46 | 47 | let screen_pos = match v_align { 48 | VerticalAlign::Top => screen_left, 49 | VerticalAlign::Center => { 50 | let mut screen_pos = screen_left; 51 | screen_pos.y -= self.line_height() / 2.0; 52 | screen_pos 53 | } 54 | VerticalAlign::Bottom => { 55 | let mut screen_pos = screen_left; 56 | screen_pos.y -= self.line_height(); 57 | screen_pos 58 | } 59 | }; 60 | 61 | self.glyphs 62 | .iter_mut() 63 | .for_each(|sg| sg.glyph.position += screen_pos); 64 | 65 | self.glyphs 66 | } 67 | } 68 | 69 | /// `Line` iterator. 70 | /// 71 | /// Will iterator through `Word` until the next word would break the `width_bound`. 72 | /// 73 | /// Note: Will always have at least one word, if possible, even if the word itself 74 | /// breaks the `width_bound`. 75 | pub(crate) struct Lines<'a, 'b, L, F, S> 76 | where 77 | L: LineBreaker, 78 | F: Font, 79 | S: Iterator>, 80 | { 81 | pub(crate) words: Peekable>, 82 | pub(crate) width_bound: f32, 83 | } 84 | 85 | impl<'a, L, F, S> Iterator for Lines<'a, '_, L, F, S> 86 | where 87 | L: LineBreaker, 88 | F: Font, 89 | S: Iterator>, 90 | { 91 | type Item = Line; 92 | 93 | fn next(&mut self) -> Option { 94 | let mut caret = point(0.0, 0.0); 95 | let mut line = Line::default(); 96 | 97 | let mut progressed = false; 98 | 99 | while let Some(word) = self.words.peek() { 100 | // Drop trailing spaces when bounds-wrapping. 101 | // However, if the word ends in a hard-break "Foo \n" keep the trailing space width. 102 | let word_wrap_width = match word.hard_break { 103 | false => word.layout_width_no_trail, 104 | true => word.layout_width, 105 | }; 106 | 107 | let word_right = caret.x + word_wrap_width; 108 | // Reduce float errors by using relative "<= width bound" check 109 | let word_in_bounds = 110 | word_right < self.width_bound || approx::relative_eq!(word_right, self.width_bound); 111 | 112 | // only if `progressed` means the first word is allowed to overlap the bounds 113 | if !word_in_bounds && progressed { 114 | break; 115 | } 116 | 117 | let word = self.words.next().unwrap(); 118 | progressed = true; 119 | 120 | line.rightmost = word_right; 121 | 122 | if (line.glyphs.is_empty() || !word.glyphs.is_empty()) 123 | && word.max_v_metrics.height() > line.max_v_metrics.height() 124 | { 125 | let diff_y = word.max_v_metrics.ascent - caret.y; 126 | caret.y += diff_y; 127 | 128 | // modify all smaller lined glyphs to occupy the new larger line 129 | for SectionGlyph { glyph, .. } in &mut line.glyphs { 130 | glyph.position.y += diff_y; 131 | } 132 | 133 | line.max_v_metrics = word.max_v_metrics; 134 | } 135 | 136 | line.glyphs.extend(word.glyphs.into_iter().map(|mut sg| { 137 | sg.glyph.position += caret; 138 | sg 139 | })); 140 | 141 | caret.x += word.layout_width; 142 | 143 | if word.hard_break { 144 | break; 145 | } 146 | } 147 | 148 | Some(line).filter(|_| progressed) 149 | } 150 | } 151 | 152 | impl<'a, L, F, S> FusedIterator for Lines<'a, '_, L, F, S> 153 | where 154 | L: LineBreaker, 155 | F: Font, 156 | S: Iterator>, 157 | { 158 | } 159 | -------------------------------------------------------------------------------- /layout/src/section.rs: -------------------------------------------------------------------------------- 1 | use crate::FontId; 2 | use ab_glyph::*; 3 | use std::f32; 4 | 5 | #[derive(Debug, Clone, Copy, PartialEq)] 6 | pub struct SectionGeometry { 7 | /// Position on screen to render text, in pixels from top-left. Defaults to (0, 0). 8 | pub screen_position: (f32, f32), 9 | /// Max (width, height) bounds, in pixels from top-left. Defaults to unbounded. 10 | pub bounds: (f32, f32), 11 | } 12 | 13 | impl Default for SectionGeometry { 14 | #[inline] 15 | fn default() -> Self { 16 | Self { 17 | screen_position: (0.0, 0.0), 18 | bounds: (f32::INFINITY, f32::INFINITY), 19 | } 20 | } 21 | } 22 | 23 | /// Text to layout together using a font & scale. 24 | #[derive(Debug, Clone, Copy, PartialEq)] 25 | pub struct SectionText<'a> { 26 | /// Text to render 27 | pub text: &'a str, 28 | /// Pixel scale of text. Defaults to 16. 29 | pub scale: PxScale, 30 | /// Font id to use for this section. 31 | /// 32 | /// It must be a valid id in the `FontMap` used for layout calls. 33 | /// The default `FontId(0)` should always be valid. 34 | pub font_id: FontId, 35 | } 36 | 37 | impl Default for SectionText<'static> { 38 | #[inline] 39 | fn default() -> Self { 40 | Self { 41 | text: "", 42 | scale: PxScale::from(16.0), 43 | font_id: FontId::default(), 44 | } 45 | } 46 | } 47 | 48 | pub trait ToSectionText { 49 | fn to_section_text(&self) -> SectionText<'_>; 50 | } 51 | 52 | impl ToSectionText for SectionText<'_> { 53 | #[inline] 54 | fn to_section_text(&self) -> SectionText<'_> { 55 | *self 56 | } 57 | } 58 | 59 | impl ToSectionText for &SectionText<'_> { 60 | #[inline] 61 | fn to_section_text(&self) -> SectionText<'_> { 62 | **self 63 | } 64 | } 65 | 66 | /// A positioned glyph with info relating to the [`SectionText`] (or glyph_brush `Section::text`) 67 | /// from which it was derived. 68 | #[derive(Debug, Clone, PartialEq, PartialOrd)] 69 | pub struct SectionGlyph { 70 | /// The index of the [`SectionText`] source for this glyph. 71 | pub section_index: usize, 72 | /// The exact character byte index from the [`SectionText::text`] source for this glyph. 73 | pub byte_index: usize, 74 | /// A positioned glyph. 75 | pub glyph: Glyph, 76 | /// Font id. 77 | pub font_id: FontId, 78 | } 79 | -------------------------------------------------------------------------------- /layout/src/words.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | characters::{Character, Characters}, 3 | linebreak::{LineBreak, LineBreaker}, 4 | lines::Lines, 5 | SectionGlyph, SectionText, 6 | }; 7 | use ab_glyph::*; 8 | use std::iter::{FusedIterator, Iterator, Peekable}; 9 | 10 | #[derive(Clone, Debug, Default)] 11 | pub(crate) struct VMetrics { 12 | pub ascent: f32, 13 | pub descent: f32, 14 | pub line_gap: f32, 15 | } 16 | 17 | impl VMetrics { 18 | #[inline] 19 | pub fn height(&self) -> f32 { 20 | self.ascent - self.descent + self.line_gap 21 | } 22 | 23 | #[inline] 24 | pub fn max(self, other: Self) -> Self { 25 | if other.height() > self.height() { 26 | other 27 | } else { 28 | self 29 | } 30 | } 31 | } 32 | 33 | impl From> for VMetrics { 34 | #[inline] 35 | fn from(scale_font: PxScaleFont) -> Self { 36 | Self { 37 | ascent: scale_font.ascent(), 38 | descent: scale_font.descent(), 39 | line_gap: scale_font.line_gap(), 40 | } 41 | } 42 | } 43 | 44 | /// Single 'word' ie a sequence of `Character`s where the last is a line-break. 45 | /// 46 | /// Glyphs are relatively positioned from (0, 0) in a left-top alignment style. 47 | pub(crate) struct Word { 48 | pub glyphs: Vec, 49 | /// pixel advance width of word includes ending spaces/invisibles 50 | pub layout_width: f32, 51 | /// pixel advance width of word not including any trailing spaces/invisibles 52 | pub layout_width_no_trail: f32, 53 | pub max_v_metrics: VMetrics, 54 | /// indicates the break after the word is a hard one 55 | pub hard_break: bool, 56 | } 57 | 58 | /// `Word` iterator. 59 | pub(crate) struct Words<'a, 'b, L, F, S> 60 | where 61 | L: LineBreaker, 62 | F: Font, 63 | S: Iterator>, 64 | { 65 | pub(crate) characters: Peekable>, 66 | } 67 | 68 | impl<'a, 'b, L, F, S> Words<'a, 'b, L, F, S> 69 | where 70 | L: LineBreaker, 71 | F: Font, 72 | S: Iterator>, 73 | { 74 | pub(crate) fn lines(self, width_bound: f32) -> Lines<'a, 'b, L, F, S> { 75 | Lines { 76 | words: self.peekable(), 77 | width_bound, 78 | } 79 | } 80 | } 81 | 82 | impl<'a, L, F, S> Iterator for Words<'a, '_, L, F, S> 83 | where 84 | L: LineBreaker, 85 | F: Font, 86 | S: Iterator>, 87 | { 88 | type Item = Word; 89 | 90 | #[inline] 91 | fn next(&mut self) -> Option { 92 | let mut glyphs = Vec::new(); 93 | let mut caret = 0.0; 94 | let mut caret_no_trail = caret; 95 | let mut last_glyph_id = None; 96 | let mut max_v_metrics = VMetrics::default(); 97 | let mut hard_break = false; 98 | let mut progress = false; 99 | 100 | for Character { 101 | mut glyph, 102 | scale_font, 103 | font_id, 104 | line_break, 105 | control, 106 | whitespace, 107 | section_index, 108 | byte_index, 109 | } in &mut self.characters 110 | { 111 | progress = true; 112 | 113 | max_v_metrics = max_v_metrics.max(scale_font.into()); 114 | 115 | if let Some(id) = last_glyph_id.take() { 116 | caret += scale_font.kern(id, glyph.id); 117 | } 118 | last_glyph_id = Some(glyph.id); 119 | 120 | if !control { 121 | let advance_width = scale_font.h_advance(glyph.id); 122 | 123 | glyph.position = point(caret, 0.0); 124 | glyphs.push(SectionGlyph { 125 | section_index, 126 | byte_index, 127 | glyph, 128 | font_id, 129 | }); 130 | caret += advance_width; 131 | 132 | if !whitespace { 133 | // not an invisible trail 134 | caret_no_trail = caret; 135 | } 136 | } 137 | 138 | if let Some(lbreak) = line_break { 139 | // simulate hard-break at end of all sections 140 | if matches!(lbreak, LineBreak::Hard(_)) || self.characters.peek().is_none() { 141 | hard_break = true 142 | } 143 | break; 144 | } 145 | } 146 | 147 | if progress { 148 | return Some(Word { 149 | glyphs, 150 | layout_width: caret, 151 | layout_width_no_trail: caret_no_trail, 152 | hard_break, 153 | max_v_metrics, 154 | }); 155 | } 156 | 157 | None 158 | } 159 | } 160 | 161 | impl<'a, L, F, S> FusedIterator for Words<'a, '_, L, F, S> 162 | where 163 | L: LineBreaker, 164 | F: Font, 165 | S: Iterator>, 166 | { 167 | } 168 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # run CI-like set of tests 3 | set -eu 4 | 5 | dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | cd "$dir" 7 | 8 | echo "==> check wasm32-unknown-unknown" 9 | cargo check --target wasm32-unknown-unknown --target-dir "${CARGO_TARGET_DIR:-./target}/wasm/" 10 | echo "==> test" 11 | cargo test 12 | cargo test --benches 13 | #echo "==> test (32-bit)" 14 | #cargo test --target i686-unknown-linux-musl --target-dir "${CARGO_TARGET_DIR:-./target}/32bit/" 15 | echo "==> rustfmt" 16 | cargo fmt -- --check 17 | --------------------------------------------------------------------------------