diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-10-04 18:01:56 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-10-04 18:01:56 +0200 |
| commit | 262a8fa36a09527b4e257c175b12c8437279cf66 (patch) | |
| tree | ed7ef10b8f8bc1723eda5d1075ac9c09fb8b014f /src/shaping.rs | |
| parent | 54e0da59e37c25f5b9f9cd8fbe162184debe521b (diff) | |
Refactor and move shaping out of the layout module ⛳
Diffstat (limited to 'src/shaping.rs')
| -rw-r--r-- | src/shaping.rs | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/src/shaping.rs b/src/shaping.rs new file mode 100644 index 00000000..01739db3 --- /dev/null +++ b/src/shaping.rs @@ -0,0 +1,153 @@ +//! Super-basic text shaping. +//! +//! This is really only suited for simple Latin text. It picks the most suitable +//! font for each individual character. When the direction is right-to-left, the +//! word is spelled backwards. Vertical shaping is not supported. + +use fontdock::{FaceId, FaceQuery, FallbackTree, FontStyle, FontVariant}; +use ttf_parser::GlyphId; + +use crate::font::FontLoader; +use crate::geom::{Point, Size}; +use crate::layout::elements::{LayoutElement, LayoutElements, Shaped}; +use crate::layout::{BoxLayout, Dir, LayoutAlign}; +use crate::style::TextStyle; + +/// Shape text into a box. +pub async fn shape( + text: &str, + dir: Dir, + align: LayoutAlign, + style: &TextStyle, + loader: &mut FontLoader, +) -> BoxLayout { + Shaper::new(text, dir, align, style, loader).shape().await +} + +/// Performs super-basic text shaping. +struct Shaper<'a> { + text: &'a str, + dir: Dir, + variant: FontVariant, + fallback: &'a FallbackTree, + loader: &'a mut FontLoader, + shaped: Shaped, + layout: BoxLayout, + offset: f64, +} + +impl<'a> Shaper<'a> { + fn new( + text: &'a str, + dir: Dir, + align: LayoutAlign, + style: &'a TextStyle, + loader: &'a mut FontLoader, + ) -> Self { + let mut variant = style.variant; + + if style.strong { + variant.weight = variant.weight.thicken(300); + } + + if style.emph { + variant.style = match variant.style { + FontStyle::Normal => FontStyle::Italic, + FontStyle::Italic => FontStyle::Normal, + FontStyle::Oblique => FontStyle::Normal, + } + } + + Self { + text, + dir, + variant, + fallback: &style.fallback, + loader, + shaped: Shaped::new(FaceId::MAX, style.font_size()), + layout: BoxLayout { + size: Size::new(0.0, style.font_size()), + align, + elements: LayoutElements::new(), + }, + offset: 0.0, + } + } + + 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.elements.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.elements.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 { + let face = owned_face.get(); + + 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; + + // 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 + } + } +} |
