summaryrefslogtreecommitdiff
path: root/src/layout/text.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-10-14 23:33:29 +0200
committerLaurenz <laurmaedje@gmail.com>2019-10-14 23:33:29 +0200
commit5473e3903a306d41d9555e2d4d5c1927fd59f0e9 (patch)
tree7e880c43e4474e4b2bbc705b64c69b2916c2f862 /src/layout/text.rs
parentc768b8b61f40ef2578d45badebe097e7a9db0be9 (diff)
Refactor the text layouting ♻
Diffstat (limited to 'src/layout/text.rs')
-rw-r--r--src/layout/text.rs160
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))
+ }
}