diff options
| author | Laurenz <laurmaedje@gmail.com> | 2019-10-14 23:33:29 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2019-10-14 23:33:29 +0200 |
| commit | 5473e3903a306d41d9555e2d4d5c1927fd59f0e9 (patch) | |
| tree | 7e880c43e4474e4b2bbc705b64c69b2916c2f862 /src/layout/text.rs | |
| parent | c768b8b61f40ef2578d45badebe097e7a9db0be9 (diff) | |
Refactor the text layouting ♻
Diffstat (limited to 'src/layout/text.rs')
| -rw-r--r-- | src/layout/text.rs | 160 |
1 files changed, 92 insertions, 68 deletions
diff --git a/src/layout/text.rs b/src/layout/text.rs index 27b65d56..3a064df4 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -1,4 +1,4 @@ -use toddle::query::{FontQuery, SharedFontLoader}; +use toddle::query::{SharedFontLoader, FontQuery, FontClass}; use toddle::tables::{CharMap, Header, HorizontalMetrics}; use super::*; @@ -13,82 +13,106 @@ pub struct TextContext<'a, 'p> { pub style: &'a TextStyle, } -/// Layout one piece of text without any breaks as one continous box. +/// Layouts text into a box. +/// +/// There is no complex layout involved. The text is simply laid out left- +/// to-right using the correct font for each character. pub fn layout_text(text: &str, ctx: TextContext) -> LayoutResult<Layout> { - let mut loader = ctx.loader.borrow_mut(); - - let mut actions = Vec::new(); - let mut active_font = std::usize::MAX; - let mut buffer = String::new(); - let mut width = Size::zero(); - - // Walk the characters. - for character in text.chars() { - // Retrieve the best font for this character. - let mut font = None; - let mut classes = ctx.style.classes.clone(); - for class in &ctx.style.fallback { - classes.push(class.clone()); - - font = loader.get(FontQuery { - chars: &[character], - classes: &classes, - }); - - if font.is_some() { - break; - } + TextLayouter::new(text, ctx).layout() +} + +/// Layouts text into boxes. +struct TextLayouter<'a, 'p> { + ctx: TextContext<'a, 'p>, + text: &'a str, + actions: LayoutActionList, + buffer: String, + active_font: usize, + width: Size, + classes: Vec<FontClass>, +} - classes.pop(); +impl<'a, 'p> TextLayouter<'a, 'p> { + /// Create a new text layouter. + fn new(text: &'a str, ctx: TextContext<'a, 'p>) -> TextLayouter<'a, 'p> { + TextLayouter { + ctx, + text, + actions: LayoutActionList::new(), + buffer: String::new(), + active_font: std::usize::MAX, + width: Size::zero(), + classes: ctx.style.classes.clone(), } + } + + /// Layout the text + fn layout(mut self) -> LayoutResult<Layout> { + for c in self.text.chars() { + let (index, char_width) = self.select_font(c)?; - let (font, index) = match font { - Some(f) => f, - None => return Err(LayoutError::NoSuitableFont(character)), - }; - - // Create a conversion function between font units and sizes. - let font_unit_ratio = 1.0 / (font.read_table::<Header>()?.units_per_em as f32); - let font_unit_to_size = |x| Size::pt(font_unit_ratio * x); - - // Add the char width to the total box width. - let glyph = font - .read_table::<CharMap>()? - .get(character) - .expect("layout text: font should have char"); - - let glyph_width = font_unit_to_size( - font.read_table::<HorizontalMetrics>()? - .get(glyph) - .expect("layout text: font should have glyph") - .advance_width as f32, - ); - - let char_width = glyph_width * ctx.style.font_size; - width += char_width; - - // Change the font if necessary. - if active_font != index { - if !buffer.is_empty() { - actions.push(LayoutAction::WriteText(buffer)); - buffer = String::new(); + self.width += char_width; + + if self.active_font != index { + if !self.buffer.is_empty() { + self.actions.add(LayoutAction::WriteText(self.buffer)); + self.buffer = String::new(); + } + + self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size)); + self.active_font = index; } - actions.push(LayoutAction::SetFont(index, ctx.style.font_size)); - active_font = index; + self.buffer.push(c); } - buffer.push(character); - } + if !self.buffer.is_empty() { + self.actions.add(LayoutAction::WriteText(self.buffer)); + } - // Write the remaining characters. - if !buffer.is_empty() { - actions.push(LayoutAction::WriteText(buffer)); + Ok(Layout { + dimensions: Size2D::new(self.width, Size::pt(self.ctx.style.font_size)), + actions: self.actions.into_vec(), + debug_render: false, + }) } - Ok(Layout { - dimensions: Size2D::new(width, Size::pt(ctx.style.font_size)), - actions, - debug_render: false, - }) + /// Select the best font for a character and return its index along with + /// the width of the char in the font. + fn select_font(&mut self, c: char) -> LayoutResult<(usize, Size)> { + let mut loader = self.ctx.loader.borrow_mut(); + + for class in &self.ctx.style.fallback { + self.classes.push(class.clone()); + + let query = FontQuery { + chars: &[c], + classes: &self.classes, + }; + + if let Some((font, index)) = loader.get(query) { + let font_unit_ratio = 1.0 / (font.read_table::<Header>()?.units_per_em as f32); + let font_unit_to_size = |x| Size::pt(font_unit_ratio * x); + + let glyph = font + .read_table::<CharMap>()? + .get(c) + .expect("layout text: font should have char"); + + let glyph_width = font + .read_table::<HorizontalMetrics>()? + .get(glyph) + .expect("layout text: font should have glyph") + .advance_width as f32; + + let char_width = font_unit_to_size(glyph_width) * self.ctx.style.font_size; + + return Ok((index, char_width)); + } + + self.classes.pop(); + } + + Err(LayoutError::NoSuitableFont(c)) + } } |
