1mod iter;
2mod print_info;
3
4use std::{io::Write, sync::Arc};
5
6use crossterm::{
7 cursor, queue,
8 style::{Attribute, Attributes},
9};
10use duat_core::{
11 context::{self, Decode, Encode},
12 form::{CONTROL_CHAR_ID, Painter},
13 opts::PrintOpts,
14 session::TwoPointsPlace,
15 text::{Item, Part, SpawnId, Text, TwoPoints, txt},
16 ui::{
17 self, Caret, DynSpawnSpecs, PushSpecs,
18 traits::{CoreAccess, RawArea},
19 },
20};
21use iter::{print_iter, rev_print_iter};
22
23pub use self::print_info::PrintInfo;
24use crate::{AreaId, CStyle, Mutex, layout::Layouts, print_style, printer::Lines};
25
26#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
27#[bincode(crate = "duat_core::context::bincode")]
28pub struct Coord {
29 pub x: u32,
30 pub y: u32,
31}
32
33impl std::fmt::Debug for Coord {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 f.write_fmt(format_args!("x: {}, y: {}", self.x, self.y))
36 }
37}
38
39impl Coord {
40 pub fn new(x: u32, y: u32) -> Coord {
41 Coord { x, y }
42 }
43}
44
45#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
46#[bincode(crate = "duat_core::context::bincode")]
47pub struct Coords {
48 pub tl: Coord,
49 pub br: Coord,
50}
51
52impl Coords {
53 pub fn new(tl: Coord, br: Coord) -> Self {
54 Coords { tl, br }
55 }
56
57 pub fn width(&self) -> u32 {
58 self.br.x - self.tl.x
59 }
60
61 pub fn height(&self) -> u32 {
62 self.br.y - self.tl.y
63 }
64
65 pub fn area(&self) -> u32 {
66 self.width() * self.height()
67 }
68
69 pub fn intersects(&self, other: Self) -> bool {
70 self.tl.x < other.br.x
71 && self.br.x > other.tl.x
72 && self.tl.y < other.br.y
73 && self.br.y > other.tl.y
74 }
75}
76
77#[derive(Clone)]
78pub struct Area {
79 layouts: Layouts,
80 id: AreaId,
81 ansi_codes: Arc<Mutex<micromap::Map<CStyle, String, 16>>>,
82}
83
84impl PartialEq for Area {
85 fn eq(&self, other: &Self) -> bool {
86 self.id == other.id
87 }
88}
89
90impl Area {
91 pub(crate) fn new(id: AreaId, layouts: Layouts) -> Self {
92 Self { layouts, id, ansi_codes: Arc::default() }
93 }
94
95 fn print<'a>(
96 &self,
97 text: &Text,
98 opts: PrintOpts,
99 mut painter: Painter,
100 mut f: impl FnMut(&Caret, &Item) + 'a,
101 ) {
102 const SPACES: &[u8] = &[b' '; 3000];
103
104 fn end_line(
105 lines: &mut Lines,
106 painter: &Painter,
107 ansi_codes: &mut micromap::Map<CStyle, String, 16>,
108 len: u32,
109 max_x: u32,
110 ) {
111 let mut default_style = painter.get_default();
112 default_style.style.foreground_color = None;
113 default_style.style.underline_color = None;
114 default_style.style.attributes = Attributes::from(Attribute::Reset);
115
116 print_style(lines, default_style.style, ansi_codes);
117 if lines.coords().br.x == max_x {
118 lines.write_all(b"\x1b[0K").unwrap();
119 } else {
120 lines
121 .write_all(&SPACES[..(lines.coords().width() - len) as usize])
122 .unwrap();
123 }
124 lines.flush().unwrap();
125 }
126
127 let (mut lines, iter, x_shift, max_x) = {
128 let Some(coords) = self.layouts.coords_of(self.id, true) else {
129 context::warn!("This Area was already deleted");
130 return;
131 };
132
133 let max = self
134 .layouts
135 .inspect(self.id, |_, layout| layout.max_value())
136 .unwrap();
137
138 if coords.width() == 0 || coords.height() == 0 {
139 return;
140 }
141
142 let (s_points, x_shift) = {
143 let mut info = self.layouts.get_info_of(self.id).unwrap();
144 let s_points = info.start_points(coords, text, opts);
145 self.layouts.set_info_of(self.id, info);
146 (s_points, info.x_shift())
147 };
148
149 let lines = Lines::new(coords);
150 let width = opts.wrap_width(coords.width()).unwrap_or(coords.width());
151 let iter = print_iter(text, s_points, width, opts);
152
153 (lines, iter, x_shift, max.x)
154 };
155
156 let mut observed_spawns = Vec::new();
157 let spawn_id = self
158 .layouts
159 .inspect(self.id, |rect, _| rect.spawn_id())
160 .unwrap();
161 let is_active = self.id == self.layouts.get_active_id();
162
163 let mut ansi_codes = self.ansi_codes.lock().unwrap();
164 let mut style_was_set = false;
165
166 enum Cursor {
167 Main,
168 Extra,
169 }
170
171 let tl_y = lines.coords().tl.y;
173 let mut y = tl_y;
174 let mut cursor = None;
175 let mut spawns_for_next: Vec<SpawnId> = Vec::new();
176 let mut last_len = 0;
177
178 for (caret, item) in iter {
179 let (painter, lines, ansi_codes) = (&mut painter, &mut lines, &mut ansi_codes);
180 f(&caret, &item);
181
182 let Caret { x, len, wrap } = caret;
183 let Item { part, .. } = item;
184
185 if wrap {
186 if y == lines.coords().br.y {
187 break;
188 }
189 if y > lines.coords().tl.y {
190 end_line(lines, painter, ansi_codes, last_len, max_x);
191 }
192 let initial_space = x.saturating_sub(x_shift).min(lines.coords().width());
193 if initial_space > 0 {
194 let mut default_style = painter.get_default().style;
195 default_style.attributes.set(Attribute::Reset);
196 print_style(lines, default_style, ansi_codes);
197 lines.write_all(&SPACES[..initial_space as usize]).unwrap();
198 }
199 y += 1;
200
201 painter.reset_prev_style();
203 style_was_set = true;
204 last_len = initial_space;
205 }
206
207 let is_contained = x + len > x_shift && x < x_shift + lines.coords().width();
208
209 match part {
210 Part::Char(char) if is_contained => {
211 if let Some(str) = get_control_str(char) {
212 painter.apply(CONTROL_CHAR_ID, 100);
213 if style_was_set && let Some(style) = painter.relative_style() {
214 print_style(lines, style, ansi_codes);
215 }
216 lines.write_all(str.as_bytes()).unwrap();
217 painter.remove(CONTROL_CHAR_ID)
218 } else {
219 if style_was_set && let Some(style) = painter.relative_style() {
220 print_style(lines, style, ansi_codes);
221 }
222 match char {
223 '\t' => {
224 let truncated_start = x_shift.saturating_sub(x);
225 let truncated_end =
226 (x + len).saturating_sub(lines.coords().width() + x_shift);
227 let tab_len = len - (truncated_start + truncated_end);
228 lines.write_all(&SPACES[..tab_len as usize]).unwrap()
229 }
230 '\n' if opts.print_new_line => lines.write_all(b" ").unwrap(),
231 '\n' | '\r' => {}
232 char => {
233 let mut bytes = [0; 4];
234 char.encode_utf8(&mut bytes);
235 lines.write_all(&bytes[..char.len_utf8()]).unwrap();
236 }
237 }
238 }
239
240 if let Some(cursor) = cursor.take() {
241 match cursor {
242 Cursor::Main => painter.remove_main_caret(),
243 Cursor::Extra => painter.remove_extra_caret(),
244 }
245 if let Some(style) = painter.relative_style() {
246 print_style(lines, style, ansi_codes)
247 }
248 }
249 for id in spawns_for_next.drain(..) {
250 observed_spawns.push(id);
251 self.layouts.move_spawn_to(
252 id,
253 Coord::new(lines.coords().tl.x + x, y - 1),
254 len,
255 );
256 }
257
258 last_len = x + len - x_shift;
259 style_was_set = false;
260 }
261 Part::Char(_) => {
262 match cursor.take() {
263 Some(Cursor::Main) => painter.remove_main_caret(),
264 Some(Cursor::Extra) => painter.remove_extra_caret(),
265 None => {}
266 }
267 spawns_for_next.clear();
268 }
269 Part::PushForm(id, prio) => {
270 painter.apply(id, prio);
271 style_was_set = true;
272 }
273 Part::PopForm(id) => {
274 painter.remove(id);
275 style_was_set = true;
276 }
277 Part::MainCaret => {
278 if let Some(shape) = painter.main_cursor()
279 && is_active
280 {
281 lines.show_real_cursor();
282 queue!(lines, shape, cursor::SavePosition).unwrap();
283 } else {
284 cursor = Some(Cursor::Main);
285 lines.hide_real_cursor();
286 painter.apply_main_cursor();
287 style_was_set = true;
288 }
289 }
290 Part::ExtraCaret => {
291 cursor = Some(Cursor::Extra);
292 painter.apply_extra_cursor();
293 style_was_set = true;
294 }
295 Part::Spacer => {
296 let truncated_start = x_shift.saturating_sub(x).min(len);
297 let truncated_end = (x + len)
298 .saturating_sub(lines.coords().width().saturating_sub(x_shift))
299 .min(len);
300 let spacer_len = len - (truncated_start + truncated_end);
301 lines.write_all(&SPACES[0..spacer_len as usize]).unwrap();
302 last_len = (x + len)
303 .saturating_sub(x_shift)
304 .min(lines.coords().width());
305 }
306 Part::ResetState => print_style(lines, painter.reset(), ansi_codes),
307 Part::SpawnedWidget(id) => spawns_for_next.push(id),
308 Part::ToggleStart(_) | Part::ToggleEnd(_) => {
309 todo!("Toggles have not been implemented yet.")
310 }
311 Part::AlignLeft | Part::AlignCenter | Part::AlignRight => {}
312 }
313 }
314
315 end_line(&mut lines, &painter, &mut ansi_codes, last_len, max_x);
316
317 for _ in 0..lines.coords().br.y - y {
318 end_line(&mut lines, &painter, &mut ansi_codes, 0, max_x);
319 }
320
321 let spawns = text.get_spawned_ids();
322
323 self.layouts
324 .send_lines(self.id, spawn_id, lines, spawns, &observed_spawns);
325 }
326}
327
328impl RawArea for Area {
329 type Cache = PrintInfo;
330 type PrintInfo = PrintInfo;
331
332 fn push(
335 &self,
336 _: CoreAccess,
337 specs: PushSpecs,
338 on_files: bool,
339 cache: PrintInfo,
340 ) -> Option<(Area, Option<Area>)> {
341 let (child, parent) = self.layouts.push(self.id, specs, on_files, cache)?;
342
343 Some((
344 Self::new(child, self.layouts.clone()),
345 parent.map(|parent| Self::new(parent, self.layouts.clone())),
346 ))
347 }
348
349 fn delete(&self, _: CoreAccess) -> (bool, Vec<Self>) {
350 let (do_rm_window, rm_areas) = self.layouts.delete(self.id);
351 (
352 do_rm_window,
353 rm_areas
354 .into_iter()
355 .map(|id| Self::new(id, self.layouts.clone()))
356 .collect(),
357 )
358 }
359
360 fn swap(&self, _: CoreAccess, rhs: &Self) -> bool {
361 self.layouts.swap(self.id, rhs.id)
362 }
363
364 fn spawn(
365 &self,
366 _: CoreAccess,
367 spawn_id: SpawnId,
368 specs: DynSpawnSpecs,
369 cache: Self::Cache,
370 ) -> Option<Self> {
371 Some(Self::new(
372 self.layouts
373 .spawn_on_widget(self.id, spawn_id, specs, cache)?,
374 self.layouts.clone(),
375 ))
376 }
377
378 fn set_width(&self, _: CoreAccess, width: f32) -> Result<(), Text> {
379 if self
380 .layouts
381 .set_constraints(self.id, Some(width), None, None)
382 {
383 Ok(())
384 } else {
385 Err(txt!("Couldn't set the width to [a]{width}"))
386 }
387 }
388
389 fn set_height(&self, _: CoreAccess, height: f32) -> Result<(), Text> {
390 if self
391 .layouts
392 .set_constraints(self.id, None, Some(height), None)
393 {
394 Ok(())
395 } else {
396 Err(txt!("Couldn't set the height to [a]{height}"))
397 }
398 }
399
400 fn hide(&self, _: CoreAccess) -> Result<(), Text> {
401 if self
402 .layouts
403 .set_constraints(self.id, None, None, Some(true))
404 {
405 Ok(())
406 } else {
407 Err(txt!("Couldn't hide the Area"))
408 }
409 }
410
411 fn reveal(&self, _: CoreAccess) -> Result<(), Text> {
412 if self
413 .layouts
414 .set_constraints(self.id, None, None, Some(false))
415 {
416 Ok(())
417 } else {
418 Err(txt!("Couldn't reveal the Area"))
419 }
420 }
421
422 fn width_of_text(&self, _: CoreAccess, opts: PrintOpts, text: &Text) -> Result<f32, Text> {
423 let max = self
424 .layouts
425 .inspect(self.id, |_, layout| layout.max_value())
426 .ok_or_else(|| txt!("This Area was already deleted"))?;
427
428 let iter = iter::print_iter(text, TwoPoints::default(), max.x, opts);
429
430 Ok(iter.map(|(c, _)| c.x + c.len).max().unwrap_or(0) as f32)
432 }
433
434 fn scroll_ver(&self, _: CoreAccess, text: &Text, by: i32, opts: PrintOpts) {
435 if by == 0 {
436 return;
437 }
438
439 let Some(coords) = self.layouts.coords_of(self.id, false) else {
440 context::warn!("This Area was already deleted");
441 return;
442 };
443
444 if coords.width() == 0 || coords.height() == 0 {
445 return;
446 }
447
448 let mut info = self.layouts.get_info_of(self.id).unwrap();
449 info.scroll_ver(by, coords, text, opts);
450 self.layouts.set_info_of(self.id, info);
451 }
452
453 fn scroll_around_points(&self, _: CoreAccess, text: &Text, points: TwoPoints, opts: PrintOpts) {
456 let Some(coords) = self.layouts.coords_of(self.id, false) else {
457 context::warn!("This Area was already deleted");
458 return;
459 };
460
461 if coords.width() == 0 || coords.height() == 0 {
462 return;
463 }
464
465 let mut info = self.layouts.get_info_of(self.id).unwrap();
466 info.scroll_around(points.real, coords, text, opts);
467 self.layouts.set_info_of(self.id, info);
468 }
469
470 fn scroll_to_points(&self, _: CoreAccess, text: &Text, points: TwoPoints, opts: PrintOpts) {
471 let Some(coords) = self.layouts.coords_of(self.id, false) else {
472 context::warn!("This Area was already deleted");
473 return;
474 };
475
476 if coords.width() == 0 || coords.height() == 0 {
477 return;
478 }
479
480 let mut info = self.layouts.get_info_of(self.id).unwrap();
481 info.scroll_to_points(points, coords, text, opts);
482 self.layouts.set_info_of(self.id, info);
483 }
484
485 fn set_as_active(&self, _: CoreAccess) {
486 self.layouts.set_active_id(self.id);
487 }
488
489 fn print(&self, _: CoreAccess, text: &Text, opts: PrintOpts, painter: Painter) {
490 self.print(text, opts, painter, |_, _| {})
491 }
492
493 fn print_with<'a>(
494 &self,
495 _: CoreAccess,
496 text: &Text,
497 opts: PrintOpts,
498 painter: Painter,
499 f: impl FnMut(&Caret, &Item) + 'a,
500 ) {
501 self.print(text, opts, painter, f)
502 }
503
504 fn set_print_info(&self, _: CoreAccess, info: Self::PrintInfo) {
507 self.layouts.set_info_of(self.id, info);
508 }
509
510 fn print_iter<'a>(
511 &self,
512 ca: CoreAccess,
513 text: &'a Text,
514 points: TwoPoints,
515 opts: PrintOpts,
516 ) -> impl Iterator<Item = (Caret, Item)> + 'a {
517 let width = (self.bottom_right(ca).x - self.top_left(ca).x) as u32;
518 print_iter(text, points, width, opts)
519 }
520
521 fn rev_print_iter<'a>(
522 &self,
523 ca: CoreAccess,
524 text: &'a Text,
525 points: TwoPoints,
526 opts: PrintOpts,
527 ) -> impl Iterator<Item = (Caret, Item)> + 'a {
528 let width = (self.bottom_right(ca).x - self.top_left(ca).x) as u32;
529 rev_print_iter(text, points, opts.wrap_width(width).unwrap_or(width), opts)
530 }
531
532 fn has_changed(&self, _: CoreAccess) -> bool {
533 self.layouts
534 .inspect(self.id, |rect, layout| rect.has_changed(layout))
535 .unwrap_or(false)
536 }
537
538 fn is_master_of(&self, _: CoreAccess, other: &Self) -> bool {
539 if other.id == self.id {
540 return true;
541 }
542
543 let mut parent_id = other.id;
544
545 self.layouts.inspect(self.id, |_, layout| {
546 while let Some((_, parent)) = layout.get_parent(parent_id) {
547 parent_id = parent.id();
548 if parent.id() == self.id {
549 break;
550 }
551 }
552 });
553
554 parent_id == self.id
555 }
556
557 fn get_cluster_master(&self, _: CoreAccess) -> Option<Self> {
558 let id = self
559 .layouts
560 .inspect(self.id, |_, layout| layout.get_cluster_master(self.id))??;
561
562 Some(Self {
563 layouts: self.layouts.clone(),
564 id,
565 ansi_codes: Arc::default(),
566 })
567 }
568
569 fn cache(&self, _: CoreAccess) -> Option<Self::Cache> {
570 let info = self.layouts.get_info_of(self.id)?.for_caching();
571 Some(info)
572 }
573
574 fn top_left(&self, _: CoreAccess) -> ui::Coord {
575 self.layouts
576 .coords_of(self.id, false)
577 .map(|coords| ui::Coord {
578 x: coords.tl.x as f32,
579 y: coords.tl.y as f32,
580 })
581 .unwrap_or_default()
582 }
583
584 fn bottom_right(&self, _: CoreAccess) -> ui::Coord {
585 self.layouts
586 .coords_of(self.id, false)
587 .map(|coords| ui::Coord {
588 x: coords.br.x as f32,
589 y: coords.br.y as f32,
590 })
591 .unwrap_or_default()
592 }
593
594 fn coord_at_points(
595 &self,
596 _: CoreAccess,
597 text: &Text,
598 points: TwoPoints,
599 opts: PrintOpts,
600 ) -> Option<ui::Coord> {
601 let Some(coords) = self.layouts.coords_of(self.id, false) else {
602 context::warn!("This Area was already deleted");
603 return None;
604 };
605
606 if coords.width() == 0 || coords.height() == 0 {
607 return None;
608 }
609
610 let (s_points, x_shift) = {
611 let mut info = self.layouts.get_info_of(self.id).unwrap();
612 let s_points = info.start_points(coords, text, opts);
613 self.layouts.set_info_of(self.id, info);
614 (s_points, info.x_shift())
615 };
616
617 let mut row = coords.tl.y;
618 for (caret, item) in print_iter(text, s_points, coords.width(), opts) {
619 row += caret.wrap as u32;
620
621 if row > coords.br.y {
622 break;
623 }
624
625 if item.points() == points && item.part.is_char() {
626 if caret.x >= x_shift && caret.x <= x_shift + coords.width() {
627 return Some(ui::Coord {
628 x: (coords.tl.x + caret.x - x_shift) as f32,
629 y: (row - 1) as f32,
630 });
631 } else {
632 break;
633 }
634 }
635 }
636
637 None
638 }
639
640 fn points_at_coord(
641 &self,
642 _: CoreAccess,
643 text: &Text,
644 coord: ui::Coord,
645 opts: PrintOpts,
646 ) -> Option<TwoPointsPlace> {
647 let Some(coords) = self.layouts.coords_of(self.id, false) else {
648 context::warn!("This Area was already deleted");
649 return None;
650 };
651
652 if coords.width() == 0 || coords.height() == 0 {
653 return None;
654 } else if !(coords.tl.x..coords.br.x).contains(&(coord.x as u32))
655 || !(coords.tl.y..coords.br.y).contains(&(coord.y as u32))
656 {
657 context::warn!("Coordinate not contained in area");
658 return None;
659 }
660
661 let (s_points, x_shift) = {
662 let mut info = self.layouts.get_info_of(self.id).unwrap();
663 let s_points = info.start_points(coords, text, opts);
664 self.layouts.set_info_of(self.id, info);
665 (s_points, info.x_shift())
666 };
667
668 let mut row = coords.tl.y;
669 let mut backup = None;
670 for (caret, item) in print_iter(text, s_points, coords.width(), opts) {
671 row += caret.wrap as u32;
672
673 if row > coord.y as u32 + 1 {
674 return backup;
675 } else if row == coord.y as u32 + 1
676 && let Some(col) = caret.x.checked_sub(x_shift)
677 {
678 if (coords.tl.x + col..coords.tl.x + col + caret.len).contains(&(coord.x as u32)) {
679 return Some(TwoPointsPlace::Within(item.points()));
680 } else if coords.tl.x + col >= coord.x as u32 {
681 return backup;
682 }
683 }
684
685 if item.part.is_char() {
686 backup = Some(TwoPointsPlace::AheadOf(item.points()));
687 }
688 }
689
690 None
691 }
692
693 fn start_points(&self, _: CoreAccess, text: &Text, opts: PrintOpts) -> TwoPoints {
694 let Some(coords) = self.layouts.coords_of(self.id, false) else {
695 context::warn!("This Area was already deleted");
696 return Default::default();
697 };
698
699 let mut info = self.layouts.get_info_of(self.id).unwrap();
700 let start_points = info.start_points(coords, text, opts);
701 self.layouts.set_info_of(self.id, info);
702
703 start_points
704 }
705
706 fn end_points(&self, _: CoreAccess, text: &Text, opts: PrintOpts) -> TwoPoints {
707 let Some(coords) = self.layouts.coords_of(self.id, false) else {
708 context::warn!("This Area was already deleted");
709 return Default::default();
710 };
711
712 let mut info = self.layouts.get_info_of(self.id).unwrap();
713 let end_points = info.end_points(coords, text, opts);
714 self.layouts.set_info_of(self.id, info);
715
716 end_points
717 }
718
719 fn get_print_info(&self, _: CoreAccess) -> Self::PrintInfo {
720 self.layouts.get_info_of(self.id).unwrap_or_default()
721 }
722
723 fn is_active(&self, _: CoreAccess) -> bool {
724 self.layouts.get_active_id() == self.id
725 }
726}
727
728const fn get_control_str(char: char) -> Option<&'static str> {
729 match char {
730 '\0' => Some("^@"),
731 '\u{01}' => Some("^A"),
732 '\u{02}' => Some("^B"),
733 '\u{03}' => Some("^C"),
734 '\u{04}' => Some("^D"),
735 '\u{05}' => Some("^E"),
736 '\u{06}' => Some("^F"),
737 '\u{07}' => Some("^G"),
738 '\u{08}' => Some("^H"),
739 '\u{0b}' => Some("^K"),
740 '\u{0c}' => Some("^L"),
741 '\u{0e}' => Some("^N"),
742 '\u{0f}' => Some("^O"),
743 '\u{10}' => Some("^P"),
744 '\u{11}' => Some("^Q"),
745 '\u{12}' => Some("^R"),
746 '\u{13}' => Some("^S"),
747 '\u{14}' => Some("^T"),
748 '\u{15}' => Some("^U"),
749 '\u{16}' => Some("^V"),
750 '\u{17}' => Some("^W"),
751 '\u{18}' => Some("^X"),
752 '\u{19}' => Some("^Y"),
753 '\u{1a}' => Some("^Z"),
754 '\u{1b}' => Some("^["),
755 '\u{1c}' => Some("^\\"),
756 '\u{1d}' => Some("^]"),
757 '\u{1e}' => Some("^^"),
758 '\u{1f}' => Some("^_"),
759 '\u{80}' => Some("<80>"),
760 '\u{81}' => Some("<81>"),
761 '\u{82}' => Some("<82>"),
762 '\u{83}' => Some("<83>"),
763 '\u{84}' => Some("<84>"),
764 '\u{85}' => Some("<85>"),
765 '\u{86}' => Some("<86>"),
766 '\u{87}' => Some("<87>"),
767 '\u{88}' => Some("<88>"),
768 '\u{89}' => Some("<89>"),
769 '\u{8a}' => Some("<8a>"),
770 '\u{8b}' => Some("<8b>"),
771 '\u{8c}' => Some("<8c>"),
772 '\u{8d}' => Some("<8d>"),
773 '\u{8e}' => Some("<8e>"),
774 '\u{8f}' => Some("<8f>"),
775 '\u{90}' => Some("<90>"),
776 '\u{91}' => Some("<91>"),
777 '\u{92}' => Some("<92>"),
778 '\u{93}' => Some("<93>"),
779 '\u{94}' => Some("<94>"),
780 '\u{95}' => Some("<95>"),
781 '\u{96}' => Some("<96>"),
782 '\u{97}' => Some("<97>"),
783 '\u{98}' => Some("<98>"),
784 '\u{99}' => Some("<99>"),
785 '\u{9a}' => Some("<9a>"),
786 '\u{9b}' => Some("<9b>"),
787 '\u{9c}' => Some("<9c>"),
788 '\u{9d}' => Some("<9d>"),
789 '\u{9e}' => Some("<9e>"),
790 '\u{9f}' => Some("<9f>"),
791 _ => None,
792 }
793}