├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── LICENSE.txt └── NotoSans-Regular.ttf ├── examples ├── assets │ ├── LICENSE.txt │ └── Ubuntu-R.ttf └── styles.rs ├── scripts ├── id_rsa.enc └── travis-doc-upload.cfg └── src ├── font.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /Cargo.lock 3 | /.cargo/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | script: 7 | - cargo build -v 8 | - cargo test -v 9 | - cargo doc -v 10 | after_success: 11 | - curl http://docs.piston.rs/travis-doc-upload.sh | sh 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gfx_text" 3 | version = "0.33.0" 4 | authors = ["Kagami Hiiragi "] 5 | description = "Draw text for gfx using freetype." 6 | keywords = ["gfx", "graphics", "text", "freetype", "font"] 7 | license = "MIT" 8 | readme = "README.md" 9 | homepage = "https://github.com/PistonDevelopers/gfx_text" 10 | repository = "https://github.com/PistonDevelopers/gfx_text.git" 11 | documentation = "http://docs.piston.rs/gfx_text/gfx_text/" 12 | 13 | [dependencies] 14 | gfx = "0.18.1" 15 | freetype-rs = "0.34.0" 16 | 17 | [features] 18 | default = ["include-font"] 19 | include-font = [] 20 | 21 | ### For examples 22 | 23 | [dev-dependencies] 24 | piston_window = "0.131.0" 25 | 26 | #log = "*" 27 | #env_logger = "*" 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 PistonDevelopers 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 | # gfx_text [![Build Status](https://travis-ci.org/PistonDevelopers/gfx_text.png?branch=master)](https://travis-ci.org/PistonDevelopers/gfx_text) [![crates.io](https://img.shields.io/crates/v/gfx_text.svg)](https://crates.io/crates/gfx_text) 2 | 3 | Library for drawing text for [gfx-rs](https://github.com/gfx-rs/gfx-rs) graphics API. Uses [freetype-rs](https://github.com/PistonDevelopers/freetype-rs) underneath. 4 | 5 | ## Usage 6 | 7 | Basic usage: 8 | 9 | ```rust 10 | // Initialize text renderer. 11 | let mut text = gfx_text::new(factory).build().unwrap(); 12 | 13 | // In render loop: 14 | 15 | // Add some text 10 pixels down and right from the top left screen corner. 16 | text.add( 17 | "The quick brown fox jumps over the lazy dog", // Text to add 18 | [10, 10], // Position 19 | [0.65, 0.16, 0.16, 1.0], // Text color 20 | ); 21 | 22 | // Draw text. 23 | text.draw(&mut stream); 24 | ``` 25 | 26 | See [API documentation](http://docs.piston.rs/gfx_text/gfx_text/) for overview of all available methods. 27 | 28 | You can skip default font by disabling `include-font` feature: 29 | 30 | ``` 31 | [dependencies.gfx_text] 32 | version = "*" 33 | default-features = false 34 | ``` 35 | 36 | ## Examples 37 | 38 | See [this example](./examples/styles.rs) on how to draw text in various styles: different sizes, colors, fonts, etc. 39 | 40 | Output: 41 | 42 | [![](https://raw.githubusercontent.com/PistonDevelopers/gfx_text/images/styles.png)](https://raw.githubusercontent.com/PistonDevelopers/gfx_text/images/styles.png) 43 | 44 | ## License 45 | 46 | * gfx_text licensed under [MIT License](./LICENSE) 47 | * Included by default Noto Sans font uses [Apache License 2.0](./assets/LICENSE.txt) 48 | * Ubuntu Font used in examples has [Ubuntu Font Licence 1.0](./examples/assets/LICENSE.txt) 49 | -------------------------------------------------------------------------------- /assets/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /assets/NotoSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PistonDevelopers/gfx_text/35ec06b9b18415e18f32e7037c58d952ec65c5b5/assets/NotoSans-Regular.ttf -------------------------------------------------------------------------------- /examples/assets/LICENSE.txt: -------------------------------------------------------------------------------- 1 | ------------------------------- 2 | UBUNTU FONT LICENCE Version 1.0 3 | ------------------------------- 4 | 5 | PREAMBLE 6 | This licence allows the licensed fonts to be used, studied, modified and 7 | redistributed freely. The fonts, including any derivative works, can be 8 | bundled, embedded, and redistributed provided the terms of this licence 9 | are met. The fonts and derivatives, however, cannot be released under 10 | any other licence. The requirement for fonts to remain under this 11 | licence does not require any document created using the fonts or their 12 | derivatives to be published under this licence, as long as the primary 13 | purpose of the document is not to be a vehicle for the distribution of 14 | the fonts. 15 | 16 | DEFINITIONS 17 | "Font Software" refers to the set of files released by the Copyright 18 | Holder(s) under this licence and clearly marked as such. This may 19 | include source files, build scripts and documentation. 20 | 21 | "Original Version" refers to the collection of Font Software components 22 | as received under this licence. 23 | 24 | "Modified Version" refers to any derivative made by adding to, deleting, 25 | or substituting -- in part or in whole -- any of the components of the 26 | Original Version, by changing formats or by porting the Font Software to 27 | a new environment. 28 | 29 | "Copyright Holder(s)" refers to all individuals and companies who have a 30 | copyright ownership of the Font Software. 31 | 32 | "Substantially Changed" refers to Modified Versions which can be easily 33 | identified as dissimilar to the Font Software by users of the Font 34 | Software comparing the Original Version with the Modified Version. 35 | 36 | To "Propagate" a work means to do anything with it that, without 37 | permission, would make you directly or secondarily liable for 38 | infringement under applicable copyright law, except executing it on a 39 | computer or modifying a private copy. Propagation includes copying, 40 | distribution (with or without modification and with or without charging 41 | a redistribution fee), making available to the public, and in some 42 | countries other activities as well. 43 | 44 | PERMISSION & CONDITIONS 45 | This licence does not grant any rights under trademark law and all such 46 | rights are reserved. 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a 49 | copy of the Font Software, to propagate the Font Software, subject to 50 | the below conditions: 51 | 52 | 1) Each copy of the Font Software must contain the above copyright 53 | notice and this licence. These can be included either as stand-alone 54 | text files, human-readable headers or in the appropriate machine- 55 | readable metadata fields within text or binary files as long as those 56 | fields can be easily viewed by the user. 57 | 58 | 2) The font name complies with the following: 59 | (a) The Original Version must retain its name, unmodified. 60 | (b) Modified Versions which are Substantially Changed must be renamed to 61 | avoid use of the name of the Original Version or similar names entirely. 62 | (c) Modified Versions which are not Substantially Changed must be 63 | renamed to both (i) retain the name of the Original Version and (ii) add 64 | additional naming elements to distinguish the Modified Version from the 65 | Original Version. The name of such Modified Versions must be the name of 66 | the Original Version, with "derivative X" where X represents the name of 67 | the new work, appended to that name. 68 | 69 | 3) The name(s) of the Copyright Holder(s) and any contributor to the 70 | Font Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except (i) as required by this licence, (ii) to 72 | acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with 73 | their explicit written permission. 74 | 75 | 4) The Font Software, modified or unmodified, in part or in whole, must 76 | be distributed entirely under this licence, and must not be distributed 77 | under any other licence. The requirement for fonts to remain under this 78 | licence does not affect any document created using the Font Software, 79 | except any version of the Font Software extracted from a document 80 | created using the Font Software may only be distributed under this 81 | licence. 82 | 83 | TERMINATION 84 | This licence becomes null and void if any of the above conditions are 85 | not met. 86 | 87 | DISCLAIMER 88 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 89 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 90 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF 91 | COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 92 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 93 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 94 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 95 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER 96 | DEALINGS IN THE FONT SOFTWARE. 97 | -------------------------------------------------------------------------------- /examples/assets/Ubuntu-R.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PistonDevelopers/gfx_text/35ec06b9b18415e18f32e7037c58d952ec65c5b5/examples/assets/Ubuntu-R.ttf -------------------------------------------------------------------------------- /examples/styles.rs: -------------------------------------------------------------------------------- 1 | extern crate piston_window; 2 | extern crate gfx_text; 3 | 4 | use piston_window::*; 5 | use gfx_text::{HorizontalAnchor, VerticalAnchor}; 6 | 7 | const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0]; 8 | const BROWN: [f32; 4] = [0.65, 0.16, 0.16, 1.0]; 9 | const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0]; 10 | const BLUE: [f32; 4] = [0.0, 0.0, 1.0, 1.0]; 11 | const FONT_PATH: &'static str = "examples/assets/Ubuntu-R.ttf"; 12 | 13 | fn main() { 14 | let title = "gfx_text example"; 15 | let mut window: PistonWindow = WindowSettings::new(title, [640, 480]) 16 | .exit_on_esc(true) 17 | .build() 18 | .unwrap_or_else(|e| panic!("Failed to build PistonWindow: {}", e)); 19 | 20 | let mut encoder: GfxEncoder = window.factory.create_command_buffer().into(); 21 | 22 | let hdpi = window.draw_size().width / window.size().width; 23 | 24 | let mut normal_text = gfx_text::new(window.factory.clone()) 25 | .with_size((16.0 * hdpi) as u8).unwrap(); 26 | let mut big_text = gfx_text::new(window.factory.clone()) 27 | .with_size((20.0 * hdpi) as u8).unwrap(); 28 | let mut custom_font_text = gfx_text::new(window.factory.clone()) 29 | .with_size((25.0 * hdpi) as u8) 30 | .with_font(FONT_PATH) 31 | .unwrap(); 32 | 33 | let main_color = window.output_color.clone(); 34 | let mut counter: u32 = 0; 35 | 36 | while let Some(e) = window.next() { 37 | window.draw_3d(&e, |window| { 38 | let pos = |p: [i32; 2]| [(p[0] as f64 * hdpi) as i32, (p[1] as f64 * hdpi) as i32]; 39 | 40 | encoder.clear(&main_color, WHITE); 41 | 42 | counter += 1; 43 | 44 | normal_text.add("The quick brown fox jumps over the lazy dog", pos([10, 10]), BROWN); 45 | normal_text.add("The quick red fox jumps over the lazy dog", pos([30, 30]), RED); 46 | normal_text.add_anchored("hello centred world", pos([320, 240]), HorizontalAnchor::Center, VerticalAnchor::Center, BLUE); 47 | normal_text.add_anchored(&format!("Count: {}", counter), pos([0, 479]), HorizontalAnchor::Left, VerticalAnchor::Bottom, BLUE); 48 | 49 | big_text.add("The big brown fox jumps over the lazy dog", pos([50, 50]), BROWN); 50 | 51 | custom_font_text.add("The custom blue fox jumps over the lazy dog", pos([10, 80]), BLUE); 52 | custom_font_text.add_anchored("I live in the bottom right", pos([639, 479]), HorizontalAnchor::Right, VerticalAnchor::Bottom, RED); 53 | 54 | normal_text.draw(&mut encoder, &main_color).unwrap(); 55 | big_text.draw(&mut encoder, &main_color).unwrap(); 56 | custom_font_text.draw(&mut encoder, &main_color).unwrap(); 57 | 58 | encoder.flush(&mut window.device); 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /scripts/id_rsa.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PistonDevelopers/gfx_text/35ec06b9b18415e18f32e7037c58d952ec65c5b5/scripts/id_rsa.enc -------------------------------------------------------------------------------- /scripts/travis-doc-upload.cfg: -------------------------------------------------------------------------------- 1 | PROJECT_NAME=gfx_text 2 | DOCS_REPO=PistonDevelopers/docs.git 3 | SSH_KEY_TRAVIS_ID=9edffcb6a5bc 4 | -------------------------------------------------------------------------------- /src/font.rs: -------------------------------------------------------------------------------- 1 | //! Construct bitmap font using FreeType library. 2 | //! Generates raw texture data for the given font and collects information 3 | //! about available font characters to map them into texture. 4 | 5 | use std::cmp::max; 6 | use std::iter::{repeat, FromIterator}; 7 | use std::collections::{HashMap, HashSet}; 8 | use std::char::from_u32; 9 | use ::freetype as ft; 10 | use ::freetype::Error as FreetypeError; 11 | use ::freetype::Face; 12 | 13 | #[derive(Debug)] 14 | pub struct BitmapFont { 15 | width: u16, 16 | height: u16, 17 | chars: HashMap, 18 | image: Vec, 19 | font_height: u16, 20 | } 21 | 22 | #[derive(Debug)] 23 | pub struct BitmapChar { 24 | // Real glyph's coordinates in pixels. 25 | pub x_offset: i32, 26 | pub y_offset: i32, 27 | pub x_advance: i32, 28 | pub width: i32, 29 | pub height: i32, 30 | // Precalculated scaled positions in texture. 31 | pub tex: [f32; 2], 32 | pub tex_width: f32, 33 | pub tex_height: f32, 34 | // This field is used only while building the texture. 35 | data: Option>, 36 | } 37 | 38 | /// Represents possible errors which may occur during the font loading. 39 | #[derive(Debug)] 40 | pub enum FontError { 41 | /// No font was specified 42 | NoFont, 43 | /// Character set is empty 44 | EmptyFont, 45 | /// FreeType library error 46 | FreetypeError(FreetypeError), 47 | } 48 | 49 | impl From for FontError { 50 | fn from(e: FreetypeError) -> FontError { FontError::FreetypeError(e) } 51 | } 52 | 53 | pub type FontResult = Result; 54 | 55 | impl BitmapFont { 56 | pub fn from_path(path: &str, font_size: u8, chars: Option<&[char]>) -> FontResult { 57 | let library = ft::Library::init()?; 58 | let face = library.new_face(path, 0)?; 59 | Self::new(face, font_size, chars) 60 | } 61 | 62 | pub fn from_bytes(data: &[u8], font_size: u8, chars: Option<&[char]>) -> FontResult { 63 | use std::rc::Rc; 64 | 65 | let library = ft::Library::init()?; 66 | let face = library.new_memory_face(Rc::new(data.into()), 0)?; 67 | Self::new(face, font_size, chars) 68 | } 69 | 70 | fn get_all_face_chars(face: &mut Face) -> HashSet { 71 | let mut result = HashSet::new(); 72 | let mut index = 0; 73 | let face_ptr = face.raw_mut(); 74 | unsafe { 75 | let mut code = ft::ffi::FT_Get_First_Char(face_ptr, &mut index); 76 | while index != 0 { 77 | from_u32(code as u32).map(|ch| result.insert(ch)); 78 | code = ft::ffi::FT_Get_Next_Char(face_ptr, code, &mut index); 79 | } 80 | } 81 | result 82 | } 83 | 84 | // FIXME(Kagami): Profile and optimize this function! 85 | // TODO(Kagami): Limit too huge textures? We should keep it less than 86 | // 8k X 8k in general. 87 | // TODO(Kagami): Add bunch of asserts to check for negative values and 88 | // overflows. 89 | /// Construct new BitMap font using provided parameters (this is general 90 | /// method, called via `from_` helpers). 91 | fn new(mut face: ft::Face, font_size: u8, chars: Option<&[char]>) -> FontResult { 92 | let needed_chars = chars 93 | .map(|sl| HashSet::from_iter(sl.iter().cloned())) 94 | .unwrap_or_else(|| Self::get_all_face_chars(&mut face)); 95 | if needed_chars.is_empty() { 96 | return Err(FontError::EmptyFont); 97 | } 98 | 99 | face.set_pixel_sizes(0, font_size as u32)?; 100 | 101 | // FreeType representation of rendered glyph 'j': 102 | // 103 | // b_left w 104 | // +-----+-----+-----+ 105 | // | | | | font_size - bitmap_top() 106 | // +-----+-----+-----+ 107 | // | | x | | 108 | // | | | | 109 | // | | x | | bitmap_top() 110 | // | | x | | 111 | // | | x | | 112 | // | | x | | 113 | // +-----+--x--+-----+ 114 | // | | x | | rows() - bitmap_top() 115 | // | |x | | 116 | // +-----------+-----+ 117 | // advance.x 118 | // 119 | // (Read 120 | // for more details.) 121 | // 122 | // Notes: 123 | // * Width/height of the rendered glyph generally smaller than the the 124 | // specified font size 125 | // * But if we add x/y offsets to the real glyph's dimensions it might 126 | // go beyound that limits (e.g. chars like 'j', 'q') 127 | // * `bottom_left()` may be less than zero for some tight characters 128 | // (too push it to the previous one) 129 | // * Theoretically `bitmap_top()` may be bigger than the `font_size` 130 | // 131 | // For simplicity we use fixed box height to store characters in the 132 | // texture (extended with blank pixels downwards), but width may vary: 133 | // 134 | // width() 135 | // +-----+-------+ 136 | // | x | x | 137 | // | | | 138 | // | x | x | 139 | // | x | x | rows() 140 | // | x | x | 141 | // | x | x | 142 | // +-----+ x | 143 | // | | x | 144 | // | +-------+ 145 | // | | | ch_box_height - rows() 146 | // +-----+-------+ 147 | // 148 | // To construct the optimal texture (i.e. square enought and with box 149 | // height of the highest character) we need to do several passes. 150 | 151 | // In first pass we collect information about the chars and store their 152 | // raw bitmap data. It gives us max character height and summary width 153 | // of all characters. 154 | 155 | let chars_len = needed_chars.len(); 156 | let mut chars_info = HashMap::with_capacity(chars_len); 157 | let mut sum_image_width = 0; 158 | let mut max_ch_width = 0; 159 | let mut ch_box_height = 0; 160 | 161 | // debug!("Start building the bitmap (chars: {})", chars_len); 162 | 163 | for ch in needed_chars { 164 | face.load_char(ch as usize, ft::face::LoadFlag::RENDER)?; 165 | let glyph = face.glyph(); 166 | let bitmap = glyph.bitmap(); 167 | let ch_width = bitmap.width(); 168 | let ch_height = bitmap.rows(); 169 | let ch_x_offset = glyph.bitmap_left(); 170 | let ch_y_offset = font_size as i32 - glyph.bitmap_top(); 171 | let ch_x_advance = (glyph.advance().x >> 6) as i32; 172 | let buffer = bitmap.buffer(); 173 | let ch_data = Vec::from(buffer); 174 | 175 | chars_info.insert(ch, BitmapChar { 176 | x_offset: ch_x_offset, 177 | y_offset: ch_y_offset, 178 | x_advance: ch_x_advance, 179 | width: ch_width, 180 | height: ch_height, 181 | // We'll need to fix that fields later: 182 | tex: [0.0, 0.0], 183 | tex_width: 0.0, 184 | tex_height: 0.0, 185 | data: Some(ch_data), 186 | }); 187 | 188 | sum_image_width += ch_width; 189 | max_ch_width = max(max_ch_width, ch_width); 190 | ch_box_height = max(ch_box_height, ch_height); 191 | } 192 | 193 | // In second pass we map character boxes with varying width onto the 194 | // fixed quad texture image and build the final texture image. 195 | // 196 | // We start with optimist (square) assumption about texture dimensions 197 | // and adjust the image's height and size while filling the rows. 198 | // 199 | // TODO(Kagami): We may try some cool CS algorithm to fit char boxes 200 | // into the quad texture space with the best level of compression. 201 | // Though current level of inefficiency is good enough. 202 | 203 | let ideal_image_size = sum_image_width * ch_box_height; 204 | let ideal_image_width = (ideal_image_size as f32).sqrt() as i32; 205 | let image_width = max(max_ch_width, ideal_image_width); 206 | let assumed_size = ideal_image_size as f32 * 1.5; 207 | let assumed_ch_in_row = image_width as f32 / max_ch_width as f32; 208 | let mut image = Vec::with_capacity(assumed_size as usize); 209 | let mut chars_row = Vec::with_capacity(assumed_ch_in_row as usize); 210 | let mut cursor_x = 0; 211 | let mut image_height = 0; 212 | 213 | let dump_row = |image: &mut Vec, chars_row: &Vec<(i32, i32, Vec)>| { 214 | // Copy character data into the image row by row: 215 | // 216 | // image_width 217 | // +-------+---------+---+ 218 | // | x | x | | 219 | // | | | | 220 | // | x | x | | ch_box_height 221 | // | x | x | | 222 | // | x | x | | 223 | // | x | x | | 224 | // | | x | | 225 | // +-------+---------+---+ 226 | // ^--- image_width - width_ch_i - width_ch_j 227 | for i in 0..ch_box_height { 228 | let mut x = 0; 229 | for &(width, height, ref data) in chars_row { 230 | if i >= height { 231 | image.extend(repeat(0).take(width as usize)); 232 | } else { 233 | let skip = i * width; 234 | debug_assert!(data.len() >= (skip + width) as usize); 235 | let line = data.iter().skip(skip as usize).take(width as usize); 236 | image.extend(line.cloned()); 237 | }; 238 | x += width; 239 | } 240 | let cols_to_fill = image_width - x; 241 | image.extend(repeat(0).take(cols_to_fill as usize)); 242 | } 243 | }; 244 | 245 | // debug!("Placing chars onto a plane"); 246 | 247 | // Hashmap doesn't preserve the order but we don't need it anyway. 248 | for (_, ch_info) in chars_info.iter_mut() { 249 | if cursor_x + ch_info.width > image_width { 250 | dump_row(&mut image, &chars_row); 251 | chars_row.clear(); 252 | cursor_x = 0; 253 | image_height += ch_box_height; 254 | } 255 | let ch_data = ch_info.data.take().unwrap(); 256 | chars_row.push((ch_info.width, ch_info.height, ch_data)); 257 | ch_info.tex = [cursor_x as f32, image_height as f32]; 258 | cursor_x += ch_info.width; 259 | } 260 | dump_row(&mut image, &chars_row); 261 | image_height += ch_box_height; 262 | 263 | // Finally, we just precalculate some fields to make it easier to use 264 | // our font. 265 | 266 | for (_, ch_info) in chars_info.iter_mut() { 267 | ch_info.tex[0] /= image_width as f32; 268 | ch_info.tex[1] /= image_height as f32; 269 | ch_info.tex_width = ch_info.width as f32 / image_width as f32; 270 | ch_info.tex_height = ch_info.height as f32 / image_height as f32; 271 | } 272 | 273 | // info!("Image width: {}, image height: {}, total size: {}", 274 | // image_width, image_height, image.len()); 275 | 276 | Ok(BitmapFont { 277 | width: image_width as u16, 278 | height: image_height as u16, 279 | chars: chars_info, 280 | image: image, 281 | font_height: (face.size_metrics().unwrap().height >> 6) as u16, 282 | }) 283 | } 284 | 285 | pub fn get_width(&self) -> u16 { 286 | self.width 287 | } 288 | 289 | pub fn get_height(&self) -> u16 { 290 | self.height 291 | } 292 | 293 | /// Return 8-bit texture raw data (grayscale). 294 | pub fn get_image(&self) -> &[u8] { 295 | &self.image 296 | } 297 | 298 | pub fn get_font_height(&self) -> u16 { 299 | self.font_height 300 | } 301 | 302 | pub fn find_char(&self, ch: char) -> Option<&BitmapChar> { 303 | self.chars.get(&ch) 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A library for drawing text for gfx-rs graphics API. 2 | //! Uses freetype-rs underneath to former the font bitmap texture and collect 3 | //! information about face glyphs. 4 | //! 5 | //! # Examples 6 | //! 7 | //! Basic usage: 8 | //! 9 | //! ```ignore 10 | //! // Initialize text renderer. 11 | //! let mut text = gfx_text::new(factory).build().unwrap(); 12 | //! 13 | //! // In render loop: 14 | //! 15 | //! // Add some text 10 pixels down and right from the top left screen corner. 16 | //! text.add( 17 | //! "The quick brown fox jumps over the lazy dog", // Text to add 18 | //! [10, 10], // Position 19 | //! [0.65, 0.16, 0.16, 1.0], // Text color 20 | //! ); 21 | //! 22 | //! // Draw text. 23 | //! text.draw(&mut stream); 24 | //! ``` 25 | 26 | #![deny(missing_docs)] 27 | 28 | // #[macro_use] 29 | // extern crate log; 30 | #[macro_use] 31 | extern crate gfx; 32 | extern crate freetype; 33 | 34 | use std::collections::hash_map::{Entry, HashMap}; 35 | use std::marker::PhantomData; 36 | use gfx::{CombinedError, CommandBuffer, Encoder, Factory, PipelineStateError, Resources, UpdateError}; 37 | use gfx::shade::ProgramError; 38 | use gfx::handle::{Buffer, RenderTargetView}; 39 | use gfx::pso::PipelineState; 40 | use gfx::texture; 41 | use gfx::traits::FactoryExt; 42 | mod font; 43 | use font::BitmapFont; 44 | pub use font::FontError; 45 | 46 | const DEFAULT_FONT_SIZE: u8 = 16; 47 | const DEFAULT_BUFFER_SIZE: usize = 128; 48 | const DEFAULT_OUTLINE_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0]; 49 | const DEFAULT_PROJECTION: [[f32; 4]; 4] = [ 50 | [1.0, 0.0, 0.0, 0.0], 51 | [0.0, 1.0, 0.0, 0.0], 52 | [0.0, 0.0, 1.0, 0.0], 53 | [0.0, 0.0, 0.0, 1.0], 54 | ]; 55 | 56 | #[cfg(feature = "include-font")] 57 | const DEFAULT_FONT_DATA: Option<&'static [u8]> = 58 | Some(include_bytes!("../assets/NotoSans-Regular.ttf")); 59 | #[cfg(not(feature = "include-font"))] 60 | const DEFAULT_FONT_DATA: Option<&'static [u8]> = 61 | None; 62 | 63 | /// General error type returned by the library. Wraps all other errors. 64 | #[derive(Debug)] 65 | pub enum Error { 66 | /// Font loading error 67 | FontError(FontError), 68 | /// Pipeline creation/update error 69 | PipelineError(PipelineStateError), 70 | /// Program shader error. 71 | ProgramError(ProgramError), 72 | /// An error occuring during creation of texture or resource view 73 | CombinedError(CombinedError), 74 | /// An error occuring in buffer/texture updates 75 | UpdateError(UpdateError), 76 | } 77 | 78 | /// An anchor aligns text horizontally to its given x position. 79 | #[derive(PartialEq)] 80 | pub enum HorizontalAnchor { 81 | /// Anchor the left edge of the text 82 | Left, 83 | /// Anchor the horizontal mid-point of the text 84 | Center, 85 | /// Anchor the right edge of the text 86 | Right, 87 | } 88 | 89 | /// An anchor aligns text vertically to its given y position. 90 | #[derive(PartialEq)] 91 | pub enum VerticalAnchor { 92 | /// Anchor the top edge of the text 93 | Top, 94 | /// Anchor the vertical mid-point of the text 95 | Center, 96 | /// Anchor the bottom edge of the text 97 | Bottom, 98 | } 99 | 100 | impl From for Error { 101 | fn from(e: FontError) -> Error { Error::FontError(e) } 102 | } 103 | 104 | impl From> for Error { 105 | fn from(e: PipelineStateError) -> Error { Error::PipelineError(e) } 106 | } 107 | 108 | impl From for Error { 109 | fn from(e: ProgramError) -> Error { Error::ProgramError(e) } 110 | } 111 | 112 | impl From for Error { 113 | fn from(e: CombinedError) -> Error { Error::CombinedError(e) } 114 | } 115 | 116 | impl From> for Error { 117 | fn from(e: UpdateError) -> Error { Error::UpdateError(e) } 118 | } 119 | 120 | type IndexT = u32; 121 | 122 | /// Text renderer. 123 | pub struct Renderer> { 124 | factory: F, 125 | pso_map: HashMap>, 126 | shaders: gfx::ShaderSet, 127 | vertex_data: Vec, 128 | vertex_buffer: Buffer, 129 | index_data: Vec, 130 | index_buffer: Buffer, 131 | font_bitmap: BitmapFont, 132 | color: (gfx::handle::ShaderResourceView, gfx::handle::Sampler), 133 | } 134 | 135 | /// Text renderer builder. Allows to set rendering options using builder 136 | /// pattern. 137 | /// 138 | /// # Examples 139 | /// 140 | /// ```ignore 141 | /// let mut text = gfx_text::RendererBuilder::new(factory) 142 | /// .with_size(25) 143 | /// .with_font("/path/to/font.ttf") 144 | /// .with_chars(&['a', 'b', 'c']) 145 | /// .build() 146 | /// .unwrap(); 147 | /// ``` 148 | pub struct RendererBuilder<'r, R: Resources, F: Factory> { 149 | factory: F, 150 | font_size: u8, 151 | // NOTE(Kagami): Better to use `P: AsRef` but since we store path in 152 | // the intermediate builder structure, Rust will unable to infer type 153 | // without manual annotation which is much worse. Anyway, it's possible to 154 | // just pass raw bytes. 155 | font_path: Option<&'r str>, 156 | font_data: Option<&'r [u8]>, 157 | outline_width: Option, 158 | outline_color: [f32; 4], 159 | buffer_size: usize, 160 | chars: Option<&'r [char]>, 161 | // XXX(Kagami): Shut up the Rust complains about unused R. We can't use 162 | // just `factory: &mut Factory` because it doesn't work with lifetimes 163 | // (complains about the Marker associated type). Is there any better way? 164 | _r: PhantomData, 165 | } 166 | 167 | /// Create a new text renderer builder. Alias for `RendererBuilder::new`. 168 | pub fn new<'r, R: Resources, F: Factory>(factory: F) -> RendererBuilder<'r, R, F> { 169 | RendererBuilder::new(factory) 170 | } 171 | 172 | impl<'r, R: Resources, F: Factory> RendererBuilder<'r, R, F> { 173 | /// Create a new text renderer builder. 174 | pub fn new(factory: F) -> Self { 175 | // Default renderer settings. 176 | RendererBuilder { 177 | factory: factory, 178 | font_size: DEFAULT_FONT_SIZE, 179 | font_path: None, // Default font will be used 180 | font_data: DEFAULT_FONT_DATA, 181 | outline_width: None, // No outline by default 182 | outline_color: DEFAULT_OUTLINE_COLOR, 183 | buffer_size: DEFAULT_BUFFER_SIZE, 184 | chars: None, // Place all available font chars into texture 185 | _r: PhantomData, 186 | } 187 | } 188 | 189 | /// Specify custom size. 190 | pub fn with_size(mut self, size: u8) -> Self { 191 | self.font_size = size; 192 | self 193 | } 194 | 195 | /// Specify custom font by path. 196 | pub fn with_font(mut self, path: &'r str) -> Self { 197 | self.font_path = Some(path); 198 | self 199 | } 200 | 201 | /// Pass raw font data. 202 | pub fn with_font_data(mut self, data: &'r [u8]) -> Self { 203 | self.font_data = Some(data); 204 | self 205 | } 206 | 207 | /// Specify outline width and color. 208 | /// **Not implemented yet.** 209 | pub fn with_outline(mut self, width: u8, color: [f32; 4]) -> Self { 210 | self.outline_width = Some(width); 211 | self.outline_color = color; 212 | self 213 | } 214 | 215 | /// Specify custom initial buffer size. 216 | pub fn with_buffer_size(mut self, size: usize) -> Self { 217 | self.buffer_size = size; 218 | self 219 | } 220 | 221 | /// Make available only provided characters in font texture instead of 222 | /// loading all existing from the font face. 223 | pub fn with_chars(mut self, chars: &'r [char]) -> Self { 224 | self.chars = Some(chars); 225 | self 226 | } 227 | 228 | /// Build a new text renderer instance using current settings. 229 | pub fn build(mut self) -> Result, Error> { 230 | use gfx::buffer; 231 | use gfx::memory; 232 | 233 | let vertex_buffer = self.factory.create_buffer( 234 | self.buffer_size, 235 | buffer::Role::Vertex, 236 | memory::Usage::Dynamic, 237 | memory::Bind::empty() 238 | ).expect("Could not create vertex buffer"); 239 | let index_buffer = self.factory.create_buffer( 240 | self.buffer_size, 241 | buffer::Role::Index, 242 | memory::Usage::Dynamic, 243 | memory::Bind::empty() 244 | ).expect("Count not create index buffer"); 245 | 246 | // Initialize bitmap font. 247 | // TODO(Kagami): Outline! 248 | // TODO(Kagami): More granulated font settings, e.g. antialiasing, 249 | // hinting, kerning, etc. 250 | let font_bitmap = match self.font_path { 251 | Some(path) => 252 | BitmapFont::from_path(path, self.font_size, self.chars), 253 | None => match self.font_data { 254 | Some(data) => BitmapFont::from_bytes(data, self.font_size, self.chars), 255 | None => Err(FontError::NoFont), 256 | }, 257 | }?; 258 | let font_texture = create_texture_r8_static( 259 | &mut self.factory, 260 | font_bitmap.get_width(), 261 | font_bitmap.get_height(), 262 | font_bitmap.get_image(), 263 | )?; 264 | let sampler = self.factory.create_sampler( 265 | texture::SamplerInfo::new(texture::FilterMethod::Bilinear, 266 | texture::WrapMode::Clamp) 267 | ); 268 | 269 | let shaders = self.factory.create_shader_set(VERTEX_SRC, FRAGMENT_SRC)?; 270 | 271 | Ok(Renderer { 272 | factory: self.factory, 273 | pso_map: HashMap::new(), 274 | shaders: shaders, 275 | vertex_data: Vec::new(), 276 | vertex_buffer: vertex_buffer, 277 | index_data: Vec::new(), 278 | index_buffer: index_buffer, 279 | font_bitmap: font_bitmap, 280 | color: (font_texture, sampler), 281 | }) 282 | } 283 | 284 | /// Just an alias for `builder.build().unwrap()`. 285 | pub fn unwrap(self) -> Renderer { 286 | self.build().unwrap() 287 | } 288 | } 289 | 290 | impl> Renderer { 291 | fn prepare_pso(&mut self, format: gfx::format::Format) -> Result<(), Error> { 292 | Ok(if let Entry::Vacant(e) = self.pso_map.entry(format) { 293 | let init = pipe::Init { 294 | vbuf: (), 295 | screen_size: "u_Screen_Size", 296 | proj: "u_Proj", 297 | color: "t_Color", 298 | out_color: ("o_Color", format, gfx::state::ColorMask::all(), Some(gfx::preset::blend::ALPHA)), 299 | }; 300 | let pso = self.factory.create_pipeline_state( 301 | &self.shaders, 302 | gfx::Primitive::TriangleList, 303 | gfx::state::Rasterizer::new_fill().with_cull_back(), 304 | init 305 | )?; 306 | e.insert(pso); 307 | }) 308 | } 309 | 310 | /// Add some text to the current draw scene relative to the top left corner 311 | /// of the screen using pixel coordinates. 312 | pub fn add(&mut self, text: &str, pos: [i32; 2], color: [f32; 4]) { 313 | self.add_generic(text, Ok(pos), color) 314 | } 315 | 316 | /// Add text to the draw scene by anchoring an edge or mid-point to a 317 | /// position defined in screen pixel coordinates. 318 | pub fn add_anchored(&mut self, text: &str, pos: [i32; 2], horizontal: HorizontalAnchor, vertical: VerticalAnchor, color: [f32; 4]) { 319 | if horizontal == HorizontalAnchor::Left && vertical == VerticalAnchor::Top { 320 | self.add_generic(text, Ok(pos), color); 321 | return 322 | } 323 | 324 | let (width, height) = self.measure(text); 325 | let x = match horizontal { 326 | HorizontalAnchor::Left => pos[0], 327 | HorizontalAnchor::Center => pos[0] - width / 2, 328 | HorizontalAnchor::Right => pos[0] - width, 329 | }; 330 | let y = match vertical { 331 | VerticalAnchor::Top => pos[1], 332 | VerticalAnchor::Center => pos[1] - height / 2, 333 | VerticalAnchor::Bottom => pos[1] - height, 334 | }; 335 | 336 | self.add_generic(text, Ok([x, y]), color) 337 | } 338 | 339 | /// Add some text to the draw scene using absolute world coordinates. 340 | pub fn add_at(&mut self, text: &str, pos: [f32; 3], color: [f32; 4]) { 341 | self.add_generic(text, Err(pos), color) 342 | } 343 | 344 | fn add_generic(&mut self, text: &str, pos: Result<[i32; 2], [f32; 3]>, color: [f32; 4]) { 345 | // `Result` is used here as an `Either` analogue. 346 | let (screen_pos, world_pos, screen_rel) = match pos { 347 | Ok(screen_pos) => (screen_pos, [0.0, 0.0, 0.0], 1), 348 | Err(world_pos) => ([0, 0], world_pos, 0), 349 | }; 350 | let (mut x, y) = (screen_pos[0] as f32, screen_pos[1] as f32); 351 | for ch in text.chars() { 352 | let ch_info = match self.font_bitmap.find_char(ch) { 353 | Some(info) => info, 354 | // Skip unknown chars from text string. Probably it would be 355 | // better to place some "?" mark instead but it may not exist 356 | // in the font too. 357 | None => continue, 358 | }; 359 | let x_offset = x + ch_info.x_offset as f32; 360 | let y_offset = y + ch_info.y_offset as f32; 361 | let tex = ch_info.tex; 362 | let index = self.vertex_data.len() as u32; 363 | 364 | // Top-left point, index + 0. 365 | self.vertex_data.push(Vertex { 366 | pos: [x_offset, y_offset], 367 | tex: [tex[0], tex[1]], 368 | world_pos: world_pos, 369 | screen_rel: screen_rel, 370 | color: color, 371 | }); 372 | // Bottom-left point, index + 1. 373 | self.vertex_data.push(Vertex { 374 | pos: [x_offset, y_offset + ch_info.height as f32], 375 | tex: [tex[0], tex[1] + ch_info.tex_height], 376 | world_pos: world_pos, 377 | screen_rel: screen_rel, 378 | color: color, 379 | }); 380 | // Bottom-right point, index + 2. 381 | self.vertex_data.push(Vertex { 382 | pos: [x_offset + ch_info.width as f32, y_offset + ch_info.height as f32], 383 | tex: [tex[0] + ch_info.tex_width, tex[1] + ch_info.tex_height], 384 | world_pos: world_pos, 385 | screen_rel: screen_rel, 386 | color: color, 387 | }); 388 | // Top-right point, index + 3. 389 | self.vertex_data.push(Vertex { 390 | pos: [x_offset + ch_info.width as f32, y_offset], 391 | tex: [tex[0] + ch_info.tex_width, tex[1]], 392 | world_pos: world_pos, 393 | screen_rel: screen_rel, 394 | color: color, 395 | }); 396 | 397 | // Top-left triangle. 398 | // 0--3 399 | // | / 400 | // |/ 401 | // 1 402 | self.index_data.push(index + 0); 403 | self.index_data.push(index + 1); 404 | self.index_data.push(index + 3); 405 | // Bottom-right triangle. 406 | // 3 407 | // /| 408 | // / | 409 | // 1--2 410 | self.index_data.push(index + 3); 411 | self.index_data.push(index + 1); 412 | self.index_data.push(index + 2); 413 | 414 | x += ch_info.x_advance as f32; 415 | } 416 | } 417 | 418 | /// Draw the current scene and clear state. 419 | /// 420 | /// # Examples 421 | /// 422 | /// ```ignore 423 | /// text.add("Test1", [10, 10], [1.0, 0.0, 0.0, 1.0]); 424 | /// text.add("Test2", [20, 20], [0.0, 1.0, 0.0, 1.0]); 425 | /// text.draw(&mut encoder, &color_output).unwrap(); 426 | /// ``` 427 | pub fn draw, T: gfx::format::RenderFormat>( 428 | &mut self, 429 | encoder: &mut Encoder, 430 | target: &RenderTargetView 431 | ) -> Result<(), Error> { 432 | self.draw_at(encoder, target, DEFAULT_PROJECTION) 433 | } 434 | 435 | /// Draw using provided projection matrix. 436 | /// 437 | /// # Examples 438 | /// 439 | /// ```ignore 440 | /// text.add_at("Test1", [6.0, 0.0, 0.0], [1.0, 0.0, 0.0, 1.0]); 441 | /// text.add_at("Test2", [0.0, 5.0, 0.0], [0.0, 1.0, 0.0, 1.0]); 442 | /// text.draw_at(&mut encoder, &color_output, camera_projection).unwrap(); 443 | /// ``` 444 | pub fn draw_at, T: gfx::format::RenderFormat>( 445 | &mut self, 446 | encoder: &mut Encoder, 447 | target: &RenderTargetView, 448 | proj: [[f32; 4]; 4] 449 | ) -> Result<(), Error> { 450 | use gfx::memory::{self, Typed}; 451 | use gfx::buffer; 452 | 453 | let ver_len = self.vertex_data.len(); 454 | let ver_buf_len = self.vertex_buffer.len(); 455 | let ind_len = self.index_data.len(); 456 | let ind_buf_len = self.index_buffer.len(); 457 | 458 | // Reallocate buffers if there is no enough space for data. 459 | if ver_len > ver_buf_len { 460 | let len = grow_buffer_size(ver_buf_len, ver_len); 461 | self.vertex_buffer = self.factory.create_buffer( 462 | len, buffer::Role::Vertex, memory::Usage::Dynamic, memory::Bind::empty() 463 | ).expect("Could not reallocate vertex buffer"); 464 | } 465 | if ind_len > ind_buf_len { 466 | let len = grow_buffer_size(ind_buf_len, ind_len); 467 | self.index_buffer = self.factory.create_buffer( 468 | len, buffer::Role::Index, memory::Usage::Dynamic, memory::Bind::empty() 469 | ).expect("Could not reallocate index buffer"); 470 | } 471 | 472 | encoder.update_buffer(&self.vertex_buffer, &self.vertex_data, 0)?; 473 | encoder.update_buffer(&self.index_buffer, &self.index_data, 0)?; 474 | 475 | let ni = self.index_data.len() as gfx::VertexCount; 476 | let mut slice: gfx::Slice = gfx::Slice { 477 | base_vertex: 0, 478 | start: 0, 479 | end: self.index_buffer.len() as u32, 480 | instances: None, 481 | buffer: gfx::IndexBuffer::Index32(self.index_buffer.clone()), 482 | }; 483 | slice.end = ni; 484 | 485 | let data = pipe::Data { 486 | vbuf: self.vertex_buffer.clone(), 487 | proj: proj, 488 | screen_size: { 489 | let (w, h, _, _) = target.get_dimensions(); 490 | [w as f32, h as f32] 491 | }, 492 | color: self.color.clone(), 493 | out_color: target.raw().clone(), 494 | }; 495 | 496 | self.prepare_pso(T::get_format())?; 497 | let pso = &self.pso_map[&T::get_format()]; 498 | 499 | // Clear state. 500 | self.vertex_data.clear(); 501 | self.index_data.clear(); 502 | 503 | encoder.draw(&slice, pso, &data); 504 | Ok(()) 505 | } 506 | 507 | /// Get the bounding box size of a string as rendered by this font. 508 | pub fn measure(&self, text: &str) -> (i32, i32) { 509 | let mut width = 0; 510 | let mut last_char = None; 511 | 512 | for ch in text.chars() { 513 | let ch_info = match self.font_bitmap.find_char(ch) { 514 | Some(info) => info, 515 | None => continue, 516 | }; 517 | last_char = Some(ch_info); 518 | 519 | width += ch_info.x_advance; 520 | } 521 | 522 | match last_char { 523 | Some(info) => width += info.x_offset + info.width - info.x_advance, 524 | None => (), 525 | } 526 | 527 | (width, self.font_bitmap.get_font_height() as i32) 528 | } 529 | } 530 | 531 | // Some missing helpers. 532 | 533 | fn grow_buffer_size(mut current_size: usize, desired_size: usize) -> usize { 534 | if current_size < 1 { 535 | current_size = 1; 536 | } 537 | while current_size < desired_size { 538 | current_size *= 2; 539 | } 540 | current_size 541 | } 542 | 543 | fn create_texture_r8_static>( 544 | factory: &mut F, 545 | width: u16, 546 | height: u16, 547 | data: &[u8], 548 | ) -> Result, CombinedError> { 549 | let kind = texture::Kind::D2(width, height, texture::AaMode::Single); 550 | let (_, texture_view) = 551 | factory.create_texture_immutable_u8::<(gfx::format::R8, gfx::format::Unorm)>( 552 | kind, texture::Mipmap::Provided, &[data])?; 553 | Ok(texture_view) 554 | } 555 | 556 | // Hack to hide shader structs from the library user. 557 | mod shader_structs { 558 | extern crate gfx; 559 | 560 | gfx_vertex_struct!( Vertex { 561 | pos: [f32; 2] = "a_Pos", 562 | tex: [f32; 2] = "a_TexCoord", 563 | world_pos: [f32; 3] = "a_World_Pos", 564 | // Should be bool but gfx-rs doesn't support it. 565 | screen_rel: i32 = "a_Screen_Rel", 566 | color: [f32; 4] = "a_Color", 567 | }); 568 | 569 | gfx_pipeline_base!( pipe { 570 | vbuf: gfx::VertexBuffer, 571 | screen_size: gfx::Global<[f32; 2]>, 572 | proj: gfx::Global<[[f32; 4]; 4]>, 573 | color: gfx::TextureSampler, 574 | out_color: gfx::RawRenderTarget, 575 | }); 576 | } 577 | use shader_structs::{Vertex, pipe}; 578 | 579 | const VERTEX_SRC: &'static [u8] = b" 580 | #version 150 core 581 | 582 | in vec2 a_Pos; 583 | in vec4 a_Color; 584 | in vec2 a_TexCoord; 585 | in vec4 a_World_Pos; 586 | in int a_Screen_Rel; 587 | out vec4 v_Color; 588 | out vec2 v_TexCoord; 589 | uniform vec2 u_Screen_Size; 590 | uniform mat4 u_Proj; 591 | 592 | void main() { 593 | // On-screen offset from text origin. 594 | vec2 v_Screen_Offset = vec2( 595 | 2 * a_Pos.x / u_Screen_Size.x - 1, 596 | 1 - 2 * a_Pos.y / u_Screen_Size.y 597 | ); 598 | vec4 v_Screen_Pos = u_Proj * a_World_Pos; 599 | vec2 v_World_Offset = a_Screen_Rel == 0 600 | // Perspective divide to get normalized device coords. 601 | ? vec2 ( 602 | v_Screen_Pos.x / v_Screen_Pos.z + 1, 603 | v_Screen_Pos.y / v_Screen_Pos.z - 1 604 | ) : vec2(0.0, 0.0); 605 | 606 | v_Color = a_Color; 607 | v_TexCoord = a_TexCoord; 608 | gl_Position = vec4(v_World_Offset + v_Screen_Offset, 0.0, 1.0); 609 | } 610 | "; 611 | 612 | const FRAGMENT_SRC: &'static [u8] = b" 613 | #version 150 core 614 | 615 | in vec4 v_Color; 616 | in vec2 v_TexCoord; 617 | out vec4 o_Color; 618 | uniform sampler2D t_Color; 619 | 620 | void main() { 621 | vec4 t_Font_Color = texture(t_Color, v_TexCoord); 622 | o_Color = vec4(v_Color.rgb, t_Font_Color.r * v_Color.a); 623 | } 624 | "; 625 | --------------------------------------------------------------------------------