From 4d8a567b36f48ef890d0842f8c7b5d2530bdd6c8 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Sun, 31 May 2020 15:59:11 -0700 Subject: [PATCH] text: migrate to ab_glyph. this should give rendering consistency across platforms --- crates/bevy_text/Cargo.toml | 4 +- crates/bevy_text/src/font.rs | 123 +++++++++++++++++++++---- crates/bevy_text/src/lib.rs | 1 - crates/bevy_text/src/render.rs | 141 ----------------------------- crates/bevy_ui/src/widget/label.rs | 2 +- examples/ui/text.rs | 2 +- 6 files changed, 108 insertions(+), 165 deletions(-) delete mode 100644 crates/bevy_text/src/render.rs diff --git a/crates/bevy_text/Cargo.toml b/crates/bevy_text/Cargo.toml index 864493c0db..c6ddcb3ad5 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -10,7 +10,5 @@ edition = "2018" bevy_app = { path = "../bevy_app" } bevy_asset = { path = "../bevy_asset" } bevy_render = { path = "../bevy_render" } -skribo = "0.1.0" -font-kit = "0.6" -pathfinder_geometry = "0.5" +ab_glyph = "0.2.2" anyhow = "1.0" \ No newline at end of file diff --git a/crates/bevy_text/src/font.rs b/crates/bevy_text/src/font.rs index 7789383de8..dfe273965c 100644 --- a/crates/bevy_text/src/font.rs +++ b/crates/bevy_text/src/font.rs @@ -1,30 +1,117 @@ -use crate::render::render_text; -use bevy_render::{texture::Texture, Color}; -use font_kit::{error::FontLoadingError, metrics::Metrics}; -use skribo::{FontCollection, FontFamily}; -use std::sync::Arc; +use ab_glyph::{FontVec, Glyph, InvalidFont, Point, PxScale, ScaleFont}; +use bevy_render::{ + texture::{Texture, TextureType}, + Color, +}; pub struct Font { - pub collection: FontCollection, - pub metrics: Metrics, + pub font: FontVec, } unsafe impl Send for Font {} unsafe impl Sync for Font {} impl Font { - pub fn try_from_bytes(font_data: Vec) -> Result { - let font = font_kit::font::Font::from_bytes(Arc::new(font_data), 0)?; - let metrics = font.metrics(); - let mut collection = FontCollection::new(); - collection.add_family(FontFamily::new_from_font(font)); - Ok(Font { - collection, - metrics, - }) + pub fn try_from_bytes(font_data: Vec) -> Result { + let font = FontVec::try_from_vec(font_data)?; + Ok(Font { font }) } - pub fn render_text(&self, text: &str, color: Color, width: usize, height: usize) -> Texture { - render_text(self, text, color, width, height) + // adapted from ab_glyph example: https://github.com/alexheretic/ab-glyph/blob/master/dev/examples/image.rs + pub fn render_text(&self, text: &str, color: Color, font_size: f32, width: usize, height: usize) -> Texture { + let scale = PxScale::from(font_size); + + let scaled_font = ab_glyph::Font::as_scaled(&self.font, scale); + + let mut glyphs = Vec::new(); + layout_paragraph( + scaled_font, + ab_glyph::point(0.0, 0.0), + width as f32, + text, + &mut glyphs, + ); + + let color_u8 = [ + (color.r * 255.0) as u8, + (color.g * 255.0) as u8, + (color.b * 255.0) as u8, + ]; + + // TODO: this offset is a bit hackey + let mut alpha = vec![0.0; width * height]; + for glyph in glyphs { + if let Some(outlined) = scaled_font.outline_glyph(glyph) { + let bounds = outlined.px_bounds(); + // Draw the glyph into the image per-pixel by using the draw closure + outlined.draw(|x, y, v| { + // Offset the position by the glyph bounding box + // Turn the coverage into an alpha value (blended with any previous) + let offset_x = x as usize + bounds.min.x as usize; + let offset_y = y as usize + bounds.min.y as usize; + if offset_x >= width || offset_y >= height { + return; + } + alpha[offset_y * width + offset_x] = v; + }); + } + } + + Texture::load(TextureType::Data( + alpha + .iter() + .map(|a| { + vec![ + color_u8[0], + color_u8[1], + color_u8[2], + (color.a * a * 255.0) as u8, + ] + }) + .flatten() + .collect::>(), + width, + height, + )) + } +} + +fn layout_paragraph( + font: SF, + position: Point, + max_width: f32, + text: &str, + target: &mut Vec, +) where + F: ab_glyph::Font, + SF: ScaleFont, +{ + let v_advance = font.height() + font.line_gap(); + let mut caret = position + ab_glyph::point(0.0, font.ascent()); + let mut last_glyph: Option = None; + for c in text.chars() { + if c.is_control() { + if c == '\n' { + caret = ab_glyph::point(position.x, caret.y + v_advance); + last_glyph = None; + } + continue; + } + let mut glyph = font.scaled_glyph(c); + if let Some(previous) = last_glyph.take() { + caret.x += font.kern(previous.id, glyph.id); + } + glyph.position = caret; + + last_glyph = Some(glyph.clone()); + caret.x += font.h_advance(glyph.id); + + if !c.is_whitespace() && caret.x > position.x + max_width { + caret = ab_glyph::point(position.x, caret.y + v_advance); + glyph.position = caret; + last_glyph = None; + } + + target.push(glyph); } } diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index dcb05fee3b..78b5574a15 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -1,6 +1,5 @@ mod font; mod font_loader; -mod render; pub use font::*; pub use font_loader::*; diff --git a/crates/bevy_text/src/render.rs b/crates/bevy_text/src/render.rs deleted file mode 100644 index 1859492354..0000000000 --- a/crates/bevy_text/src/render.rs +++ /dev/null @@ -1,141 +0,0 @@ -use crate::Font; -use bevy_render::{ - texture::{Texture, TextureType}, - Color, -}; -use font_kit::{ - canvas::{Canvas, Format, RasterizationOptions}, - hinting::HintingOptions, -}; -use pathfinder_geometry::transform2d::Transform2F; -use skribo::{LayoutSession, TextStyle}; -use std::ops::Range; - -struct TextSurface { - width: usize, - height: usize, - pixels: Vec, -} - -fn composite(a: u8, b: u8) -> u8 { - let y = ((255 - a) as u16) * ((255 - b) as u16); - let y = (y + (y >> 8) + 0x80) >> 8; // fast approx to round(y / 255) - 255 - (y as u8) -} - -impl TextSurface { - fn new(width: usize, height: usize) -> TextSurface { - let pixels = vec![0; width * height]; - TextSurface { - width, - height, - pixels, - } - } - - fn paint_from_canvas(&mut self, canvas: &Canvas, x: i32, y: i32) { - let (cw, ch) = (canvas.size.x(), canvas.size.y()); - let (w, h) = (self.width as i32, self.height as i32); - let y = y - ch; - let xmin = 0.max(-x); - let xmax = cw.min(w - x); - let ymin = 0.max(-y); - let ymax = ch.min(h - y); - for yy in ymin..(ymax.max(ymin)) { - for xx in xmin..(xmax.max(xmin)) { - let pix = canvas.pixels[(cw * yy + xx) as usize]; - let dst_ix = ((y + yy) * w + x + xx) as usize; - self.pixels[dst_ix] = composite(self.pixels[dst_ix], pix); - } - } - } - - fn paint_layout_session>( - &mut self, - layout: &mut LayoutSession, - x: i32, - y: i32, - size: f32, - range: Range, - ) { - for run in layout.iter_substr(range) { - let font = run.font(); - for glyph in run.glyphs() { - let glyph_id = glyph.glyph_id; - let glyph_x = (glyph.offset.x() as i32) + x; - let glyph_y = (glyph.offset.y() as i32) + y; - let bounds = font - .font - .raster_bounds( - glyph_id, - size, - Transform2F::default(), - HintingOptions::None, - RasterizationOptions::GrayscaleAa, - ) - .unwrap(); - if bounds.width() > 0 && bounds.height() > 0 { - let origin_adj = bounds.origin().to_f32(); - let neg_origin = -origin_adj; - let mut canvas = Canvas::new(bounds.size(), Format::A8); - font.font - .rasterize_glyph( - &mut canvas, - glyph_id, - size, - Transform2F::from_translation(neg_origin), - HintingOptions::None, - RasterizationOptions::GrayscaleAa, - ) - .unwrap(); - self.paint_from_canvas( - &canvas, - glyph_x + bounds.origin_x(), - glyph_y - bounds.origin_y(), - ); - } - } - } - } -} - -pub fn render_text(font: &Font, text: &str, color: Color, width: usize, height: usize) -> Texture { - let mut surface = TextSurface::new(width, height); - let style = TextStyle { - size: height as f32, - }; - let offset = style.size * (font.metrics.ascent - font.metrics.cap_height) - / font.metrics.units_per_em as f32; - - let mut layout = LayoutSession::create(&text, &style, &font.collection); - surface.paint_layout_session( - &mut layout, - 0, - style.size as i32 - offset as i32, - style.size, - 0..text.len(), - ); - let color_u8 = [ - (color.r * 255.0) as u8, - (color.g * 255.0) as u8, - (color.b * 255.0) as u8, - ]; - - Texture::load(TextureType::Data( - surface - .pixels - .iter() - .map(|p| { - vec![ - color_u8[0], - color_u8[1], - color_u8[2], - (color.a * *p as f32) as u8, - ] - }) - .flatten() - .collect::>(), - surface.width, - surface.height, - )) -} diff --git a/crates/bevy_ui/src/widget/label.rs b/crates/bevy_ui/src/widget/label.rs index 10c6f4ae9c..5e703b88fb 100644 --- a/crates/bevy_ui/src/widget/label.rs +++ b/crates/bevy_ui/src/widget/label.rs @@ -38,7 +38,7 @@ impl Label { if let Some(font) = fonts.get(&label.font) { let texture = - font.render_text(&label.text, label.color, width as usize, height as usize); + font.render_text(&label.text, label.color, label.font_size, width as usize, height as usize); let material = color_materials.get_or_insert_with(*color_material_handle, || { ColorMaterial::from(Handle::::new()) diff --git a/examples/ui/text.rs b/examples/ui/text.rs index 6927ee683a..2dc57cf33e 100644 --- a/examples/ui/text.rs +++ b/examples/ui/text.rs @@ -15,7 +15,7 @@ fn main() { fn text_update_system(diagnostics: Res, mut label: ComMut