swash/text/cluster/
info.rs
1use super::super::Properties;
2use super::Boundary;
3
4use core::fmt;
5
6#[derive(Copy, Clone, PartialEq, Eq, Default)]
9pub struct CharInfo(pub(crate) Properties);
10
11impl CharInfo {
12 pub fn new(properties: Properties, boundary: Boundary) -> Self {
15 Self(properties.with_boundary(boundary as u16))
16 }
17
18 pub fn properties(self) -> Properties {
20 self.0
21 }
22
23 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#[derive(Copy, Clone, PartialEq, Eq, Default)]
79pub struct ClusterInfo(pub u16);
80
81impl ClusterInfo {
82 pub fn is_broken(self) -> bool {
85 self.0 & 1 != 0
86 }
87
88 pub fn is_emoji(self) -> bool {
90 (self.0 >> EMOJI_SHIFT & EMOJI_MASK) != 0
91 }
92
93 pub fn emoji(self) -> Emoji {
95 Emoji::from_raw(self.0 >> EMOJI_SHIFT & EMOJI_MASK)
96 }
97
98 pub fn is_whitespace(self) -> bool {
100 (self.0 >> SPACE_SHIFT & SPACE_MASK) != 0
101 }
102
103 pub fn whitespace(self) -> Whitespace {
105 Whitespace::from_raw(self.0 >> SPACE_SHIFT & SPACE_MASK)
106 }
107
108 pub fn is_boundary(self) -> bool {
110 (self.0 >> BOUND_SHIFT) != 0
111 }
112
113 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#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)]
175#[repr(u8)]
176pub enum Emoji {
177 None = 0,
179 Default = 1,
181 Text = 2,
183 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#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)]
202#[repr(u8)]
203pub enum Whitespace {
204 None = 0,
206 Space = 1,
208 NoBreakSpace = 2,
210 Tab = 3,
212 Newline = 4,
214 Other = 5,
216}
217
218impl Whitespace {
219 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}