1use crate::fonts::{FONT_REF_LEN, FontRefs};
15use crate::schema::{Paint, TypeRole};
16use std::rc::Rc;
17
18pub const SPAN_LEN: usize = 36;
22
23pub const SPAN_START_AT: usize = 0;
26pub const SPAN_LENGTH_AT: usize = 4;
27pub const SPAN_ROLE_AT: usize = 8;
28pub const SPAN_FLAGS_AT: usize = 10;
29pub const SPAN_COLOR_AT: usize = 12;
30pub const SPAN_BACKGROUND_AT: usize = 20;
31pub const SPAN_HANDLER_AT: usize = 28;
32
33pub const SPAN_FONT_ENTRY_LEN: usize = 12;
39pub const SPAN_FONT_SPAN_AT: usize = 0;
40pub const SPAN_FONT_OFFSET_AT: usize = 4;
41pub const SPAN_FONT_COUNT_AT: usize = 8;
42
43pub const FLAG_BOLD: u16 = 1;
44pub const FLAG_ITALIC: u16 = 2;
45pub const FLAG_UNDERLINE: u16 = 4;
46pub const FLAG_STRIKETHROUGH: u16 = 8;
47
48#[derive(Clone, Copy, Debug, Default, PartialEq)]
53pub struct TextSpan {
54 pub start: u32,
55 pub length: u32,
56 pub type_role: Option<TypeRole>,
57 pub color: Option<Paint>,
58 pub background: Option<Paint>,
66 pub bold: bool,
67 pub italic: bool,
68 pub underline: bool,
69 pub strikethrough: bool,
70 pub on_click: Option<u64>,
72}
73
74impl TextSpan {
75 pub const fn new(start: u32, length: u32) -> Self {
77 Self {
78 start,
79 length,
80 type_role: None,
81 color: None,
82 background: None,
83 bold: false,
84 italic: false,
85 underline: false,
86 strikethrough: false,
87 on_click: None,
88 }
89 }
90
91 pub const fn bold(mut self) -> Self {
92 self.bold = true;
93 self
94 }
95
96 pub const fn italic(mut self) -> Self {
97 self.italic = true;
98 self
99 }
100
101 pub const fn underline(mut self) -> Self {
102 self.underline = true;
103 self
104 }
105
106 pub const fn strikethrough(mut self) -> Self {
107 self.strikethrough = true;
108 self
109 }
110
111 pub const fn with_role(mut self, role: TypeRole) -> Self {
112 self.type_role = Some(role);
113 self
114 }
115
116 pub const fn with_color(mut self, color: Paint) -> Self {
117 self.color = Some(color);
118 self
119 }
120
121 pub const fn with_background(mut self, background: Paint) -> Self {
124 self.background = Some(background);
125 self
126 }
127
128 fn flags(&self) -> u16 {
129 let mut flags = 0;
130 if self.bold {
131 flags |= FLAG_BOLD;
132 }
133 if self.italic {
134 flags |= FLAG_ITALIC;
135 }
136 if self.underline {
137 flags |= FLAG_UNDERLINE;
138 }
139 if self.strikethrough {
140 flags |= FLAG_STRIKETHROUGH;
141 }
142 flags
143 }
144
145 fn write_into(&self, out: &mut Vec<u8>) {
146 out.extend_from_slice(&self.start.to_le_bytes());
147 out.extend_from_slice(&self.length.to_le_bytes());
148 out.extend_from_slice(&self.type_role.map_or(0, u16::from).to_le_bytes());
149 out.extend_from_slice(&self.flags().to_le_bytes());
150 let paint = self.color.map_or(0, Paint::to_bits);
153 out.extend_from_slice(&(paint as u32).to_le_bytes());
154 out.extend_from_slice(&((paint >> 32) as u32).to_le_bytes());
155 let background = self.background.map_or(0, Paint::to_bits);
156 out.extend_from_slice(&(background as u32).to_le_bytes());
157 out.extend_from_slice(&((background >> 32) as u32).to_le_bytes());
158 let handler = self.on_click.unwrap_or(0);
159 out.extend_from_slice(&(handler as u32).to_le_bytes());
160 out.extend_from_slice(&((handler >> 32) as u32).to_le_bytes());
161 }
162}
163
164type Marked = (&'static [u8], fn(u32, u32) -> TextSpan);
167
168#[derive(Clone, Default, PartialEq)]
173pub struct TextSpans {
174 bytes: Option<Rc<[u8]>>,
175 fonts: Option<Rc<[u8]>>,
177}
178
179impl std::fmt::Debug for TextSpans {
180 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 formatter
182 .debug_struct("TextSpans")
183 .field("spans", &(self.as_bytes().len() / SPAN_LEN))
184 .field("fonts", &self.font_entry_count())
185 .finish()
186 }
187}
188
189impl TextSpans {
190 pub fn new(spans: impl IntoIterator<Item = TextSpan>) -> Self {
191 let mut bytes = Vec::new();
192 for span in spans {
193 span.write_into(&mut bytes);
194 }
195 Self::from_bytes(bytes)
196 }
197
198 pub fn new_with_fonts(spans: impl IntoIterator<Item = (TextSpan, FontRefs)>) -> Self {
203 let mut bytes = Vec::new();
204 let mut named: Vec<(u32, FontRefs)> = Vec::new();
205 for (index, (span, fonts)) in spans.into_iter().enumerate() {
206 span.write_into(&mut bytes);
207 if !fonts.is_empty() {
208 named.push((index as u32, fonts));
209 }
210 }
211 let mut spans = Self::from_bytes(bytes);
212 spans.fonts = encode_font_table(&named);
213 spans
214 }
215
216 pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
218 let bytes = bytes.into();
219 Self {
220 bytes: (!bytes.is_empty()).then_some(bytes),
221 fonts: None,
222 }
223 }
224
225 pub fn with_font_table(mut self, table: impl Into<Rc<[u8]>>) -> Self {
227 let table = table.into();
228 self.fonts = (!table.is_empty()).then_some(table);
229 self
230 }
231
232 pub fn as_bytes(&self) -> &[u8] {
233 self.bytes.as_deref().unwrap_or(&[])
234 }
235
236 pub fn font_table(&self) -> &[u8] {
239 self.fonts.as_deref().unwrap_or(&[])
240 }
241
242 pub fn is_empty(&self) -> bool {
243 self.bytes.is_none()
244 }
245
246 pub fn font_entry_count(&self) -> usize {
248 let table = self.font_table();
249 if table.len() < 4 {
250 return 0;
251 }
252 u32::from_le_bytes([table[0], table[1], table[2], table[3]]) as usize
253 }
254
255 pub fn span_fonts(&self) -> Vec<(u32, Vec<crate::fonts::FontRef>)> {
257 let table = self.font_table();
258 let word = |at: usize| -> Option<u32> {
259 let slice = table.get(at..at + 4)?;
260 Some(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
261 };
262 (0..self.font_entry_count())
263 .filter_map(|entry| {
264 let at = 4 + entry * SPAN_FONT_ENTRY_LEN;
265 let span = word(at + SPAN_FONT_SPAN_AT)?;
266 let offset = word(at + SPAN_FONT_OFFSET_AT)? as usize;
267 let count = word(at + SPAN_FONT_COUNT_AT)? as usize;
268 let refs = (0..count)
269 .filter_map(|index| {
270 crate::fonts::decode_record(table, offset + index * FONT_REF_LEN)
271 })
272 .collect();
273 Some((span, refs))
274 })
275 .collect()
276 }
277
278 pub fn spans(&self) -> impl Iterator<Item = TextSpan> + '_ {
280 self.as_bytes().chunks_exact(SPAN_LEN).map(|record| {
281 let word = |at: usize| {
282 u32::from_le_bytes([record[at], record[at + 1], record[at + 2], record[at + 3]])
283 };
284 let half = |at: usize| u16::from_le_bytes([record[at], record[at + 1]]);
285 let flags = half(SPAN_FLAGS_AT);
286 let paint = u64::from(word(SPAN_COLOR_AT)) | (u64::from(word(SPAN_COLOR_AT + 4)) << 32);
287 let background = u64::from(word(SPAN_BACKGROUND_AT))
288 | (u64::from(word(SPAN_BACKGROUND_AT + 4)) << 32);
289 let handler =
290 u64::from(word(SPAN_HANDLER_AT)) | (u64::from(word(SPAN_HANDLER_AT + 4)) << 32);
291 TextSpan {
292 start: word(SPAN_START_AT),
293 length: word(SPAN_LENGTH_AT),
294 type_role: TypeRole::try_from(half(SPAN_ROLE_AT)).ok(),
295 color: Paint::from_bits(paint),
296 background: Paint::from_bits(background),
297 bold: flags & FLAG_BOLD != 0,
298 italic: flags & FLAG_ITALIC != 0,
299 underline: flags & FLAG_UNDERLINE != 0,
300 strikethrough: flags & FLAG_STRIKETHROUGH != 0,
301 on_click: (handler != 0).then_some(handler),
302 }
303 })
304 }
305
306 pub fn from_markdown(source: &str) -> (String, Self) {
315 let mut text = String::with_capacity(source.len());
316 let mut spans = Vec::new();
317 let bytes = source.as_bytes();
318 let mut at = 0;
319 while at < bytes.len() {
320 let (marker, span_of): Marked = if bytes[at..].starts_with(b"**") {
321 (b"**", |start, length| TextSpan::new(start, length).bold())
322 } else if bytes[at] == b'*' {
323 (b"*", |start, length| TextSpan::new(start, length).italic())
324 } else if bytes[at] == b'`' {
325 (b"`", |start, length| {
326 TextSpan::new(start, length).with_role(TypeRole::Mono)
327 })
328 } else {
329 text.push(source[at..].chars().next().expect("a char boundary"));
330 at += source[at..].chars().next().map_or(1, char::len_utf8);
331 continue;
332 };
333 let body = at + marker.len();
334 let Some(end) = find(&bytes[body..], marker).map(|offset| body + offset) else {
335 text.push_str(&source[at..body]);
337 at = body;
338 continue;
339 };
340 let start = text.len() as u32;
341 text.push_str(&source[body..end]);
342 spans.push(span_of(start, text.len() as u32 - start));
343 at = end + marker.len();
344 }
345 (text, Self::new(spans))
346 }
347}
348
349fn encode_font_table(named: &[(u32, FontRefs)]) -> Option<Rc<[u8]>> {
352 if named.is_empty() {
353 return None;
354 }
355 let mut entries = Vec::with_capacity(named.len() * SPAN_FONT_ENTRY_LEN);
356 let mut lists: Vec<u8> = Vec::new();
357 let lists_at = 4 + named.len() * SPAN_FONT_ENTRY_LEN;
358 for (span, fonts) in named {
359 let offset = (lists_at + lists.len()) as u32;
360 let count = fonts.len() as u32;
361 entries.extend_from_slice(&span.to_le_bytes());
362 entries.extend_from_slice(&offset.to_le_bytes());
363 entries.extend_from_slice(&count.to_le_bytes());
364 let names_at = offset + count * FONT_REF_LEN as u32;
365 fonts.write_relocated(&mut lists, names_at);
366 }
367 let mut table = Vec::with_capacity(lists_at + lists.len());
368 table.extend_from_slice(&(named.len() as u32).to_le_bytes());
369 table.extend_from_slice(&entries);
370 table.extend_from_slice(&lists);
371 Some(table.into())
372}
373
374fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
375 haystack
376 .windows(needle.len())
377 .position(|window| window == needle)
378}