├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── assets └── fonts │ ├── FiraMono-Medium.ttf │ ├── LICENSE │ └── README.md ├── docs └── highlight.webp ├── examples ├── 2d_text.rs ├── 3d_scene.rs └── performance.rs └── src ├── font_loader.rs ├── lib.rs ├── mesh_cache.rs ├── mesh_data_generator.rs ├── mesh_system.rs └── text_mesh.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | test: 10 | strategy: 11 | matrix: 12 | os: [windows-latest, ubuntu-latest] 13 | rust: [stable, nightly] 14 | runs-on: ${{ matrix.os }} 15 | 16 | steps: 17 | - name: Install dependencies 18 | run: sudo apt-get install gcc patch libudev-dev 19 | if: runner.os == 'linux' 20 | 21 | - uses: actions/checkout@v2 22 | 23 | - uses: dtolnay/rust-toolchain@v1 24 | with: 25 | toolchain: ${{matrix.rust}} 26 | 27 | - name: Cache 28 | uses: actions/cache@v3 29 | with: 30 | path: | 31 | ~/.cargo/registry 32 | ~/.cargo/git 33 | target 34 | key: ${{ runner.os }}-${{ matrix.rust }}-cargo-${{ hashFiles('**/Cargo.toml') }} 35 | 36 | - name: Run tests 37 | run: cargo test --verbose 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | [git_tag_comparison]: https://github.com/blaind/bevy_text_mesh/compare/v0.9.0...main 4 | 5 | ## Version 0.9.0 (2023-11-21) 6 | 7 | [Compare changelog](https://github.com/blaind/bevy_text_mesh/compare/v0.8.0...v0.9.0) 8 | 9 | ### Changed 10 | 11 | - [Breaking: reverted the `#mesh` suffix for font loading - the suffix is required again][34] 12 | 13 | ## Version 0.8.0 (2023-11-19) 14 | 15 | [Compare changelog](https://github.com/blaind/bevy_text_mesh/compare/v0.7.0...v0.8.0) 16 | 17 | ### Changed 18 | 19 | - [Upgrade bevy to 0.12.0][32] 20 | - Breaking: font must no longer be loaded with `#mesh` suffix 21 | 22 | ## Version 0.7.0 (2023-08-05) 23 | 24 | [Compare changelog](https://github.com/blaind/bevy_text_mesh/compare/v0.6.0...v0.7.0) 25 | 26 | This version was never published to crates.io. 27 | 28 | ### Changed 29 | 30 | - [Upgrade bevy to 0.11.0][29] 31 | 32 | ## Version 0.6.0 (2023-04-11) 33 | 34 | [Compare changelog](https://github.com/blaind/bevy_text_mesh/compare/v0.5.0...v0.6.0) 35 | 36 | ### Changed 37 | 38 | - Upgrade bevy to 0.10.0 39 | - Upgrade bitflags to 2.1.0 40 | 41 | ## Version 0.5.0 (2022-11-13) 42 | 43 | [Compare changelog](https://github.com/blaind/bevy_text_mesh/compare/v0.4.0...v0.5.0) 44 | 45 | ### Changed 46 | 47 | - Upgrade bevy to 0.9.0 48 | 49 | ## Version 0.4.0 (2022-10-24) 50 | 51 | [Compare changelog](https://github.com/blaind/bevy_text_mesh/compare/v0.3.0...v0.4.0) 52 | 53 | ### Changed 54 | 55 | - [Breaking: use #mesh label for font loading to allow interop with bevy `.ttf` AssetLoader][15] 56 | 57 | ## Version 0.3.0 (2022-08-25) 58 | 59 | [Compare changelog](https://github.com/blaind/bevy_text_mesh/compare/v0.2.0...v0.3.0) 60 | 61 | ### Changed 62 | 63 | - Upgrade bevy to 0.8.0 64 | 65 | ## Version 0.2.0 (2022-04-17) 66 | 67 | [Compare changelog](https://github.com/blaind/bevy_text_mesh/compare/v0.1.0...v0.2.0) 68 | 69 | ### Changed 70 | 71 | - Upgrade bevy to 0.7.0 72 | 73 | [15]: https://github.com/blaind/bevy_text_mesh/pull/15 74 | [29]: https://github.com/blaind/bevy_text_mesh/pull/29 75 | [32]: https://github.com/blaind/bevy_text_mesh/pull/32 76 | [34]: https://github.com/blaind/bevy_text_mesh/pull/34 77 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_text_mesh" 3 | version = "0.9.0" 4 | edition = "2021" 5 | description = "A bevy 3D text mesh generator for displaying text" 6 | repository = "https://github.com/blaind/bevy_text_mesh" 7 | keywords = ["bevy"] 8 | categories = ["game-engines"] 9 | license = "MIT" 10 | readme = "README.md" 11 | 12 | [dependencies] 13 | ttf2mesh = "0.2.0" 14 | bitflags = "2.1" 15 | anyhow = "1.0" 16 | glyph_brush_layout = "0.2.3" 17 | 18 | [dependencies.bevy] 19 | version = "0.12.0" 20 | default-features = false 21 | features = ["bevy_render", "bevy_text", "bevy_pbr", "bevy_asset", "bevy_sprite"] 22 | 23 | [dev-dependencies] 24 | rand = "0.8.4" 25 | 26 | [dev-dependencies.bevy] 27 | version = "0.12.0" 28 | default-features = false 29 | features = [ 30 | "bevy_winit", 31 | "bevy_render", 32 | "bevy_text", 33 | "bevy_pbr", 34 | "bevy_core_pipeline", 35 | "bevy_sprite", 36 | "x11", 37 | "tonemapping_luts", 38 | ] 39 | 40 | [features] 41 | default = [] 42 | unstable = [] 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bevy_text_mesh   [![Build Status]][actions] [![Latest Version]][crates.io] [![Docs Version]][docs] 2 | 3 | [build status]: https://img.shields.io/github/actions/workflow/status/blaind/bevy_text_mesh/ci.yml?branch=main 4 | [actions]: https://github.com/blaind/bevy_text_mesh/actions?query=branch%3Amain 5 | [latest version]: https://img.shields.io/crates/v/bevy_text_mesh.svg 6 | [crates.io]: https://crates.io/crates/bevy_text_mesh 7 | [docs version]: https://docs.rs/bevy_text_mesh/badge.svg 8 | [docs]: https://docs.rs/bevy_text_mesh 9 | 10 | A bevy 3D text mesh generator plugin for displaying text in 3D scenes 11 | 12 | ![Example](docs/highlight.webp) 13 | 14 | The text mesh is generated at runtime from runtime-tessellated (and cached) TrueType font glyphs. Tessellation of glyphs is done with C-based [github.com/fetisov/ttf2mesh](https://github.com/fetisov/ttf2mesh/) library that is being interfaced through Rust-based FFI API (see [ttf2glyph-rs](https://crates.io/crates/ttf2mesh)). 15 | 16 | ## Known limitations 17 | 18 | Consider this as a preview of the plugin for gathering feedback about the API: 19 | 20 | - **The API will change in future - still iterating** 21 | - Multiple `TextMesh` configuration fields are not implemented yet, see example below 22 | - Text color update is not implemented yet 23 | - Spacing of characters are incorrect 24 | - Mesh cache purging is not implemented - this implementation will leak memory (see [#2](https://github.com/blaind/bevy_text_mesh/issues/2)) 25 | - WASM builds are not supported (see [#11](https://github.com/blaind/bevy_text_mesh/issues/11)) 26 | 27 | ## Bevy versions support table 28 | 29 | | bevy | bevy_text_mesh | 30 | | ---- | -------------- | 31 | | 0.12 | 0.9.0 | 32 | | 0.11 | 0.7.0 | 33 | | 0.10 | 0.6.0 | 34 | | 0.9 | 0.5.0 | 35 | | 0.8 | 0.4.0 | 36 | | 0.7 | 0.2.0 | 37 | | 0.6 | 0.1.0 | 38 | | 0.5 | 0.0.2 | 39 | 40 | ## Usage 41 | 42 | ## Prequisites 43 | 44 | Prequisites (for compiling [ttf2mesh-rs](https://crates.io/crates/ttf2mesh)): 45 | 46 | apt-get install build-essential patch 47 | 48 | ## Running the examples 49 | 50 | See the [examples](/examples) -folder. 51 | 52 | ``` 53 | git clone https://github.com/blaind/bevy_text_mesh.git 54 | cd bevy_text_mesh 55 | cargo run --example 3d_scene --release # or 56 | cargo run --example performance --release 57 | ``` 58 | 59 | ## Integrating to your Bevy App 60 | 61 | Add to Cargo.toml: 62 | 63 | ``` 64 | [dependencies] 65 | bevy_text_mesh = "0.9.0" 66 | ``` 67 | 68 | Include the library: 69 | 70 | ```rust 71 | use bevy_text_mesh::prelude::*; 72 | ``` 73 | 74 | Second, add a `TextMeshPlugin` to your app: 75 | 76 | ```rust 77 | App::new() 78 | ... 79 | .add_plugins(TextMeshPlugin) 80 | ...; 81 | ``` 82 | 83 | Then, add the desired TrueType-fonts (with suffix `.ttf`) into your assets folder, a good convention is to store them to `assets/fonts` folder. 84 | 85 | For example, see Fira fonts. Please read also their [LICENSE](https://github.com/mozilla/Fira/blob/master/LICENSE). 86 | 87 | mkdir -p assets/fonts 88 | wget https://github.com/mozilla/Fira/raw/master/ttf/FiraSans-Medium.ttf -O assets/fonts/FiraSans-Medium.ttf 89 | 90 | Next, you are ready to spawn a text in your scene at a system: 91 | 92 | First, load a font asset: 93 | 94 | ```rust 95 | let font: Handle = asset_server.load("fonts/FiraSans-Medium.ttf#mesh"); 96 | ``` 97 | 98 | Then, spawn a textmesh bundle: 99 | 100 | ```rust 101 | commands.spawn(TextMeshBundle { 102 | text_mesh: TextMesh::new_with_color("Hello Bevy", font, Color::rgb(1., 1., 0.)), 103 | transform: Transform::from_xyz(-1., 1.75, 0.), 104 | ..Default::default() 105 | }); 106 | ``` 107 | 108 | Or with expanded syntax: 109 | 110 | ```rust 111 | commands.spawn(TextMeshBundle { 112 | text_mesh: TextMesh { 113 | text: String::from("Hello Bevy!"), 114 | style: TextMeshStyle { 115 | font, 116 | font_size: SizeUnit::NonStandard(36.), 117 | color: Color::rgb(1.0, 1.0, 0.0), 118 | font_style: FontStyle::UPPERCASE, // only UPPERCASE & LOWERCASE implemented currently 119 | mesh_quality: Quality::Low, 120 | ..Default::default() 121 | }, 122 | alignment: TextMeshAlignment { 123 | vertical: VerticalAlign::Top, // FUNCTIONALITY NOT IMPLEMENTED YET - NO EFFECT 124 | horizontal: HorizontalAlign::Left, // FUNCTIONALITY NOT IMPLEMENTED YET - NO EFFECT 125 | ..Default::default() 126 | }, 127 | size: TextMeshSize { 128 | width: SizeUnit::NonStandard(135.), // partially implemented 129 | height: SizeUnit::NonStandard(50.), // partially implemented 130 | depth: Some(SizeUnit::NonStandard(50.0)), // must be > 0 currently, 2d mesh not supported yet 131 | wrapping: true, // partially implemented 132 | overflow: false, // NOT IMPLEMENTED YET 133 | ..Default::default() 134 | }, 135 | ..Default::default() 136 | }, 137 | transform: Transform { 138 | translation: Vec3::new(-1., 1.75, 0.), 139 | ..Default::default() 140 | }, 141 | ..Default::default() 142 | }); 143 | ``` 144 | 145 | ## License 146 | 147 | Licensed under MIT license 148 | 149 | ### Contribution 150 | 151 | Unless you explicitly state otherwise, any contribution intentionally submitted 152 | for inclusion in the software by you, shall be licensed as above, without any additional terms or conditions. 153 | -------------------------------------------------------------------------------- /assets/fonts/FiraMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blaind/bevy_text_mesh/677f8f8638a6f5f42982b1dac50fa6c6e01430f5/assets/fonts/FiraMono-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/LICENSE: -------------------------------------------------------------------------------- 1 | Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /assets/fonts/README.md: -------------------------------------------------------------------------------- 1 | # Example fonts 2 | 3 | These fonts are downloaded from [Mozilla Fira](https://github.com/mozilla/Fira) project, and are covered under separate license. See [LICENSE](LICENSE). -------------------------------------------------------------------------------- /docs/highlight.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blaind/bevy_text_mesh/677f8f8638a6f5f42982b1dac50fa6c6e01430f5/docs/highlight.webp -------------------------------------------------------------------------------- /examples/2d_text.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_text_mesh::TextMeshPlugin; 3 | 4 | fn main() { 5 | App::new() 6 | .add_plugins((DefaultPlugins, TextMeshPlugin)) // TextMeshPlugin for interop check 7 | .add_systems(Startup, setup) 8 | .add_systems(Update, animate_rotation) 9 | .run(); 10 | } 11 | 12 | #[derive(Component)] 13 | struct AnimateRotation; 14 | 15 | fn setup(mut commands: Commands, asset_server: Res) { 16 | let font = asset_server.load("fonts/FiraMono-Medium.ttf"); 17 | let text_style = TextStyle { 18 | font, 19 | font_size: 60.0, 20 | color: Color::WHITE, 21 | }; 22 | let text_alignment = TextAlignment::Center; 23 | 24 | commands.spawn(Camera2dBundle::default()); 25 | commands 26 | .spawn(Text2dBundle { 27 | text: Text::from_section("standard 2d text works too", text_style.clone()) 28 | .with_alignment(text_alignment), 29 | ..default() 30 | }) 31 | .insert(AnimateRotation); 32 | } 33 | 34 | fn animate_rotation( 35 | time: Res