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
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License in the LICENSE-APACHE file or at:
//     https://www.apache.org/licenses/LICENSE-2.0

//! KAS Rich-Text library — text-display environment

use crate::fonts::{fonts, FontId, InvalidFontId};
use crate::Vec2;

/// Environment in which text is prepared for display
///
/// An `Environment` can be default-constructed (without line-wrapping).
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Environment {
    /// Text direction
    ///
    /// By default, text direction (LTR or RTL) is automatically detected with
    /// full bi-directional text support (Unicode Technical Report #9).
    pub direction: Direction,
    /// Line wrapping
    ///
    /// By default, this is true and long text lines are wrapped based on the
    /// width bounds. If set to false, lines are not wrapped at the width
    /// boundary, but explicit line-breaks such as `\n` still result in new
    /// lines.
    pub wrap: bool,
    /// Alignment (`horiz`, `vert`)
    ///
    /// By default, horizontal alignment is left or right depending on the
    /// text direction (see [`Self::direction`]), and vertical alignment is
    /// to the top.
    pub align: (Align, Align),
    /// Default font
    ///
    /// This font is used unless a formatting token (see [`crate::format`])
    /// is used.
    pub font_id: FontId,
    /// Font size: pixels per Em
    ///
    /// This is a scaling factor used to convert font sizes, with units
    /// `pixels/Em`. Equivalently, this is the line-height in pixels.
    /// See [`crate::fonts`] documentation.
    ///
    /// To calculate this from text size in Points, use `dpem = dpp * pt_size`
    /// where the dots-per-point is usually `dpp = scale_factor * 96.0 / 72.0`
    /// on PC platforms, or `dpp = 1` on MacOS (or 2 for retina displays).
    pub dpem: f32,
    /// The available (horizontal and vertical) space
    ///
    /// This defaults to infinity (implying no bounds). To enable line-wrapping
    /// set at least a horizontal bound. The vertical bound is required for
    /// alignment (when aligning to the center or bottom).
    /// Glyphs outside of these bounds may not be drawn.
    pub bounds: Vec2,
}

impl Default for Environment {
    fn default() -> Self {
        Environment {
            direction: Direction::default(),
            wrap: true,
            font_id: Default::default(),
            dpem: 11.0 * 96.0 / 72.0,
            bounds: Vec2::INFINITY,
            align: Default::default(),
        }
    }
}

impl Environment {
    /// Set font size
    ///
    /// This is an alternative to setting [`Self::dpem`] directly. It is assumed
    /// that 72 Points = 1 Inch and the base screen resolution is 96 DPI.
    /// (Note: MacOS uses a different definition where 1 Point = 1 Pixel.)
    pub fn set_font_size(&mut self, pt_size: f32, scale_factor: f32) {
        self.dpem = pt_size * scale_factor * (96.0 / 72.0);
    }

    /// Returns the height of horizontal text
    ///
    /// This should be similar to the value of [`Self::dpem`], but depends on
    /// the font.
    ///
    /// To use "the standard font", use `font_id = Default::default()`.
    pub fn line_height(&self, font_id: FontId) -> Result<f32, InvalidFontId> {
        fonts()
            .get_first_face(font_id)
            .map(|face| face.height(self.dpem))
    }
}

impl Environment {
    /// Construct, with explicit font size (pixels per Em)
    pub fn new(dpem: f32) -> Self {
        Environment {
            dpem,
            ..Default::default()
        }
    }
}

/// Alignment of contents
///
/// Note that alignment information is often passed as a `(horiz, vert)` pair.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Align {
    /// Default alignment
    ///
    /// This is context dependent. For example, for Left-To-Right text it means
    /// `TL`; for things which want to stretch it may mean `Stretch`.
    Default,
    /// Align to top or left
    TL,
    /// Align to center
    Center,
    /// Align to bottom or right
    BR,
    /// Stretch to fill space
    ///
    /// For text, this is known as "justified alignment".
    Stretch,
}

impl Default for Align {
    fn default() -> Self {
        Align::Default
    }
}

/// Directionality of text
///
/// This can be used to force the text direction.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Direction {
    /// Auto-detect with bi-directional support (default)
    Bidi,
    /// Auto-detect with bi-directional support, defaulting to right-to-left
    BidiRtl,
    /// Auto-detect, single line direction only
    Single,
    /// Force left-to-right text direction
    Ltr,
    /// Force right-to-left text direction
    Rtl,
}

impl Default for Direction {
    fn default() -> Self {
        Direction::Bidi
    }
}

#[test]
fn size() {
    assert_eq!(std::mem::size_of::<Environment>(), 20);
}