1use crate::crosswords::grid::GridSquare;
13use crate::crosswords::style::{StyleId, DEFAULT_STYLE_ID};
14use crate::crosswords::Column;
15use crate::crosswords::Row;
16use bitflags::bitflags;
17use std::sync::atomic::{AtomicU32, Ordering};
18use std::sync::Arc;
19
20const CODEPOINT_SHIFT: u64 = 0;
45const CODEPOINT_MASK: u64 = (1 << 21) - 1;
46
47const WIDE_SHIFT: u64 = 21;
48const WIDE_MASK: u64 = 0b11 << WIDE_SHIFT;
49
50const CELL_FLAGS_SHIFT: u64 = 23;
51const CELL_FLAGS_MASK: u64 = 0x7F << CELL_FLAGS_SHIFT; const CONTENT_TAG_SHIFT: u64 = 30;
54
55const STYLE_ID_SHIFT: u64 = 32;
56const STYLE_ID_MASK: u64 = 0xFFFF << STYLE_ID_SHIFT;
57
58const EXTRAS_ID_SHIFT: u64 = 48;
59const EXTRAS_ID_MASK: u64 = 0xFFFF << EXTRAS_ID_SHIFT;
60
61const BG_PALETTE_SHIFT: u64 = 32;
63const BG_PALETTE_MASK: u64 = 0xFF << BG_PALETTE_SHIFT;
64
65const BG_RGB_R_SHIFT: u64 = 32;
66const BG_RGB_G_SHIFT: u64 = 40;
67const BG_RGB_B_SHIFT: u64 = 48;
68
69#[repr(u8)]
71#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72pub enum Wide {
73 Narrow = 0,
75 Wide = 1,
77 Spacer = 2,
79 LeadingSpacer = 3,
82}
83
84impl Wide {
85 #[inline]
86 fn from_bits(bits: u64) -> Wide {
87 match (bits >> WIDE_SHIFT) & 0b11 {
88 0 => Wide::Narrow,
89 1 => Wide::Wide,
90 2 => Wide::Spacer,
91 _ => Wide::LeadingSpacer,
92 }
93 }
94}
95
96#[repr(u8)]
102#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103pub enum ContentTag {
104 Codepoint = 0,
106 BgPalette = 1,
109 BgRgb = 2,
112}
113
114impl ContentTag {
115 #[inline(always)]
120 pub fn from_bits(bits: u64) -> ContentTag {
121 match (bits >> CONTENT_TAG_SHIFT) & 0b11 {
122 0 => ContentTag::Codepoint,
123 1 => ContentTag::BgPalette,
124 _ => ContentTag::BgRgb,
125 }
126 }
127}
128
129bitflags! {
130 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
133 pub struct CellFlags: u8 {
134 const WRAPLINE = 1 << 0;
136 const RESERVED_GRAPHICS = 1 << 1;
141 const HYPERLINK = 1 << 2;
143 const GRAPHEME = 1 << 3;
145 }
146}
147
148static HYPERLINK_ID_SUFFIX: AtomicU32 = AtomicU32::new(0);
150
151#[derive(Debug, Clone, PartialEq, Eq, Hash)]
152pub struct Hyperlink {
153 inner: Arc<HyperlinkInner>,
154}
155
156impl Hyperlink {
157 pub fn new<T: ToString>(id: Option<T>, uri: T) -> Self {
158 let inner = Arc::new(HyperlinkInner::new(id, uri));
159 Self { inner }
160 }
161
162 pub fn id(&self) -> &str {
163 &self.inner.id
164 }
165
166 pub fn uri(&self) -> &str {
167 &self.inner.uri
168 }
169}
170
171#[derive(Debug, PartialEq, Eq, Hash)]
172struct HyperlinkInner {
173 id: String,
174 uri: String,
175}
176
177impl HyperlinkInner {
178 pub fn new<T: ToString>(id: Option<T>, uri: T) -> Self {
179 let id = match id {
180 Some(id) => id.to_string(),
181 None => {
182 let mut id = HYPERLINK_ID_SUFFIX
183 .fetch_add(1, Ordering::Relaxed)
184 .to_string();
185 id.push_str("_rio");
186 id
187 }
188 };
189
190 Self {
191 id,
192 uri: uri.to_string(),
193 }
194 }
195}
196
197pub type ExtrasId = u16;
199
200#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
203pub struct Extras {
204 pub zerowidth: Vec<char>,
205 pub hyperlink: Option<Hyperlink>,
206}
207
208impl Extras {
209 #[inline]
210 pub fn is_empty(&self) -> bool {
211 self.zerowidth.is_empty() && self.hyperlink.is_none()
212 }
213}
214
215#[repr(transparent)]
216#[derive(Clone, Copy, Debug, PartialEq, Eq)]
217pub struct Square(u64);
218
219impl Default for Square {
220 #[inline]
221 fn default() -> Square {
222 Square(0)
223 }
224}
225
226impl Square {
227 #[inline]
229 pub fn from_char(c: char) -> Self {
230 let mut s = Square(0);
231 s.set_c(c);
232 s
233 }
234
235 #[inline]
243 pub fn from_template(template: Square, c: char) -> Self {
244 let cp = c as u32 as u64;
245 debug_assert!(cp <= CODEPOINT_MASK, "codepoint exceeds 21 bits");
246 debug_assert!(
247 template.0 & CODEPOINT_MASK == 0,
248 "template must have a zero codepoint field"
249 );
250 Square(template.0 | cp)
251 }
252
253 #[inline(always)]
258 pub fn raw(self) -> u64 {
259 self.0
260 }
261
262 #[inline]
268 pub fn c(self) -> char {
269 let cp = ((self.0 >> CODEPOINT_SHIFT) & CODEPOINT_MASK) as u32;
270 char::from_u32(cp).unwrap_or('\0')
272 }
273
274 #[inline]
275 pub fn set_c(&mut self, c: char) {
276 let cp = c as u32 as u64;
277 debug_assert!(cp <= CODEPOINT_MASK, "codepoint exceeds 21 bits");
278 self.0 = (self.0 & !CODEPOINT_MASK) | ((cp & CODEPOINT_MASK) << CODEPOINT_SHIFT);
279 }
280
281 #[inline]
285 pub fn needs_wide_cleanup(self) -> bool {
286 matches!(self.wide(), Wide::Wide | Wide::Spacer)
287 }
288
289 #[inline]
290 pub fn wide(self) -> Wide {
291 Wide::from_bits(self.0)
292 }
293
294 #[inline]
295 pub fn set_wide(&mut self, w: Wide) {
296 self.0 = (self.0 & !WIDE_MASK) | ((w as u64) << WIDE_SHIFT);
297 }
298
299 #[inline]
300 pub fn cell_flags(self) -> CellFlags {
301 let bits = ((self.0 & CELL_FLAGS_MASK) >> CELL_FLAGS_SHIFT) as u8;
302 CellFlags::from_bits_truncate(bits)
303 }
304
305 #[inline]
306 pub fn set_cell_flags(&mut self, f: CellFlags) {
307 self.0 = (self.0 & !CELL_FLAGS_MASK) | ((f.bits() as u64) << CELL_FLAGS_SHIFT);
308 }
309
310 #[inline]
311 pub fn insert_cell_flag(&mut self, f: CellFlags) {
312 let mut cur = self.cell_flags();
313 cur.insert(f);
314 self.set_cell_flags(cur);
315 }
316
317 #[inline]
318 pub fn remove_cell_flag(&mut self, f: CellFlags) {
319 let mut cur = self.cell_flags();
320 cur.remove(f);
321 self.set_cell_flags(cur);
322 }
323
324 #[inline]
325 pub fn contains_cell_flag(self, f: CellFlags) -> bool {
326 self.cell_flags().contains(f)
327 }
328
329 #[inline(always)]
337 pub fn style_id(self) -> StyleId {
338 ((self.0 & STYLE_ID_MASK) >> STYLE_ID_SHIFT) as StyleId
339 }
340
341 #[inline]
342 pub fn set_style_id(&mut self, id: StyleId) {
343 self.0 = (self.0 & !STYLE_ID_MASK) | ((id as u64) << STYLE_ID_SHIFT);
344 }
345
346 #[inline]
351 pub fn style_id_checked(self) -> Option<StyleId> {
352 match self.content_tag() {
353 ContentTag::Codepoint => Some(self.style_id()),
354 ContentTag::BgPalette | ContentTag::BgRgb => None,
355 }
356 }
357
358 #[inline]
361 pub fn extras_id_checked(self) -> Option<ExtrasId> {
362 match self.content_tag() {
363 ContentTag::Codepoint => self.extras_id(),
364 ContentTag::BgPalette | ContentTag::BgRgb => None,
365 }
366 }
367
368 #[inline(always)]
375 pub fn extras_id(self) -> Option<ExtrasId> {
376 let id = ((self.0 & EXTRAS_ID_MASK) >> EXTRAS_ID_SHIFT) as ExtrasId;
377 if id == 0 {
378 None
379 } else {
380 Some(id)
381 }
382 }
383
384 #[inline]
385 pub fn set_extras_id(&mut self, id: Option<ExtrasId>) {
386 let bits = id.unwrap_or(0) as u64;
387 self.0 = (self.0 & !EXTRAS_ID_MASK) | (bits << EXTRAS_ID_SHIFT);
388 }
389
390 #[inline]
391 pub fn content_tag(self) -> ContentTag {
392 ContentTag::from_bits(self.0)
393 }
394
395 #[inline]
399 pub fn set_bg_palette(&mut self, idx: u8) {
400 let preserved = self.0 & CELL_FLAGS_MASK;
401 self.0 = preserved
402 | ((ContentTag::BgPalette as u64) << CONTENT_TAG_SHIFT)
403 | ((idx as u64) << BG_PALETTE_SHIFT);
404 }
405
406 #[inline]
408 pub fn set_bg_rgb(&mut self, r: u8, g: u8, b: u8) {
409 let preserved = self.0 & CELL_FLAGS_MASK;
410 self.0 = preserved
411 | ((ContentTag::BgRgb as u64) << CONTENT_TAG_SHIFT)
412 | ((r as u64) << BG_RGB_R_SHIFT)
413 | ((g as u64) << BG_RGB_G_SHIFT)
414 | ((b as u64) << BG_RGB_B_SHIFT);
415 }
416
417 #[inline]
420 pub fn bg_palette_index(self) -> u8 {
421 ((self.0 & BG_PALETTE_MASK) >> BG_PALETTE_SHIFT) as u8
422 }
423
424 #[inline]
427 pub fn bg_rgb(self) -> (u8, u8, u8) {
428 (
429 ((self.0 >> BG_RGB_R_SHIFT) & 0xFF) as u8,
430 ((self.0 >> BG_RGB_G_SHIFT) & 0xFF) as u8,
431 ((self.0 >> BG_RGB_B_SHIFT) & 0xFF) as u8,
432 )
433 }
434
435 #[inline]
438 pub fn is_bg_only(self) -> bool {
439 !matches!(self.content_tag(), ContentTag::Codepoint)
440 }
441
442 #[inline]
444 pub fn clear(&mut self) {
445 *self = Square(0);
446 }
447
448 #[inline]
451 pub fn reset(&mut self, template: Square) {
452 let new = Square(0).with_style_id(template.style_id());
453 *self = new;
454 }
455
456 #[inline]
458 pub fn with_style_id(mut self, id: StyleId) -> Self {
459 self.set_style_id(id);
460 self
461 }
462
463 #[inline]
464 pub fn is_default(self) -> bool {
465 self.0 == 0
466 }
467
468 #[inline]
469 pub fn is_wide(self) -> bool {
470 matches!(self.wide(), Wide::Wide)
471 }
472
473 #[inline]
474 pub fn is_spacer(self) -> bool {
475 matches!(self.wide(), Wide::Spacer)
476 }
477
478 #[inline]
479 pub fn is_leading_spacer(self) -> bool {
480 matches!(self.wide(), Wide::LeadingSpacer)
481 }
482
483 #[inline]
484 pub fn wrapline(self) -> bool {
485 self.contains_cell_flag(CellFlags::WRAPLINE)
486 }
487
488 #[inline]
489 pub fn set_wrapline(&mut self, on: bool) {
490 if on {
491 self.insert_cell_flag(CellFlags::WRAPLINE);
492 } else {
493 self.remove_cell_flag(CellFlags::WRAPLINE);
494 }
495 }
496
497 #[inline]
498 pub fn has_extras(self) -> bool {
499 self.extras_id_checked().is_some()
500 }
501
502 #[inline]
503 pub fn has_grapheme(self) -> bool {
504 self.contains_cell_flag(CellFlags::GRAPHEME)
505 }
506
507 #[inline]
508 pub fn has_hyperlink(self) -> bool {
509 self.contains_cell_flag(CellFlags::HYPERLINK)
510 }
511}
512
513impl GridSquare for Square {
514 #[inline]
515 fn carries_style(&self) -> bool {
516 matches!(self.style_id_checked(), Some(id) if id != DEFAULT_STYLE_ID)
517 }
518
519 #[inline]
520 fn is_empty(&self) -> bool {
521 if self.0 == 0 {
522 return true;
523 }
524 if self.is_bg_only() {
527 return false;
528 }
529 (self.c() == '\0' || self.c() == '\t')
530 && self.style_id() == DEFAULT_STYLE_ID
531 && self.extras_id().is_none()
532 && !self.contains_cell_flag(CellFlags::WRAPLINE)
533 && matches!(self.wide(), Wide::Narrow)
534 }
535
536 #[inline]
537 fn reset(&mut self, template: &Self) {
538 let style_id = template.style_id();
539 *self = Square(0).with_style_id(style_id);
540 }
541}
542
543pub trait LineLength {
544 fn line_length(&self) -> Column;
550}
551
552impl LineLength for Row<Square> {
553 fn line_length(&self) -> Column {
554 let mut length = Column(0);
555
556 if self[Column(self.len() - 1)].wrapline() {
557 return Column(self.len());
558 }
559
560 for (index, cell) in self[..].iter().rev().enumerate() {
561 if cell.c() != '\0' || cell.has_extras() {
562 length = Column(self.len() - index);
563 break;
564 }
565 }
566
567 length
568 }
569}
570
571pub trait ResetDiscriminant<T> {
572 fn discriminant(&self) -> T;
574}
575
576impl<T: Copy> ResetDiscriminant<T> for T {
577 fn discriminant(&self) -> T {
578 *self
579 }
580}
581
582impl ResetDiscriminant<StyleId> for Square {
583 fn discriminant(&self) -> StyleId {
584 self.style_id()
585 }
586}
587
588#[cfg(test)]
589mod tests {
590 use super::*;
591
592 use std::mem;
593
594 use crate::crosswords::grid::row::Row;
595 use crate::crosswords::pos::Column;
596
597 #[test]
598 fn line_length_ignores_bg_only_cells_regardless_of_color() {
599 let mut row: Row<Square> = Row::new(4);
605 row[Column(1)].set_bg_rgb(10, 20, 0);
606 assert_eq!(row.line_length(), Column(0));
607
608 let mut row: Row<Square> = Row::new(4);
609 row[Column(1)].set_bg_rgb(10, 20, 30);
610 assert_eq!(row.line_length(), Column(0));
611
612 let mut row: Row<Square> = Row::new(4);
614 row[Column(0)].set_bg_rgb(10, 20, 30);
615 row[Column(1)] = Square::from_char('x');
616 assert_eq!(row.line_length(), Column(2));
617 }
618
619 #[test]
620 fn checked_id_accessors_reject_bg_only_cells() {
621 let mut sq = Square::default();
622 sq.set_style_id(7);
623 assert_eq!(sq.style_id_checked(), Some(7));
624
625 let mut bg = Square::default();
626 bg.set_bg_rgb(1, 2, 3);
627 assert_eq!(bg.style_id_checked(), None);
628 assert_eq!(bg.extras_id_checked(), None);
629 assert!(!bg.has_extras());
630 }
631
632 #[test]
633 fn square_is_eight_bytes() {
634 assert_eq!(mem::size_of::<Square>(), 8);
636 }
637
638 #[test]
639 fn codepoint_round_trip() {
640 let mut s = Square(0);
641 s.set_c('🦀');
642 assert_eq!(s.c(), '🦀');
643 s.set_c('a');
644 assert_eq!(s.c(), 'a');
645 s.set_c('\0');
646 assert_eq!(s.c(), '\0');
647 }
648
649 #[test]
650 fn style_id_round_trip() {
651 let mut s = Square(0);
652 s.set_style_id(42);
653 assert_eq!(s.style_id(), 42);
654 s.set_style_id(0xFFFF);
655 assert_eq!(s.style_id(), 0xFFFF);
656 }
657
658 #[test]
659 fn extras_id_round_trip() {
660 let mut s = Square(0);
661 assert_eq!(s.extras_id(), None);
662 s.set_extras_id(Some(7));
663 assert_eq!(s.extras_id(), Some(7));
664 s.set_extras_id(None);
665 assert_eq!(s.extras_id(), None);
666 }
667
668 #[test]
669 fn wide_round_trip() {
670 let mut s = Square(0);
671 for w in [Wide::Narrow, Wide::Wide, Wide::Spacer, Wide::LeadingSpacer] {
672 s.set_wide(w);
673 assert_eq!(s.wide(), w);
674 }
675 }
676
677 #[test]
678 fn cell_flags_round_trip() {
679 let mut s = Square(0);
680 s.insert_cell_flag(CellFlags::WRAPLINE | CellFlags::GRAPHEME);
681 assert!(s.wrapline());
682 assert!(s.has_grapheme());
683 assert!(!s.has_hyperlink());
684 s.remove_cell_flag(CellFlags::WRAPLINE);
685 assert!(!s.wrapline());
686 assert!(s.has_grapheme());
687 }
688
689 #[test]
690 fn fields_are_independent() {
691 let mut s = Square(0);
692 s.set_c('Z');
693 s.set_style_id(0x1234);
694 s.set_extras_id(Some(0x5678));
695 s.set_wide(Wide::Wide);
696 s.insert_cell_flag(CellFlags::WRAPLINE);
697 assert_eq!(s.c(), 'Z');
698 assert_eq!(s.style_id(), 0x1234);
699 assert_eq!(s.extras_id(), Some(0x5678));
700 assert_eq!(s.wide(), Wide::Wide);
701 assert!(s.wrapline());
702 }
703
704 #[test]
705 fn bg_palette_round_trip() {
706 let mut s = Square(0);
707 s.set_bg_palette(42);
708 assert_eq!(s.content_tag(), ContentTag::BgPalette);
709 assert!(s.is_bg_only());
710 assert_eq!(s.bg_palette_index(), 42);
711 assert_eq!(s.c(), '\0');
713 }
722
723 #[test]
724 fn bg_rgb_round_trip() {
725 let mut s = Square(0);
726 s.set_bg_rgb(0x12, 0x34, 0x56);
727 assert_eq!(s.content_tag(), ContentTag::BgRgb);
728 assert!(s.is_bg_only());
729 assert_eq!(s.bg_rgb(), (0x12, 0x34, 0x56));
730 assert_eq!(s.c(), '\0');
731 }
736
737 #[test]
738 fn bg_only_preserves_wrapline() {
739 let mut s = Square(0);
740 s.set_wrapline(true);
741 s.set_bg_rgb(1, 2, 3);
742 assert!(s.wrapline());
743 assert_eq!(s.bg_rgb(), (1, 2, 3));
744 }
745
746 #[test]
747 fn bg_only_cells_are_not_empty() {
748 let mut s = Square(0);
749 s.set_bg_palette(7);
750 assert!(!<Square as crate::crosswords::grid::GridSquare>::is_empty(
751 &s
752 ));
753 }
754
755 #[test]
756 fn line_length_works() {
757 let mut row = Row::<Square>::new(10);
758 row[Column(5)].set_c('a');
759 assert_eq!(row.line_length(), Column(6));
760 }
761
762 #[test]
763 fn line_length_works_with_wrapline() {
764 let mut row = Row::<Square>::new(10);
765 row[Column(9)].set_wrapline(true);
766 assert_eq!(row.line_length(), Column(10));
767 }
768}