summaryrefslogtreecommitdiff
path: root/src/layout/text.rs
blob: a66e04a23d76322ac275ab932b5fe7229484ee54 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use toddle::query::{SharedFontLoader, FontQuery, FontIndex};
use toddle::tables::{CharMap, Header, HorizontalMetrics};

use crate::size::{Size, Size2D};
use crate::style::TextStyle;
use super::*;


/// The context for text layouting.
///
/// See [`LayoutContext`] for details about the fields.
#[derive(Copy, Clone)]
pub struct TextContext<'a, 'p> {
    pub loader: &'a SharedFontLoader<'p>,
    pub style: &'a TextStyle,
    pub axes: LayoutAxes,
    pub alignment: LayoutAlignment,
}

/// 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 async fn layout_text(text: &str, ctx: TextContext<'_, '_>) -> LayoutResult<Layout> {
    TextLayouter::new(text, ctx).layout().await
}

/// Layouts text into boxes.
struct TextLayouter<'a, 'p> {
    ctx: TextContext<'a, 'p>,
    text: &'a str,
    actions: LayoutActions,
    buffer: String,
    active_font: FontIndex,
    width: Size,
}

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: LayoutActions::new(),
            buffer: String::new(),
            active_font: FontIndex::MAX,
            width: Size::ZERO,
        }
    }

    /// Layout the text
    async fn layout(mut self) -> LayoutResult<Layout> {
        if self.ctx.axes.primary.is_positive() {
            for c in self.text.chars() {
                self.layout_char(c).await?;
            }
        } else {
            for c in self.text.chars().rev() {
                self.layout_char(c).await?;
            }
        }

        if !self.buffer.is_empty() {
            self.actions.add(LayoutAction::WriteText(self.buffer));
        }

        Ok(Layout {
            dimensions: Size2D::new(self.width, self.ctx.style.font_size()),
            alignment: self.ctx.alignment,
            actions: self.actions.to_vec(),
        })
    }

    /// Layout an individual character.
    async fn layout_char(&mut self, c: char) -> LayoutResult<()> {
        let (index, char_width) = self.select_font(c).await?;

        self.width += char_width;

        if self.active_font != index {
            if !self.buffer.is_empty() {
                let text = std::mem::replace(&mut self.buffer, String::new());
                self.actions.add(LayoutAction::WriteText(text));
            }

            self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size()));
            self.active_font = index;
        }

        self.buffer.push(c);

        Ok(())
    }

    /// Select the best font for a character and return its index along with
    /// the width of the char in the font.
    async fn select_font(&mut self, c: char) -> LayoutResult<(FontIndex, Size)> {
        let mut loader = self.ctx.loader.borrow_mut();

        let query = FontQuery {
            fallback: &self.ctx.style.fallback,
            variant: self.ctx.style.variant,
            c,
        };

        if let Some((font, index)) = loader.get(query).await {
            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("select_font: font should have char");

            let glyph_width = font
                .read_table::<HorizontalMetrics>()?
                .get(glyph)
                .expect("select_font: font should have glyph")
                .advance_width as f32;

            let char_width = font_unit_to_size(glyph_width)
                * self.ctx.style.font_size().to_pt();

            return Ok((index, char_width));
        }

        error!("no suitable font for character `{}`", c);
    }
}