diff --git a/src/layout/tree.rs b/src/layout/tree.rs index fde7833e9..c5b6609b1 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -116,11 +116,11 @@ impl<'a> TreeLayouter<'a> { let boxed = shaping::shape( text, - self.ctx.state.sys.primary, self.ctx.state.text.font_size(), - variant, - &self.ctx.state.text.fallback, + self.ctx.state.sys.primary, &mut self.ctx.loader.borrow_mut(), + &self.ctx.state.text.fallback, + variant, ) .await; diff --git a/src/shaping.rs b/src/shaping.rs index 7c60e0d20..c620f8729 100644 --- a/src/shaping.rs +++ b/src/shaping.rs @@ -7,24 +7,12 @@ use std::fmt::{self, Debug, Formatter}; use fontdock::{FaceId, FaceQuery, FallbackTree, FontVariant}; -use ttf_parser::GlyphId; +use ttf_parser::{Face, GlyphId}; use crate::font::FontLoader; use crate::geom::{Point, Size}; use crate::layout::{BoxLayout, Dir, LayoutElement}; -/// Shape text into a box containing shaped runs. -pub async fn shape( - text: &str, - dir: Dir, - size: f64, - variant: FontVariant, - fallback: &FallbackTree, - loader: &mut FontLoader, -) -> BoxLayout { - Shaper::new(text, dir, size, variant, fallback, loader).shape().await -} - /// A shaped run of text. #[derive(Clone, PartialEq)] pub struct Shaped { @@ -70,113 +58,75 @@ impl Debug for Shaped { } } -/// Performs super-basic text shaping. -struct Shaper<'a> { - text: &'a str, +/// Shape text into a box containing [`Shaped`] runs. +/// +/// [`Shaped`]: struct.Shaped.html +pub async fn shape( + text: &str, + size: f64, dir: Dir, + loader: &mut FontLoader, + fallback: &FallbackTree, variant: FontVariant, - fallback: &'a FallbackTree, - loader: &'a mut FontLoader, - shaped: Shaped, - layout: BoxLayout, - offset: f64, -} +) -> BoxLayout { + let mut layout = BoxLayout::new(Size::new(0.0, size)); + let mut shaped = Shaped::new(FaceId::MAX, size); + let mut offset = 0.0; -impl<'a> Shaper<'a> { - fn new( - text: &'a str, - dir: Dir, - size: f64, - variant: FontVariant, - fallback: &'a FallbackTree, - loader: &'a mut FontLoader, - ) -> Self { - Self { - text, - dir, - variant, - fallback, - loader, - shaped: Shaped::new(FaceId::MAX, size), - layout: BoxLayout::new(Size::new(0.0, size)), - offset: 0.0, - } - } + // Create an iterator with conditional direction. + let mut forwards = text.chars(); + let mut backwards = text.chars().rev(); + let chars: &mut dyn Iterator = if dir.is_positive() { + &mut forwards + } else { + &mut backwards + }; - async fn shape(mut self) -> BoxLayout { - // If the primary axis is negative, we layout the characters reversed. - if self.dir.is_positive() { - for c in self.text.chars() { - self.shape_char(c).await; - } - } else { - for c in self.text.chars().rev() { - self.shape_char(c).await; - } - } - - // Flush the last buffered parts of the word. - if !self.shaped.text.is_empty() { - let pos = Point::new(self.offset, 0.0); - self.layout.push(pos, LayoutElement::Text(self.shaped)); - } - - self.layout - } - - async fn shape_char(&mut self, c: char) { - let (index, glyph, char_width) = match self.select_font(c).await { - Some(selected) => selected, - // TODO: Issue warning about missing character. - None => return, - }; - - // Flush the buffer and issue a font setting action if the font differs - // from the last character's one. - if self.shaped.face != index { - if !self.shaped.text.is_empty() { - let shaped = std::mem::replace( - &mut self.shaped, - Shaped::new(FaceId::MAX, self.layout.size.height), - ); - - let pos = Point::new(self.offset, 0.0); - self.layout.push(pos, LayoutElement::Text(shaped)); - self.offset = self.layout.size.width; - } - - self.shaped.face = index; - } - - self.shaped.text.push(c); - self.shaped.glyphs.push(glyph); - self.shaped.offsets.push(self.layout.size.width - self.offset); - - self.layout.size.width += char_width; - } - - async fn select_font(&mut self, c: char) -> Option<(FaceId, GlyphId, f64)> { - let query = FaceQuery { - fallback: self.fallback.iter(), - variant: self.variant, - c, - }; - - if let Some((id, owned_face)) = self.loader.query(query).await { + for c in chars { + let query = FaceQuery { fallback: fallback.iter(), variant, c }; + if let Some((id, owned_face)) = loader.query(query).await { let face = owned_face.get(); + let (glyph, width) = match lookup_glyph(face, c, size) { + Some(v) => v, + None => continue, + }; - let units_per_em = face.units_per_em().unwrap_or(1000) as f64; - let ratio = 1.0 / units_per_em; - let font_size = self.layout.size.height; - let to_raw = |x| ratio * x as f64 * font_size; + // Flush the buffer if we change the font face. + if shaped.face != id && !shaped.text.is_empty() { + let pos = Point::new(layout.size.width, 0.0); + layout.push(pos, LayoutElement::Text(shaped)); + layout.size.width += offset; + shaped = Shaped::new(FaceId::MAX, size); + offset = 0.0; + } - // Determine the width of the char. - let glyph = face.glyph_index(c)?; - let glyph_width = to_raw(face.glyph_hor_advance(glyph)? as i32); - - Some((id, glyph, glyph_width)) - } else { - None + shaped.face = id; + shaped.text.push(c); + shaped.glyphs.push(glyph); + shaped.offsets.push(offset); + offset += width; } } + + // Flush the last buffered parts of the word. + if !shaped.text.is_empty() { + let pos = Point::new(layout.size.width, 0.0); + layout.push(pos, LayoutElement::Text(shaped)); + layout.size.width += offset; + } + + layout +} + +/// Looks up the glyph for `c` and returns its index alongside its width at the +/// given `size`. +fn lookup_glyph(face: &Face, c: char, size: f64) -> Option<(GlyphId, f64)> { + let glyph = face.glyph_index(c)?; + + // Determine the width of the char. + let units_per_em = face.units_per_em().unwrap_or(1000) as f64; + let width_units = face.glyph_hor_advance(glyph)? as f64; + let width = width_units / units_per_em * size; + + Some((glyph, width)) }