summaryrefslogtreecommitdiff
path: root/src/layout/shaping.rs
blob: 141fa10e9d6627ba29593ba64258c497386f7725 (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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//! 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 std::fmt::{self, Debug, Display, Formatter};

use fontdock::{FaceId, FaceQuery, FallbackTree, FontVariant};
use ttf_parser::{Face, GlyphId};

use crate::env::FontLoader;
use crate::geom::{Dir, Length, Point, Size};
use crate::layout::{Element, Frame};

/// A shaped run of text.
#[derive(Clone, PartialEq)]
pub struct Shaped {
    /// The shaped text.
    pub text: String,
    /// The font face the text was shaped with.
    pub face: FaceId,
    /// The shaped glyphs.
    pub glyphs: Vec<GlyphId>,
    /// The horizontal offsets of the glyphs. This is indexed parallel to
    /// `glyphs`. Vertical offsets are not yet supported.
    pub offsets: Vec<Length>,
    /// The font size.
    pub font_size: Length,
}

impl Shaped {
    /// Create a new shape run with empty `text`, `glyphs` and `offsets`.
    pub fn new(face: FaceId, font_size: Length) -> Self {
        Self {
            text: String::new(),
            face,
            glyphs: vec![],
            offsets: vec![],
            font_size,
        }
    }

    /// Encode the glyph ids into a big-endian byte buffer.
    pub fn encode_glyphs_be(&self) -> Vec<u8> {
        let mut bytes = Vec::with_capacity(2 * self.glyphs.len());
        for &GlyphId(g) in &self.glyphs {
            bytes.push((g >> 8) as u8);
            bytes.push((g & 0xff) as u8);
        }
        bytes
    }
}

impl Debug for Shaped {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "Shaped({})", self.text)
    }
}

/// Identifies a vertical metric of a font.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum VerticalFontMetric {
    /// The distance from the baseline to the typographic ascender.
    ///
    /// Corresponds to the typographic ascender from the `OS/2` table if present
    /// and falls back to the ascender from the `hhea` table otherwise.
    Ascender,
    /// The approximate height of uppercase letters.
    CapHeight,
    /// The approximate height of non-ascending lowercase letters.
    XHeight,
    /// The baseline on which the letters rest.
    Baseline,
    /// The distance from the baseline to the typographic descender.
    ///
    /// Corresponds to the typographic descender from the `OS/2` table if
    /// present and falls back to the descender from the `hhea` table otherwise.
    Descender,
}

impl Display for VerticalFontMetric {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.pad(match self {
            Self::Ascender => "ascender",
            Self::CapHeight => "cap-height",
            Self::XHeight => "x-height",
            Self::Baseline => "baseline",
            Self::Descender => "descender",
        })
    }
}

/// Shape text into a frame containing [`Shaped`] runs.
pub fn shape(
    text: &str,
    dir: Dir,
    fallback: &FallbackTree,
    variant: FontVariant,
    font_size: Length,
    top_edge: VerticalFontMetric,
    bottom_edge: VerticalFontMetric,
    loader: &mut FontLoader,
) -> Frame {
    let mut frame = Frame::new(Size::new(Length::ZERO, Length::ZERO));
    let mut shaped = Shaped::new(FaceId::MAX, font_size);
    let mut width = Length::ZERO;
    let mut top = Length::ZERO;
    let mut bottom = Length::ZERO;

    // Create an iterator with conditional direction.
    let mut forwards = text.chars();
    let mut backwards = text.chars().rev();
    let chars: &mut dyn Iterator<Item = char> = if dir.is_positive() {
        &mut forwards
    } else {
        &mut backwards
    };

    for c in chars {
        let query = FaceQuery { fallback: fallback.iter(), variant, c };
        if let Some(id) = loader.query(query) {
            let face = loader.face(id).get();
            let (glyph, glyph_width) = match lookup_glyph(face, c) {
                Some(v) => v,
                None => continue,
            };

            let units_per_em = f64::from(face.units_per_em().unwrap_or(1000));
            let convert = |units| units / units_per_em * font_size;

            // Flush the buffer and reset the metrics if we use a new font face.
            if shaped.face != id {
                place(&mut frame, shaped, width, top, bottom);

                shaped = Shaped::new(id, font_size);
                width = Length::ZERO;
                top = convert(f64::from(lookup_metric(face, top_edge)));
                bottom = convert(f64::from(lookup_metric(face, bottom_edge)));
            }

            shaped.text.push(c);
            shaped.glyphs.push(glyph);
            shaped.offsets.push(width);
            width += convert(f64::from(glyph_width));
        }
    }

    place(&mut frame, shaped, width, top, bottom);

    frame
}

/// Place shaped text into a frame.
fn place(frame: &mut Frame, shaped: Shaped, width: Length, top: Length, bottom: Length) {
    if !shaped.text.is_empty() {
        frame.push(Point::new(frame.size.width, top), Element::Text(shaped));
        frame.size.width += width;
        frame.size.height = frame.size.height.max(top - bottom);
    }
}

/// Look up the glyph for `c` and returns its index alongside its advance width.
fn lookup_glyph(face: &Face, c: char) -> Option<(GlyphId, u16)> {
    let glyph = face.glyph_index(c)?;
    let width = face.glyph_hor_advance(glyph)?;
    Some((glyph, width))
}

/// Look up a vertical metric.
fn lookup_metric(face: &Face, metric: VerticalFontMetric) -> i16 {
    match metric {
        VerticalFontMetric::Ascender => lookup_ascender(face),
        VerticalFontMetric::CapHeight => face
            .capital_height()
            .filter(|&h| h > 0)
            .unwrap_or_else(|| lookup_ascender(face)),
        VerticalFontMetric::XHeight => face
            .x_height()
            .filter(|&h| h > 0)
            .unwrap_or_else(|| lookup_ascender(face)),
        VerticalFontMetric::Baseline => 0,
        VerticalFontMetric::Descender => lookup_descender(face),
    }
}

/// The ascender of the face.
fn lookup_ascender(face: &Face) -> i16 {
    // We prefer the typographic ascender over the Windows ascender because
    // it can be overly large if the font has large glyphs.
    face.typographic_ascender().unwrap_or_else(|| face.ascender())
}

/// The descender of the face.
fn lookup_descender(face: &Face) -> i16 {
    // See `lookup_ascender` for reason.
    face.typographic_descender().unwrap_or_else(|| face.descender())
}