swash/text/cluster/
info.rs

1use super::super::Properties;
2use super::Boundary;
3
4use core::fmt;
5
6/// Information about a character including unicode properties and boundary
7/// analysis.
8#[derive(Copy, Clone, PartialEq, Eq, Default)]
9pub struct CharInfo(pub(crate) Properties);
10
11impl CharInfo {
12    /// Creates new character information from Unicode properties and
13    /// boundary analysis.
14    pub fn new(properties: Properties, boundary: Boundary) -> Self {
15        Self(properties.with_boundary(boundary as u16))
16    }
17
18    /// Returns the unicode properties for the character.
19    pub fn properties(self) -> Properties {
20        self.0
21    }
22
23    /// Returns the boundary state.
24    pub fn boundary(self) -> Boundary {
25        Boundary::from_raw(self.0.boundary())
26    }
27
28    pub(crate) fn with_properties(self, props: Properties) -> Self {
29        Self(props.with_boundary(self.0.boundary()))
30    }
31}
32
33impl core::ops::Deref for CharInfo {
34    type Target = Properties;
35
36    fn deref(&self) -> &Self::Target {
37        &self.0
38    }
39}
40
41impl From<char> for CharInfo {
42    fn from(c: char) -> Self {
43        Self(Properties::from(c))
44    }
45}
46
47impl From<CharInfo> for Properties {
48    fn from(a: CharInfo) -> Self {
49        a.0
50    }
51}
52
53impl From<&CharInfo> for Properties {
54    fn from(a: &CharInfo) -> Self {
55        a.0
56    }
57}
58
59impl From<Properties> for CharInfo {
60    fn from(p: Properties) -> Self {
61        Self(p)
62    }
63}
64
65impl From<&Properties> for CharInfo {
66    fn from(p: &Properties) -> Self {
67        Self(*p)
68    }
69}
70
71const BOUND_SHIFT: u16 = 14;
72const SPACE_SHIFT: u16 = 1;
73const EMOJI_SHIFT: u16 = 8;
74const SPACE_MASK: u16 = 0b111;
75const EMOJI_MASK: u16 = 0b11;
76
77/// Information about a cluster including content properties and boundary analysis.
78#[derive(Copy, Clone, PartialEq, Eq, Default)]
79pub struct ClusterInfo(pub u16);
80
81impl ClusterInfo {
82    /// Returns true if the cluster is missing an appropriate base
83    /// character.
84    pub fn is_broken(self) -> bool {
85        self.0 & 1 != 0
86    }
87
88    /// Returns true if the cluster is an emoji.
89    pub fn is_emoji(self) -> bool {
90        (self.0 >> EMOJI_SHIFT & EMOJI_MASK) != 0
91    }
92
93    /// Returns the emoji presentation mode of the cluster.
94    pub fn emoji(self) -> Emoji {
95        Emoji::from_raw(self.0 >> EMOJI_SHIFT & EMOJI_MASK)
96    }
97
98    /// Returns true if the cluster is whitespace.
99    pub fn is_whitespace(self) -> bool {
100        (self.0 >> SPACE_SHIFT & SPACE_MASK) != 0
101    }
102
103    /// Returns the whitespace content of the cluster.
104    pub fn whitespace(self) -> Whitespace {
105        Whitespace::from_raw(self.0 >> SPACE_SHIFT & SPACE_MASK)
106    }
107
108    /// Returns true if the cluster is a boundary.
109    pub fn is_boundary(self) -> bool {
110        (self.0 >> BOUND_SHIFT) != 0
111    }
112
113    /// Returns the boundary state of the cluster.
114    pub fn boundary(self) -> Boundary {
115        Boundary::from_raw(self.0 >> BOUND_SHIFT)
116    }
117
118    pub(super) fn set_broken(&mut self) {
119        self.0 |= 1;
120    }
121
122    pub(super) fn set_emoji(&mut self, emoji: Emoji) {
123        self.0 = self.0 & !(EMOJI_MASK << EMOJI_SHIFT) | (emoji as u16) << EMOJI_SHIFT;
124    }
125
126    pub(super) fn set_space(&mut self, space: Whitespace) {
127        self.0 = self.0 & !(SPACE_MASK << SPACE_SHIFT) | (space as u16) << SPACE_SHIFT;
128    }
129
130    #[inline]
131    pub(super) fn set_space_from_char(&mut self, ch: char) {
132        match ch {
133            ' ' => self.set_space(Whitespace::Space),
134            '\u{a0}' => self.set_space(Whitespace::NoBreakSpace),
135            '\t' => self.set_space(Whitespace::Tab),
136            _ => {}
137        }
138    }
139
140    pub(super) fn merge_boundary(&mut self, boundary: u16) {
141        let bits = (self.0 >> BOUND_SHIFT).max(boundary) << BOUND_SHIFT;
142        self.0 = ((self.0 << 2) >> 2) | bits;
143    }
144}
145
146impl fmt::Debug for ClusterInfo {
147    fn fmt(&self, f: &mut fmt::Formatter) -> core::fmt::Result {
148        let e = match self.emoji() {
149            Emoji::None => " ",
150            Emoji::Default => "E",
151            Emoji::Text => "T",
152            Emoji::Color => "C",
153        };
154        let s = match self.whitespace() {
155            Whitespace::None => " ",
156            Whitespace::Space => "s",
157            Whitespace::NoBreakSpace => "b",
158            Whitespace::Tab => "t",
159            Whitespace::Newline => "n",
160            Whitespace::Other => "o",
161        };
162        write!(f, "{}", if self.is_broken() { "!" } else { " " })?;
163        let b = match self.boundary() {
164            Boundary::Mandatory => "L",
165            Boundary::Line => "l",
166            Boundary::Word => "w",
167            _ => " ",
168        };
169        write!(f, "{}{}{}", e, s, b)
170    }
171}
172
173/// Presentation mode for an emoji cluster.
174#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)]
175#[repr(u8)]
176pub enum Emoji {
177    /// Not an emoji.
178    None = 0,
179    /// Emoji with default presentation.
180    Default = 1,
181    /// Emoji with text presentation.
182    Text = 2,
183    /// Emoji with color presentation.
184    Color = 3,
185}
186
187impl Emoji {
188    #[inline]
189    fn from_raw(bits: u16) -> Self {
190        match bits & 0b11 {
191            0 => Self::None,
192            1 => Self::Default,
193            2 => Self::Text,
194            3 => Self::Color,
195            _ => Self::None,
196        }
197    }
198}
199
200/// Whitespace content of a cluster.
201#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)]
202#[repr(u8)]
203pub enum Whitespace {
204    /// Not a space.
205    None = 0,
206    /// Standard space.
207    Space = 1,
208    /// Non-breaking space (U+00A0).
209    NoBreakSpace = 2,
210    /// Horizontal tab.
211    Tab = 3,
212    /// Newline (CR, LF, or CRLF).
213    Newline = 4,
214    /// Other space.
215    Other = 5,
216}
217
218impl Whitespace {
219    /// Returns true for space or no break space.
220    pub fn is_space_or_nbsp(self) -> bool {
221        matches!(self, Self::Space | Self::NoBreakSpace)
222    }
223
224    #[inline]
225    fn from_raw(bits: u16) -> Self {
226        match bits & 0b111 {
227            0 => Self::None,
228            1 => Self::Space,
229            2 => Self::NoBreakSpace,
230            3 => Self::Tab,
231            4 => Self::Newline,
232            5 => Self::Other,
233            _ => Self::None,
234        }
235    }
236}