summaryrefslogtreecommitdiff
path: root/src/layout/text.rs
blob: c1f4e464b299efb4e5e875c00b8995e5d9e26e88 (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
//! Layouting of text into boxes.

use toddle::query::{FontQuery, SharedFontLoader};
use toddle::tables::{Header, CharMap, HorizontalMetrics};

use crate::doc::LayoutAction;
use crate::size::{Size, Size2D};
use super::*;


/// The context for text layouting.
#[derive(Copy, Clone)]
pub struct TextContext<'a, 'p> {
    /// Loads fonts matching queries.
    pub loader: &'a SharedFontLoader<'p>,
    /// Base style to set text with.
    pub style: &'a TextStyle,
}

/// Layout one piece of text without any breaks as one continous box.
pub fn layout(text: &str, ctx: TextContext) -> LayoutResult<BoxLayout> {
    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;
            }

            classes.pop();
        }

        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();
            }

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

        buffer.push(character);
    }

    // Write the remaining characters.
    if !buffer.is_empty() {
        actions.push(LayoutAction::WriteText(buffer));
    }

    Ok(BoxLayout {
        dimensions: Size2D::new(width, Size::pt(ctx.style.font_size)),
        actions,
    })
}