Font fallbacks

This commit is contained in:
b-guild
2025-10-24 13:52:08 -07:00
parent 44c881f8b2
commit 1cc075858d
6 changed files with 502 additions and 143 deletions
+3
View File
@@ -819,6 +819,9 @@ impl Editor {
*font_state_data = Font::from_memory(
include_bytes!("../resources/Roboto-Regular.ttf").as_slice(),
1024,
None,
None,
Vec::default(),
)
.unwrap();
}
@@ -169,6 +169,7 @@ use crate::{
},
},
};
use fyrox::gui::font::Font;
use fyrox::gui::style::resource::StyleResource;
use fyrox::gui::style::Style;
use fyrox::scene::base::SceneNodeId;
@@ -294,6 +295,7 @@ pub fn make_property_editors_container(
container.insert(InheritablePropertyEditorDefinition::<FontResource>::new());
container.insert(InheritablePropertyEditorDefinition::<Option<TextureResource>>::new());
container.insert(InheritablePropertyEditorDefinition::<Option<UntypedResource>>::new());
container.register_inheritable_vec_collection::<Option<FontResource>>();
container.register_inheritable_vec_collection::<Option<TextureResource>>();
container.register_inheritable_vec_collection::<Option<UntypedResource>>();
@@ -337,6 +339,9 @@ pub fn make_property_editors_container(
container.register_inheritable_vec_collection::<Signal>();
}
container.insert(ResourceFieldPropertyEditorDefinition::<Font>::new(
sender.clone(),
));
container.insert(ResourceFieldPropertyEditorDefinition::<Model>::new(
sender.clone(),
));
+1 -1
View File
@@ -1374,7 +1374,7 @@ pub(crate) fn initialize_resource_manager_loaders(
loaders.set(MaterialLoader {
resource_manager: resource_manager.clone(),
});
loaders.set(FontLoader::default());
loaders.set(FontLoader::new(resource_manager.clone()));
loaders.set(UserInterfaceLoader {
resource_manager: resource_manager.clone(),
});
+49 -5
View File
@@ -22,12 +22,15 @@
use crate::{
core::{reflect::prelude::*, uuid::Uuid, TypeUuidProvider},
font::Font,
font::{Font, FontResource},
};
use fyrox_resource::{
io::ResourceIo,
loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader},
options::{try_get_import_settings, ImportOptions},
loader::{BoxedImportOptionsLoaderFuture, BoxedLoaderFuture, LoaderPayload, ResourceLoader},
manager::ResourceManager,
options::{
try_get_import_settings, try_get_import_settings_opaque, BaseImportOptions, ImportOptions,
},
state::LoadError,
};
use serde::{Deserialize, Serialize};
@@ -37,16 +40,31 @@ fn default_page_size() -> usize {
1024
}
/// Options to control how a font is imported, allowing data to be included beyond
/// what is stored in the font file.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Reflect, Eq)]
pub struct FontImportOptions {
/// The size of each page of the atlas where glyphs are copied before they are rendered.
/// Each page is a square `page_size` x `page_size` large, and no glyph may be so large
/// that it cannot fit in that square or else it will fail to render.
#[serde(default = "default_page_size")]
pub page_size: usize,
/// The bold version of this font.
pub bold: Option<FontResource>,
/// The italic version of this font.
pub italic: Option<FontResource>,
/// Fallback fonts are used for rendering special characters that do not have glyphs in this
/// font.
pub fallbacks: Vec<Option<FontResource>>,
}
impl Default for FontImportOptions {
fn default() -> Self {
Self {
page_size: default_page_size(),
bold: None,
italic: None,
fallbacks: Vec::default(),
}
}
}
@@ -54,11 +72,22 @@ impl Default for FontImportOptions {
impl ImportOptions for FontImportOptions {}
/// Default implementation for font loading.
#[derive(Default)]
pub struct FontLoader {
/// Resource manager to allow fallback font loading.
pub resource_manager: ResourceManager,
default_import_options: FontImportOptions,
}
impl FontLoader {
/// Create an instance of the font loader using the given resource manager.
pub fn new(resource_manager: ResourceManager) -> Self {
Self {
resource_manager,
default_import_options: FontImportOptions::default(),
}
}
}
impl ResourceLoader for FontLoader {
fn extensions(&self) -> &[&str] {
&["ttf", "otf"]
@@ -68,8 +97,23 @@ impl ResourceLoader for FontLoader {
Font::type_uuid()
}
fn default_import_options(&self) -> Option<Box<dyn BaseImportOptions>> {
Some(Box::new(self.default_import_options.clone()))
}
fn try_load_import_settings(
&self,
resource_path: PathBuf,
io: Arc<dyn ResourceIo>,
) -> BoxedImportOptionsLoaderFuture {
Box::pin(async move {
try_get_import_settings_opaque::<FontImportOptions>(&resource_path, &*io).await
})
}
fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
let default_import_options = self.default_import_options.clone();
let resource_manager = self.resource_manager.clone();
Box::pin(async move {
let io = io.as_ref();
@@ -77,7 +121,7 @@ impl ResourceLoader for FontLoader {
.await
.unwrap_or(default_import_options);
let font = Font::from_file(&path, import_options.page_size, io)
let font = Font::from_file(&path, import_options, io, &resource_manager)
.await
.map_err(LoadError::new)?;
Ok(LoaderPayload::new(font))
+441 -137
View File
@@ -18,15 +18,22 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! A font resource allows [`FormattedText`](crate::formatted_text::FormattedText)
//! to render text as a series of glyphs taken from a font file such as a ttf file
//! or an otf file.
#![allow(clippy::unnecessary_to_owned)] // false-positive
use crate::core::{
algebra::Vector2, rectpack::RectPacker, reflect::prelude::*, uuid::Uuid, uuid_provider,
visitor::prelude::*, TypeUuidProvider,
};
use crate::font::loader::FontImportOptions;
use fxhash::FxHashMap;
use fyrox_core::math::Rect;
use fyrox_core::uuid;
use fyrox_core::{err, uuid};
use fyrox_resource::manager::ResourceManager;
use fyrox_resource::state::LoadError;
use fyrox_resource::untyped::ResourceKind;
use fyrox_resource::{
embedded_data_source, io::ResourceIo, manager::BuiltInResource, untyped::UntypedResource,
@@ -43,24 +50,65 @@ use std::{
pub mod loader;
/// Arbitrarily chosen limit to the number of levels of recursion
/// we will search through fallbacks. In most cases a limit of 1 should
/// be sufficient and if we get to 10 that most likely indicates
/// a cycle in the fallback fonts.
const MAX_FALLBACK_DEPTH: usize = 10;
enum FontError {
FallbackNotLoaded,
GlyphTooLarge,
}
/// The geometric data specifying where to find a glyph on a font atlas
/// texture for rendering text.
#[derive(Debug, Clone)]
pub struct FontGlyph {
/// The vertical position of the glyph relative to other glyphs on the line, measured in font pixels.
/// This would be 0 for a glyph with its bottom directly on the baseline, but may be
/// negative if the glyph extends below the baseline.
pub bitmap_top: f32,
/// The horizontal position of the glyph relative to other glyphs on the line, measured in font pixels.
/// This would usually be 0, but a negative value would allow a glyph to extend into the space usually reserved
/// for the previous glyph on the line.
pub bitmap_left: f32,
/// The width of the glyph as measured in pixels. This may be more or less than `advance` depending on how much
/// this glyph crowds into the space of the glyphs to the left and right of it on the line.
pub bitmap_width: f32,
/// The height of the glyph as measured in pixels.
pub bitmap_height: f32,
/// The horizontal distance between the start of this glyph and the start of the next glyph, measured in pixels.
pub advance: f32,
/// The position of the texture data of this glyph on the atlas page.
/// Each corner of the quad containing the glyph is given a UV coordinate.
pub tex_coords: [Vector2<f32>; 4],
/// The index of the atlas page.
pub page_index: usize,
/// The position of the gylph measured to sub-pixel precision.
/// It is like `bitmap_top`, `bitmap_left`, `bitmap_width`, and `bitmap_height`,
/// except that those measure the whole pixels that the font needs for rendering while
/// this rect contains the exact outline of the glyph, which is potentially smaller.
pub bounds: Rect<f32>,
}
/// Page is a storage for rasterized glyphs.
#[derive(Clone)]
pub struct Page {
/// The texture data for rendering some glyphs.
/// When new glyphs are required, this data may be modified if space
/// is available.
pub pixels: Vec<u8>,
/// An embedded texture containing a copy of `pixels`.
/// This is used by the GPU to render the glyph, and it must be updated
/// whenever `pixels` changes.
pub texture: Option<UntypedResource>,
/// A structure that contains the occupied rectangles within the page texture,
/// allowing new glyphs to be added to the texture by finding unoccupied space,
/// until the page is full.
pub rect_packer: RectPacker<usize>,
/// True if one or more glyphs have been added to the page, but
/// `texture` has not yet been updated by copying the content of `pixels`.
pub modified: bool,
}
@@ -78,148 +126,236 @@ impl Debug for Page {
/// store the rasterized glyphs.
#[derive(Default, Clone, Debug)]
pub struct Atlas {
/// The geometric data used for rendering glyphs from the pages of this atlas,
/// such as the size of each glyph, the index of its page, and the UVs of the corners
/// of its quad.
pub glyphs: Vec<FontGlyph>,
/// A map to lookup the index of the glyph for a particular char in the `glyphs` array.
pub char_map: FxHashMap<char, usize>,
/// The list of pages the contain the glyphs of this atlas. Each page is a texture
/// that can be used to render glyphs based on the UV data stored in `glyphs`.
pub pages: Vec<Page>,
}
impl Atlas {
fn render_glyph(
&mut self,
font: &'_ fontdue::Font,
unicode: char,
char_index: u16,
height: FontHeight,
page_size: usize,
) -> Result<usize, FontError> {
let border = 2;
let (metrics, glyph_raster) = font.rasterize_indexed(char_index, height.0);
// Find a page, that is capable to fit the new character or create a new
// page and put the character there.
let mut placement_info =
self.pages
.iter_mut()
.enumerate()
.find_map(|(page_index, page)| {
page.rect_packer
.find_free(metrics.width + border, metrics.height + border)
.map(|bounds| (page_index, bounds))
});
// No space for the character in any of the existing pages, so create a new page.
if placement_info.is_none() {
let mut page = Page {
pixels: vec![0; page_size * page_size],
texture: None,
rect_packer: RectPacker::new(page_size, page_size),
modified: true,
};
let page_index = self.pages.len();
if let Some(bounds) = page
.rect_packer
.find_free(metrics.width + border, metrics.height + border)
{
placement_info = Some((page_index, bounds));
self.pages.push(page);
}
}
let Some((page_index, placement_rect)) = placement_info else {
err!(
"Font error: The atlas page size is too small for a requested glyph at font size {}.\
Glyph width: {}, height: {}, atlas page size: {}",
height.0,
metrics.width + border,
metrics.height + border,
page_size,
);
return Err(FontError::GlyphTooLarge);
};
let page = &mut self.pages[page_index];
let glyph_index = self.glyphs.len();
// Raise a flag to notify users that the content of the page has changed, and
// it should be re-uploaded to GPU (if needed).
page.modified = true;
let mut glyph = FontGlyph {
bitmap_left: metrics.xmin as f32,
bitmap_top: metrics.ymin as f32,
advance: metrics.advance_width,
tex_coords: Default::default(),
bitmap_width: metrics.width as f32,
bitmap_height: metrics.height as f32,
bounds: Rect::new(
metrics.bounds.xmin,
metrics.bounds.ymin,
metrics.bounds.width,
metrics.bounds.height,
),
page_index,
};
let k = 1.0 / page_size as f32;
let bw = placement_rect.w().saturating_sub(border);
let bh = placement_rect.h().saturating_sub(border);
let bx = placement_rect.x() + border / 2;
let by = placement_rect.y() + border / 2;
let tw = bw as f32 * k;
let th = bh as f32 * k;
let tx = bx as f32 * k;
let ty = by as f32 * k;
glyph.tex_coords[0] = Vector2::new(tx, ty);
glyph.tex_coords[1] = Vector2::new(tx + tw, ty);
glyph.tex_coords[2] = Vector2::new(tx + tw, ty + th);
glyph.tex_coords[3] = Vector2::new(tx, ty + th);
let row_end = by + bh;
let col_end = bx + bw;
// Copy glyph pixels to the atlas pixels
for (src_row, row) in (by..row_end).enumerate() {
for (src_col, col) in (bx..col_end).enumerate() {
page.pixels[row * page_size + col] = glyph_raster[src_row * bw + src_col];
}
}
self.glyphs.push(glyph);
// Map the new glyph to its unicode position.
self.char_map.insert(unicode, glyph_index);
Ok(glyph_index)
}
fn glyph(
&mut self,
font: &fontdue::Font,
unicode: char,
height: FontHeight,
page_size: usize,
fallbacks: &[Option<FontResource>],
) -> Option<&FontGlyph> {
let border = 2;
match self.char_map.get(&unicode) {
Some(glyph_index) => self.glyphs.get(*glyph_index),
None => {
// Char might be missing, because it wasn't requested earlier. Try to find
// Char might be missing because it wasn't requested earlier. Try to find
// it in the inner font and render/pack it.
if let Some(char_index) = font.chars().get(&unicode) {
if !height.0.is_finite() || height.0 <= f32::EPSILON {
return None;
}
let (metrics, glyph_raster) =
font.rasterize_indexed(char_index.get(), height.0);
// Find a page, that is capable to fit the new character or create a new
// page and put the character there.
let mut placement_info =
self.pages
.iter_mut()
.enumerate()
.find_map(|(page_index, page)| {
page.rect_packer
.find_free(metrics.width + border, metrics.height + border)
.map(|bounds| (page_index, bounds))
});
// No space for the character in any of the existing pages, create a new page.
if placement_info.is_none() {
let mut page = Page {
pixels: vec![0; page_size * page_size],
texture: None,
rect_packer: RectPacker::new(page_size, page_size),
modified: true,
};
let page_index = self.pages.len();
match page
.rect_packer
.find_free(metrics.width + border, metrics.height + border)
{
Some(bounds) => {
placement_info = Some((page_index, bounds));
self.pages.push(page);
}
None => {
// No free space in the given page size (requested glyph is too big).
return None;
}
}
}
let (page_index, placement_rect) = placement_info?;
let page = &mut self.pages[page_index];
let glyph_index = self.glyphs.len();
// Raise a flag to notify users that the content of the page has changed, and
// it should be re-uploaded to GPU (if needed).
page.modified = true;
let mut glyph = FontGlyph {
bitmap_left: metrics.xmin as f32,
bitmap_top: metrics.ymin as f32,
advance: metrics.advance_width,
tex_coords: Default::default(),
bitmap_width: metrics.width as f32,
bitmap_height: metrics.height as f32,
bounds: Rect::new(
metrics.bounds.xmin,
metrics.bounds.ymin,
metrics.bounds.width,
metrics.bounds.height,
),
page_index,
};
let k = 1.0 / page_size as f32;
let bw = placement_rect.w().saturating_sub(border);
let bh = placement_rect.h().saturating_sub(border);
let bx = placement_rect.x() + border / 2;
let by = placement_rect.y() + border / 2;
let tw = bw as f32 * k;
let th = bh as f32 * k;
let tx = bx as f32 * k;
let ty = by as f32 * k;
glyph.tex_coords[0] = Vector2::new(tx, ty);
glyph.tex_coords[1] = Vector2::new(tx + tw, ty);
glyph.tex_coords[2] = Vector2::new(tx + tw, ty + th);
glyph.tex_coords[3] = Vector2::new(tx, ty + th);
let row_end = by + bh;
let col_end = bx + bw;
// Copy glyph pixels to the atlas pixels
for (src_row, row) in (by..row_end).enumerate() {
for (src_col, col) in (bx..col_end).enumerate() {
page.pixels[row * page_size + col] =
glyph_raster[src_row * bw + src_col];
}
}
self.glyphs.push(glyph);
// Map the new glyph to its unicode position.
self.char_map.insert(unicode, glyph_index);
self.glyphs.get(glyph_index)
let glyph_index = if let Some(char_index) = font.chars().get(&unicode) {
self.render_glyph(font, unicode, char_index.get(), height, page_size)
.ok()
} else {
None
}
// Otherwise, search the fallback fonts for a glyph to add to the atlas.
match self.fallback_glyph(
MAX_FALLBACK_DEPTH,
fallbacks,
unicode,
height,
page_size,
) {
Ok(Some(glyph_index)) => Some(glyph_index),
Ok(None) | Err(FontError::GlyphTooLarge) => {
// We have failed to find the character in the inner font and the fallbacks.
// Every font's default character is supposed to be at index 0, so add that to the atlas
// in the place of the character.
self.render_glyph(font, unicode, 0, height, page_size).ok()
}
Err(FontError::FallbackNotLoaded) => {
// If a fallback is not loaded successfully, do not write anything to the
// atlas and hope that the fallbacks will be ready next time.
None
}
}
};
glyph_index.and_then(|i| self.glyphs.get(i))
}
}
}
/// Attempt to render and return the index of the given char using the fallback fonts.
/// Return the index if the glyph was found and rendered using a fallback font.
/// Return None if the glyph was not found in any fallback font.
fn fallback_glyph(
&mut self,
depth: usize,
fonts: &[Option<FontResource>],
unicode: char,
height: FontHeight,
page_size: usize,
) -> Result<Option<usize>, FontError> {
let Some(depth) = depth.checked_sub(1) else {
return Ok(None);
};
for font in fonts.iter().flatten() {
if !font.is_ok() {
return Err(FontError::FallbackNotLoaded);
}
let font = font.data_ref();
let inner = font
.inner
.as_ref()
.expect("Fallback font reader must be initialized!");
if let Some(char_index) = inner.chars().get(&unicode) {
return self
.render_glyph(inner, unicode, char_index.get(), height, page_size)
.map(Some);
} else if let Some(glyph_index) =
self.fallback_glyph(depth, &font.fallbacks, unicode, height, page_size)?
{
return Ok(Some(glyph_index));
}
}
Ok(None)
}
}
/// A font resource and the associated data required for rendering glyphs from the font.
#[derive(Default, Clone, Debug, Reflect, Visit)]
#[reflect(hide_all)]
pub struct Font {
/// The source font data, such as might come from a ttf file.
#[reflect(hidden)]
#[visit(skip)]
pub inner: Option<fontdue::Font>,
/// The atlases containing textures that allow the GPU to render glyphs from this font,
/// or from its fallbacks. Each font size gets its own atlas.
#[reflect(hidden)]
#[visit(skip)]
pub atlases: FxHashMap<FontHeight, Atlas>,
/// The size of each atlas page in font pixels. Each page is a square measuring `page_size` x `page_size`.
#[reflect(hidden)]
#[visit(skip)]
pub page_size: usize,
/// A font representing the bold version of this font.
#[visit(skip)]
pub bold: Option<FontResource>,
/// A font representing the italic version of this font.
#[visit(skip)]
pub italic: Option<FontResource>,
/// Fallback fonts are used for rendering special characters that do not have glyphs in this
/// font.
#[visit(skip)]
pub fallbacks: Vec<Option<FontResource>>,
}
uuid_provider!(Font = "692fec79-103a-483c-bb0b-9fc3a349cb48");
@@ -242,6 +378,9 @@ impl ResourceData for Font {
}
}
/// The size the text that a font is supposed to render.
/// Each distinct size gets its own atlas page, and `FontHeight`
/// allows a f32 to be hashed to lookup the page.
#[derive(Copy, Clone, Default, Debug)]
pub struct FontHeight(pub f32);
@@ -267,9 +406,11 @@ impl Hash for FontHeight {
}
}
/// A resource that allows a font to be loaded.
pub type FontResource = Resource<Font>;
lazy_static! {
/// Fyrox's default build-in font for rendering text when no other font is specified.
pub static ref BUILT_IN_FONT: BuiltInResource<Font> = BuiltInResource::new(
"__BUILT_IN_FONT__",
embedded_data_source!("./built_in_font.ttf"),
@@ -277,34 +418,129 @@ lazy_static! {
FontResource::new_ok(
uuid!("77260e8e-f6fa-429c-8009-13dda2673925"),
ResourceKind::External,
Font::from_memory(data.to_vec(), 1024).unwrap(),
Font::from_memory(data.to_vec(), 1024, None, None, Vec::default()).unwrap(),
)
}
);
}
/// Wait for all subfonts of this font to be completely loaded, including recursively searching
/// through the fallback fonts, and the fallbacks of the fallbacks, to ensure that this font is
/// fully ready to be used. In the event that any of the fonts failed to load, or a cycle is found
/// in the fallbacks, then an error is returned.
pub async fn wait_for_subfonts(font: FontResource) -> Result<FontResource, LoadError> {
let mut stack = Vec::new();
let font = font.await?;
let bold = font.data_ref().bold.clone();
if let Some(bold) = bold {
wait_for_fallbacks(bold, &mut stack).await?;
stack.clear();
}
let italic = font.data_ref().italic.clone();
if let Some(italic) = italic {
wait_for_fallbacks(italic, &mut stack).await?;
stack.clear();
}
wait_for_fallbacks(font, &mut stack).await
}
fn write_font_names<W: std::fmt::Write>(fonts: &[FontResource], out: &mut W) -> std::fmt::Result {
fn write_name<W: std::fmt::Write>(font: &FontResource, out: &mut W) -> std::fmt::Result {
if font.is_ok() {
out.write_str(font.data_ref().name().unwrap_or("unnamed"))
} else {
out.write_str("unnamed")
}
}
if let Some((first, rest)) = fonts.split_first() {
write_name(first, out)?;
for font in rest {
out.write_str(" > ")?;
write_name(font, out)?;
}
}
Ok(())
}
/// Recursively wait for all fallback fonts of the given font to be loaded,
/// so that this font is fully ready to be rendered, and use the given stack
/// to prevent infinite recursion due to a fallback cycle. An error is returned
/// if a cycle is detected.
async fn wait_for_fallbacks(
font: FontResource,
stack: &mut Vec<FontResource>,
) -> Result<FontResource, LoadError> {
if stack.contains(&font) {
let mut err = "Cyclic fallback fonts at: ".to_string();
write_font_names(stack, &mut err).unwrap();
return Err(LoadError::new(err));
}
stack.push(font.clone());
let font = font.await?;
let fallbacks = font
.data_ref()
.fallbacks
.iter()
.flatten()
.cloned()
.collect::<Vec<_>>();
for fallback in fallbacks {
Box::pin(wait_for_fallbacks(fallback, stack)).await?;
}
_ = stack.pop();
Ok(font)
}
impl Font {
/// The name of the font, if available.
pub fn name(&self) -> Option<&str> {
self.inner.as_ref().and_then(|f| f.name())
}
/// Create a font from a u8 array of font data such as one might get from a font file.
pub fn from_memory(
data: impl Deref<Target = [u8]>,
page_size: usize,
bold: Option<FontResource>,
italic: Option<FontResource>,
fallbacks: Vec<Option<FontResource>>,
) -> Result<Self, &'static str> {
let fontdue_font = fontdue::Font::from_bytes(data, fontdue::FontSettings::default())?;
Ok(Font {
inner: Some(fontdue_font),
atlases: Default::default(),
page_size,
bold,
italic,
fallbacks,
})
}
/// Asynchronously read font data from the file at the given path to construct a font.
pub async fn from_file<P: AsRef<Path>>(
path: P,
page_size: usize,
options: FontImportOptions,
io: &dyn ResourceIo,
) -> Result<Self, &'static str> {
resource_manager: &ResourceManager,
) -> Result<Self, LoadError> {
if let Ok(file_content) = io.load_file(path.as_ref()).await {
Self::from_memory(file_content, page_size)
let page_size = options.page_size;
let mut bold = options.bold;
let mut italic = options.italic;
if let Some(bold) = &mut bold {
resource_manager.request_resource(bold);
}
if let Some(italic) = &mut italic {
resource_manager.request_resource(italic);
}
let mut fallbacks = options.fallbacks;
for font in fallbacks.iter_mut().flatten() {
resource_manager.request_resource(font);
}
Self::from_memory(file_content, page_size, bold, italic, fallbacks)
.map_err(LoadError::new)
} else {
Err("Unable to read file")
Err(LoadError::new("Unable to read file"))
}
}
@@ -317,23 +553,24 @@ impl Font {
/// in the atlas could be rendered at any page in the atlas.
#[inline]
pub fn glyph(&mut self, unicode: char, height: f32) -> Option<&FontGlyph> {
self.atlases
.entry(FontHeight(height))
.or_insert_with(|| Atlas {
glyphs: Default::default(),
char_map: Default::default(),
pages: Default::default(),
})
.glyph(
self.inner
.as_ref()
.expect("Font reader must be initialized!"),
unicode,
FontHeight(height),
self.page_size,
)
if !height.is_finite() || height <= f32::EPSILON {
return None;
}
let height = FontHeight(height);
let inner = self
.inner
.as_ref()
.expect("Font reader must be initialized!");
self.atlases.entry(height).or_default().glyph(
inner,
unicode,
height,
self.page_size,
&self.fallbacks,
)
}
/// The highest point of any glyph of this font above the baseline, usually positive.
#[inline]
pub fn ascender(&self, height: f32) -> f32 {
self.inner
@@ -344,6 +581,7 @@ impl Font {
.unwrap_or_default()
}
/// The lowest point of any glyph of this font below the baseline, usually negative.
#[inline]
pub fn descender(&self, height: f32) -> f32 {
self.inner
@@ -354,6 +592,10 @@ impl Font {
.unwrap_or_default()
}
/// The horizontal scaled kerning value for the given two characters, if available,
/// scaled to the given text height. The kerning value is usually negative, and it is added
/// to the advance of the left glyph to bring the right glyph closer when appropriate for some
/// pairs of glyphs.
#[inline]
pub fn horizontal_kerning(&self, height: f32, left: char, right: char) -> Option<f32> {
self.inner
@@ -362,11 +604,14 @@ impl Font {
.horizontal_kern(left, right, height)
}
/// The size of each atlas page in font pixels. Each page is a square measuring `page_size` x `page_size`.
#[inline]
pub fn page_size(&self) -> usize {
self.page_size
}
/// The horizontal distance between the start of the glyph for the given character and the start of the next
/// glyph, when rendered for the given text height.
#[inline]
pub fn glyph_advance(&mut self, unicode: char, height: f32) -> f32 {
self.glyph(unicode, height)
@@ -376,13 +621,64 @@ impl Font {
/// Font builder allows you to load fonts in declarative manner.
pub struct FontBuilder {
/// The size of each atlas page in font pixels. Each page is a square measuring `page_size` x `page_size`.
page_size: usize,
bold: Option<FontResource>,
italic: Option<FontResource>,
fallbacks: Vec<Option<FontResource>>,
}
impl FontBuilder {
/// Creates a default FontBuilder.
pub fn new() -> Self {
Self { page_size: 1024 }
Self {
page_size: 1024,
bold: None,
italic: None,
fallbacks: Vec::default(),
}
}
/// The size of each atlas page in font pixels. Each page is a square measuring `page_size` x `page_size`.
pub fn with_page_size(mut self, size: usize) -> Self {
self.page_size = size;
self
}
/// The bold version of this font.
pub fn with_bold(mut self, font: FontResource) -> Self {
self.bold = Some(font);
self
}
/// The italic version of this font.
pub fn with_italic(mut self, font: FontResource) -> Self {
self.italic = Some(font);
self
}
/// A fallback font to supply glyphs for special characters that are not represented in
/// this font.
pub fn with_fallback(mut self, font: FontResource) -> Self {
self.fallbacks.push(Some(font));
self
}
/// A list of fallback fonts to supply glyphs for special characters that are not represented
/// in this font, replacing any existing fallbacks for this font.
pub fn with_fallbacks(mut self, fallbacks: Vec<Option<FontResource>>) -> Self {
self.fallbacks = fallbacks;
self
}
/// Build the options object for this font.
fn into_options(self) -> FontImportOptions {
FontImportOptions {
page_size: self.page_size,
bold: self.bold,
italic: self.italic,
fallbacks: self.fallbacks,
}
}
/// Creates a new font from the data at the specified path.
@@ -390,12 +686,20 @@ impl FontBuilder {
self,
path: impl AsRef<Path>,
io: &dyn ResourceIo,
) -> Result<Font, &'static str> {
Font::from_file(path, self.page_size, io).await
resource_manager: &ResourceManager,
) -> Result<Font, LoadError> {
Font::from_file(path, self.into_options(), io, resource_manager).await
}
/// Creates a new font from bytes in memory.
pub fn build_from_memory(self, data: impl Deref<Target = [u8]>) -> Result<Font, &'static str> {
Font::from_memory(data, self.page_size)
pub fn build_from_memory(
mut self,
data: impl Deref<Target = [u8]>,
resource_manager: &ResourceManager,
) -> Result<Font, &'static str> {
for font in self.fallbacks.iter_mut().flatten() {
resource_manager.request_resource(font);
}
Font::from_memory(data, self.page_size, self.bold, self.italic, self.fallbacks)
}
}
+3
View File
@@ -103,6 +103,9 @@ fn main() {
Font::from_memory(
include_bytes!("../resources/Roboto-Regular.ttf").to_vec(),
1024,
None,
None,
Vec::default(),
)
.unwrap(),
);