1use std::ops::Range;
2
3use crate::text::TextBrush;
4use crate::text::{FontStyle, FontWeight, FontWidth};
5use fontique::GenericFamily;
6use parley::style::{FontFamily, FontStack, StyleProperty, WordBreakStrength};
7use peniko::Color;
8
9#[derive(Clone, Debug, Eq, Hash, PartialEq)]
29pub enum FamilyOwned {
30 Name(String),
32 Serif,
34 SansSerif,
36 Cursive,
38 Fantasy,
40 Monospace,
42}
43
44impl FamilyOwned {
45 pub fn parse_list(s: &str) -> impl Iterator<Item = FamilyOwned> + '_ + Clone {
64 ParseList {
65 source: s.as_bytes(),
66 len: s.len(),
67 pos: 0,
68 }
69 }
70
71 fn to_font_family(&self) -> FontFamily<'_> {
73 match self {
74 FamilyOwned::Name(name) => FontFamily::Named(std::borrow::Cow::Borrowed(name.as_str())),
75 FamilyOwned::Serif => FontFamily::Generic(GenericFamily::Serif),
76 FamilyOwned::SansSerif => FontFamily::Generic(GenericFamily::SansSerif),
77 FamilyOwned::Cursive => FontFamily::Generic(GenericFamily::Cursive),
78 FamilyOwned::Fantasy => FontFamily::Generic(GenericFamily::Fantasy),
79 FamilyOwned::Monospace => FontFamily::Generic(GenericFamily::Monospace),
80 }
81 }
82
83 pub fn to_font_stack(families: &[FamilyOwned]) -> FontStack<'_> {
92 match families {
93 [] => FontStack::Single(FontFamily::Generic(GenericFamily::SansSerif)),
94 [single] => match single {
95 FamilyOwned::Name(name) => {
96 FontStack::Source(std::borrow::Cow::Borrowed(name.as_str()))
97 }
98 other => FontStack::Single(other.to_font_family()),
99 },
100 multiple => {
101 let list: Vec<FontFamily<'_>> =
102 multiple.iter().map(|f| f.to_font_family()).collect();
103 FontStack::List(std::borrow::Cow::Owned(list))
104 }
105 }
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq)]
123pub enum LineHeightValue {
124 Normal(f32),
126 Pt(f32),
128}
129impl LineHeightValue {
130 pub fn resolve(&self, font_size: f32) -> f32 {
131 match self {
132 LineHeightValue::Pt(value) => *value,
133 LineHeightValue::Normal(multiplier) => font_size * multiplier,
134 }
135 }
136}
137
138impl From<f32> for LineHeightValue {
139 fn from(value: f32) -> Self {
140 LineHeightValue::Normal(value)
141 }
142}
143
144impl From<f64> for LineHeightValue {
145 fn from(value: f64) -> Self {
146 LineHeightValue::Normal(value as f32)
147 }
148}
149
150impl From<i32> for LineHeightValue {
151 fn from(value: i32) -> Self {
152 LineHeightValue::Normal(value as f32)
153 }
154}
155
156#[derive(Clone, Debug)]
190pub struct Attrs<'a> {
191 pub font_size: f32,
193 line_height: LineHeightValue,
195 color: Option<Color>,
197 family: Option<&'a [FamilyOwned]>,
199 weight: Option<FontWeight>,
201 style: Option<FontStyle>,
203 font_width: Option<FontWidth>,
205 word_break: Option<WordBreakStrength>,
207 metadata: Option<usize>,
209}
210
211impl Default for Attrs<'_> {
212 fn default() -> Self {
213 Self::new()
214 }
215}
216
217impl<'a> Attrs<'a> {
218 pub fn new() -> Self {
220 Self {
221 font_size: 16.0,
222 line_height: LineHeightValue::Normal(1.0),
223 color: None,
224 family: None,
225 weight: None,
226 style: None,
227 font_width: None,
228 word_break: None,
229 metadata: None,
230 }
231 }
232
233 pub fn color(mut self, color: Color) -> Self {
235 self.color = Some(color);
236 self
237 }
238
239 pub fn family(mut self, family: &'a [FamilyOwned]) -> Self {
241 self.family = Some(family);
242 self
243 }
244
245 pub fn font_width(mut self, stretch: FontWidth) -> Self {
247 self.font_width = Some(stretch);
248 self
249 }
250
251 pub fn font_style(mut self, font_style: FontStyle) -> Self {
253 self.style = Some(font_style);
254 self
255 }
256
257 pub fn weight(mut self, weight: FontWeight) -> Self {
259 self.weight = Some(weight);
260 self
261 }
262
263 pub fn raw_weight(mut self, weight: u16) -> Self {
265 self.weight = Some(FontWeight::new(weight as f32));
266 self
267 }
268
269 pub fn font_size(mut self, font_size: f32) -> Self {
271 self.font_size = font_size;
272 self
273 }
274
275 pub fn word_break(mut self, word_break: WordBreakStrength) -> Self {
277 self.word_break = Some(word_break);
278 self
279 }
280
281 pub fn line_height(mut self, line_height: LineHeightValue) -> Self {
283 self.line_height = line_height;
284 self
285 }
286
287 pub fn metadata(mut self, metadata: usize) -> Self {
292 self.metadata = Some(metadata);
293 self
294 }
295
296 pub fn get_color(&self) -> Option<Color> {
298 self.color
299 }
300
301 pub fn get_line_height(&self) -> LineHeightValue {
303 self.line_height
304 }
305
306 pub fn get_family(&self) -> Option<&'a [FamilyOwned]> {
308 self.family
309 }
310
311 pub fn get_weight(&self) -> Option<FontWeight> {
313 self.weight
314 }
315
316 pub fn get_font_style(&self) -> Option<FontStyle> {
318 self.style
319 }
320
321 pub fn get_stretch(&self) -> Option<FontWidth> {
323 self.font_width
324 }
325
326 pub fn get_word_break(&self) -> Option<WordBreakStrength> {
328 self.word_break
329 }
330
331 pub fn get_metadata(&self) -> Option<usize> {
333 self.metadata
334 }
335
336 pub fn effective_line_height(&self) -> f32 {
341 match self.line_height {
342 LineHeightValue::Normal(n) => self.font_size * n,
343 LineHeightValue::Pt(n) => n,
344 }
345 }
346
347 pub fn apply_defaults(&self, builder: &mut parley::RangedBuilder<'_, TextBrush>) {
354 builder.push_default(StyleProperty::FontSize(self.font_size));
355 let lh = self.effective_line_height();
356 builder.push_default(StyleProperty::LineHeight(
357 parley::style::LineHeight::Absolute(lh),
358 ));
359 if let Some(color) = self.color {
360 builder.push_default(StyleProperty::Brush(TextBrush(color)));
361 }
362 if let Some(family) = self.family {
363 let stack = FamilyOwned::to_font_stack(family);
364 builder.push_default(StyleProperty::FontStack(stack));
365 }
366 if let Some(weight) = self.weight {
367 builder.push_default(StyleProperty::FontWeight(weight));
368 }
369 if let Some(style) = self.style {
370 builder.push_default(StyleProperty::FontStyle(style));
371 }
372 if let Some(width) = self.font_width {
373 builder.push_default(StyleProperty::FontWidth(width));
374 }
375 if let Some(word_break) = self.word_break {
376 builder.push_default(StyleProperty::WordBreak(word_break));
377 }
378 }
379
380 pub fn apply_range(
387 &self,
388 builder: &mut parley::RangedBuilder<'_, TextBrush>,
389 range: Range<usize>,
390 defaults: &Attrs<'_>,
391 ) {
392 if self.font_size != defaults.font_size {
393 builder.push(StyleProperty::FontSize(self.font_size), range.clone());
394 }
395 if self.effective_line_height() != defaults.effective_line_height() {
396 let lh = self.effective_line_height();
397 builder.push(
398 StyleProperty::LineHeight(parley::style::LineHeight::Absolute(lh)),
399 range.clone(),
400 );
401 }
402 if let Some(color) = self.color {
403 builder.push(StyleProperty::Brush(TextBrush(color)), range.clone());
404 }
405 if let Some(family) = self.family {
406 let stack = FamilyOwned::to_font_stack(family);
407 builder.push(StyleProperty::FontStack(stack), range.clone());
408 }
409 if let Some(weight) = self.weight {
410 builder.push(StyleProperty::FontWeight(weight), range.clone());
411 }
412 if let Some(style) = self.style {
413 builder.push(StyleProperty::FontStyle(style), range.clone());
414 }
415 if let Some(width) = self.font_width {
416 builder.push(StyleProperty::FontWidth(width), range.clone());
417 }
418 if let Some(word_break) = self.word_break {
419 builder.push(StyleProperty::WordBreak(word_break), range);
420 }
421 }
422}
423
424#[derive(Clone, Debug)]
442pub struct AttrsOwned {
443 pub font_size: f32,
445 line_height: LineHeightValue,
447 color: Option<Color>,
449 family: Option<Vec<FamilyOwned>>,
451 weight: Option<FontWeight>,
453 style: Option<FontStyle>,
455 font_width: Option<FontWidth>,
457 word_break: Option<WordBreakStrength>,
459 metadata: Option<usize>,
461}
462
463impl AttrsOwned {
464 pub fn new(attrs: Attrs) -> Self {
466 Self {
467 font_size: attrs.font_size,
468 line_height: attrs.line_height,
469 color: attrs.color,
470 family: attrs.family.map(|f| f.to_vec()),
471 weight: attrs.weight,
472 style: attrs.style,
473 font_width: attrs.font_width,
474 word_break: attrs.word_break,
475 metadata: attrs.metadata,
476 }
477 }
478
479 pub fn as_attrs(&self) -> Attrs<'_> {
481 Attrs {
482 font_size: self.font_size,
483 line_height: self.line_height,
484 color: self.color,
485 family: self.family.as_deref(),
486 weight: self.weight,
487 style: self.style,
488 font_width: self.font_width,
489 word_break: self.word_break,
490 metadata: self.metadata,
491 }
492 }
493}
494
495#[derive(Clone, Debug)]
520pub struct AttrsList {
521 defaults: AttrsOwned,
522 spans: Vec<(Range<usize>, AttrsOwned)>,
523}
524
525impl PartialEq for AttrsList {
526 fn eq(&self, _other: &Self) -> bool {
527 false
529 }
530}
531
532impl AttrsList {
533 pub fn new(defaults: Attrs) -> Self {
535 Self {
536 defaults: AttrsOwned::new(defaults),
537 spans: Vec::new(),
538 }
539 }
540
541 pub fn defaults(&self) -> Attrs<'_> {
543 self.defaults.as_attrs()
544 }
545
546 pub fn clear_spans(&mut self) {
548 self.spans.clear();
549 }
550
551 pub fn add_span(&mut self, range: Range<usize>, attrs: Attrs) {
556 self.spans
558 .retain(|(r, _)| r.end <= range.start || r.start >= range.end);
559 self.spans.push((range, AttrsOwned::new(attrs)));
560 }
561
562 pub fn get_span(&self, index: usize) -> Attrs<'_> {
567 for (range, attrs) in &self.spans {
568 if range.contains(&index) {
569 return attrs.as_attrs();
570 }
571 }
572 self.defaults.as_attrs()
573 }
574
575 pub fn split_off(&mut self, index: usize) -> Self {
582 let mut new_spans = Vec::new();
583 let mut remaining = Vec::new();
584
585 for (range, attrs) in self.spans.drain(..) {
586 if range.start >= index {
587 new_spans.push((range.start - index..range.end - index, attrs));
588 } else if range.end > index {
589 remaining.push((range.start..index, attrs.clone()));
590 new_spans.push((0..range.end - index, attrs));
591 } else {
592 remaining.push((range, attrs));
593 }
594 }
595
596 self.spans = remaining;
597
598 AttrsList {
599 defaults: self.defaults.clone(),
600 spans: new_spans,
601 }
602 }
603
604 pub fn apply_to_builder(&self, builder: &mut parley::RangedBuilder<'_, TextBrush>) {
612 let defaults = self.defaults.as_attrs();
613 defaults.apply_defaults(builder);
614 for (range, attrs) in &self.spans {
615 attrs
616 .as_attrs()
617 .apply_range(builder, range.clone(), &defaults);
618 }
619 }
620
621 pub fn spans(&self) -> &[(Range<usize>, AttrsOwned)] {
623 &self.spans
624 }
625}
626
627#[derive(Clone)]
641struct ParseList<'a> {
642 source: &'a [u8],
644 len: usize,
646 pos: usize,
648}
649
650impl Iterator for ParseList<'_> {
651 type Item = FamilyOwned;
652
653 fn next(&mut self) -> Option<Self::Item> {
656 let mut quote = None;
657 let mut pos = self.pos;
658 while pos < self.len && {
659 let ch = self.source[pos];
660 ch.is_ascii_whitespace() || ch == b','
661 } {
662 pos += 1;
663 }
664 self.pos = pos;
665 if pos >= self.len {
666 return None;
667 }
668 let first = self.source[pos];
669 let mut start = pos;
670 match first {
671 b'"' | b'\'' => {
672 quote = Some(first);
673 pos += 1;
674 start += 1;
675 }
676 _ => {}
677 }
678 if let Some(quote) = quote {
679 while pos < self.len {
680 if self.source[pos] == quote {
681 self.pos = pos + 1;
682 return Some(FamilyOwned::Name(
683 core::str::from_utf8(self.source.get(start..pos)?)
684 .ok()?
685 .trim()
686 .to_string(),
687 ));
688 }
689 pos += 1;
690 }
691 self.pos = pos;
692 return Some(FamilyOwned::Name(
693 core::str::from_utf8(self.source.get(start..pos)?)
694 .ok()?
695 .trim()
696 .to_string(),
697 ));
698 }
699 let mut end = start;
700 while pos < self.len {
701 if self.source[pos] == b',' {
702 pos += 1;
703 break;
704 }
705 pos += 1;
706 end += 1;
707 }
708 self.pos = pos;
709 let name = core::str::from_utf8(self.source.get(start..end)?)
710 .ok()?
711 .trim();
712 Some(match name.to_lowercase().as_str() {
713 "serif" => FamilyOwned::Serif,
714 "sans-serif" => FamilyOwned::SansSerif,
715 "monospace" => FamilyOwned::Monospace,
716 "cursive" => FamilyOwned::Cursive,
717 "fantasy" => FamilyOwned::Fantasy,
718 _ => FamilyOwned::Name(name.to_string()),
719 })
720 }
721}
722
723#[cfg(test)]
724mod tests {
725 use super::*;
726
727 #[test]
730 fn parse_list_named_and_generic() {
731 let families: Vec<_> = FamilyOwned::parse_list("Arial, sans-serif").collect();
732 assert_eq!(
733 families,
734 vec![
735 FamilyOwned::Name("Arial".to_string()),
736 FamilyOwned::SansSerif,
737 ]
738 );
739 }
740
741 #[test]
742 fn parse_list_quoted_names() {
743 let families: Vec<_> =
744 FamilyOwned::parse_list("'Fira Code', \"Noto Sans\", monospace").collect();
745 assert_eq!(
746 families,
747 vec![
748 FamilyOwned::Name("Fira Code".to_string()),
749 FamilyOwned::Name("Noto Sans".to_string()),
750 FamilyOwned::Monospace,
751 ]
752 );
753 }
754
755 #[test]
756 fn parse_list_all_generics() {
757 let families: Vec<_> =
758 FamilyOwned::parse_list("serif, sans-serif, monospace, cursive, fantasy").collect();
759 assert_eq!(
760 families,
761 vec![
762 FamilyOwned::Serif,
763 FamilyOwned::SansSerif,
764 FamilyOwned::Monospace,
765 FamilyOwned::Cursive,
766 FamilyOwned::Fantasy,
767 ]
768 );
769 }
770
771 #[test]
772 fn parse_list_case_insensitive() {
773 let families: Vec<_> = FamilyOwned::parse_list("SERIF, Sans-Serif").collect();
774 assert_eq!(families, vec![FamilyOwned::Serif, FamilyOwned::SansSerif]);
775 }
776
777 #[test]
778 fn parse_list_empty() {
779 let families: Vec<_> = FamilyOwned::parse_list("").collect();
780 assert!(families.is_empty());
781 }
782
783 #[test]
784 fn parse_list_whitespace_only() {
785 let families: Vec<_> = FamilyOwned::parse_list(" , , ").collect();
786 assert!(families.is_empty());
787 }
788
789 #[test]
790 fn to_font_stack_single_named() {
791 let families = vec![FamilyOwned::Name("Inter".to_string())];
792 let stack = FamilyOwned::to_font_stack(&families);
793 assert!(matches!(stack, FontStack::Source(_)));
794 }
795
796 #[test]
797 fn to_font_stack_single_generic() {
798 let families = vec![FamilyOwned::Monospace];
799 let stack = FamilyOwned::to_font_stack(&families);
800 assert!(matches!(stack, FontStack::Single(_)));
801 }
802
803 #[test]
804 fn to_font_stack_multi_family_uses_list() {
805 let families = vec![
806 FamilyOwned::Name("Inter".to_string()),
807 FamilyOwned::Monospace,
808 FamilyOwned::SansSerif,
809 ];
810 let stack = FamilyOwned::to_font_stack(&families);
811 match stack {
812 FontStack::List(list) => {
813 assert_eq!(list.len(), 3, "all families should be preserved");
814 assert!(matches!(list[0], FontFamily::Named(_)));
815 assert!(matches!(
816 list[1],
817 FontFamily::Generic(GenericFamily::Monospace)
818 ));
819 assert!(matches!(
820 list[2],
821 FontFamily::Generic(GenericFamily::SansSerif)
822 ));
823 }
824 other => panic!("expected FontStack::List, got {other:?}"),
825 }
826 }
827
828 #[test]
829 fn to_font_stack_two_named_families() {
830 let families = vec![
831 FamilyOwned::Name("Fira Code".to_string()),
832 FamilyOwned::Name("Cascadia Code".to_string()),
833 ];
834 let stack = FamilyOwned::to_font_stack(&families);
835 assert!(
836 matches!(stack, FontStack::List(_)),
837 "two families should produce List"
838 );
839 }
840
841 #[test]
842 fn to_font_stack_empty_defaults_to_sans_serif() {
843 let families: Vec<FamilyOwned> = vec![];
844 let stack = FamilyOwned::to_font_stack(&families);
845 assert!(matches!(
846 stack,
847 FontStack::Single(FontFamily::Generic(GenericFamily::SansSerif))
848 ));
849 }
850
851 #[test]
854 fn attrs_defaults() {
855 let a = Attrs::new();
856 assert_eq!(a.font_size, 16.0);
857 assert_eq!(a.get_line_height(), LineHeightValue::Normal(1.0));
858 assert_eq!(a.get_color(), None);
859 assert_eq!(a.get_family(), None);
860 assert_eq!(a.get_weight(), None);
861 assert_eq!(a.get_font_style(), None);
862 assert_eq!(a.get_stretch(), None);
863 assert_eq!(a.get_metadata(), None);
864 }
865
866 #[test]
867 fn attrs_builder_chain() {
868 let families = [FamilyOwned::Monospace];
869 let a = Attrs::new()
870 .font_size(20.0)
871 .color(Color::WHITE)
872 .family(&families)
873 .weight(FontWeight::BOLD)
874 .font_style(FontStyle::Italic)
875 .font_width(FontWidth::CONDENSED)
876 .line_height(LineHeightValue::Pt(24.0))
877 .metadata(42);
878
879 assert_eq!(a.font_size, 20.0);
880 assert_eq!(a.get_color(), Some(Color::WHITE));
881 assert_eq!(a.get_family(), Some(families.as_slice()));
882 assert_eq!(a.get_weight(), Some(FontWeight::BOLD));
883 assert_eq!(a.get_font_style(), Some(FontStyle::Italic));
884 assert_eq!(a.get_stretch(), Some(FontWidth::CONDENSED));
885 assert_eq!(a.get_line_height(), LineHeightValue::Pt(24.0));
886 assert_eq!(a.get_metadata(), Some(42));
887 }
888
889 #[test]
890 fn attrs_raw_weight() {
891 let a = Attrs::new().raw_weight(700);
892 assert_eq!(a.get_weight(), Some(FontWeight::new(700.0)));
893 }
894
895 #[test]
896 fn effective_line_height_normal_multiplier() {
897 let a = Attrs::new()
898 .font_size(20.0)
899 .line_height(LineHeightValue::Normal(1.5));
900 assert!((a.effective_line_height() - 30.0).abs() < f32::EPSILON);
901 }
902
903 #[test]
904 fn effective_line_height_px_absolute() {
905 let a = Attrs::new()
906 .font_size(20.0)
907 .line_height(LineHeightValue::Pt(24.0));
908 assert!((a.effective_line_height() - 24.0).abs() < f32::EPSILON);
909 }
910
911 #[test]
912 fn effective_line_height_default() {
913 let a = Attrs::new();
915 assert!((a.effective_line_height() - 16.0).abs() < f32::EPSILON);
916 }
917
918 #[test]
921 fn attrs_owned_roundtrip() {
922 let families = [
923 FamilyOwned::Name("Inter".to_string()),
924 FamilyOwned::SansSerif,
925 ];
926 let a = Attrs::new()
927 .font_size(18.0)
928 .family(&families)
929 .weight(FontWeight::BOLD)
930 .color(Color::WHITE)
931 .metadata(7);
932
933 let owned = AttrsOwned::new(a);
934 let back = owned.as_attrs();
935
936 assert_eq!(back.font_size, 18.0);
937 assert_eq!(back.get_weight(), Some(FontWeight::BOLD));
938 assert_eq!(back.get_color(), Some(Color::WHITE));
939 assert_eq!(back.get_metadata(), Some(7));
940 let fam = back.get_family().unwrap();
942 assert_eq!(fam.len(), 2);
943 assert_eq!(fam[0], FamilyOwned::Name("Inter".to_string()));
944 assert_eq!(fam[1], FamilyOwned::SansSerif);
945 }
946
947 #[test]
948 fn attrs_owned_no_family() {
949 let a = Attrs::new();
950 let owned = AttrsOwned::new(a);
951 assert_eq!(owned.as_attrs().get_family(), None);
952 }
953
954 #[test]
957 fn attrs_list_new_has_no_spans() {
958 let list = AttrsList::new(Attrs::new().font_size(14.0));
959 assert_eq!(list.defaults().font_size, 14.0);
960 assert!(list.spans().is_empty());
961 }
962
963 #[test]
964 fn attrs_list_add_span_and_get() {
965 let mut list = AttrsList::new(Attrs::new().font_size(14.0));
966 list.add_span(5..10, Attrs::new().font_size(20.0).weight(FontWeight::BOLD));
967
968 let at7 = list.get_span(7);
970 assert_eq!(at7.font_size, 20.0);
971 assert_eq!(at7.get_weight(), Some(FontWeight::BOLD));
972
973 let at0 = list.get_span(0);
975 assert_eq!(at0.font_size, 14.0);
976 assert_eq!(at0.get_weight(), None);
977
978 let at10 = list.get_span(10);
980 assert_eq!(at10.font_size, 14.0);
981 }
982
983 #[test]
984 fn attrs_list_overlapping_span_replaces() {
985 let mut list = AttrsList::new(Attrs::new());
986 list.add_span(0..10, Attrs::new().font_size(20.0));
987 list.add_span(5..15, Attrs::new().font_size(30.0));
988
989 assert_eq!(list.spans().len(), 1);
991 assert_eq!(list.spans()[0].0, 5..15);
992 }
993
994 #[test]
995 fn attrs_list_clear_spans() {
996 let mut list = AttrsList::new(Attrs::new());
997 list.add_span(0..5, Attrs::new().font_size(20.0));
998 list.add_span(5..10, Attrs::new().font_size(30.0));
999 assert_eq!(list.spans().len(), 2);
1000
1001 list.clear_spans();
1002 assert!(list.spans().is_empty());
1003 assert_eq!(list.defaults().font_size, 16.0);
1005 }
1006
1007 #[test]
1008 fn attrs_list_split_off_basic() {
1009 let mut list = AttrsList::new(Attrs::new().font_size(14.0));
1010 list.add_span(2..4, Attrs::new().font_size(20.0));
1011 list.add_span(6..8, Attrs::new().font_size(30.0));
1012
1013 let right = list.split_off(5);
1014
1015 assert_eq!(list.spans().len(), 1);
1017 assert_eq!(list.spans()[0].0, 2..4);
1018
1019 assert_eq!(right.spans().len(), 1);
1021 assert_eq!(right.spans()[0].0, 1..3);
1022 }
1023
1024 #[test]
1025 fn attrs_list_split_off_crossing_span() {
1026 let mut list = AttrsList::new(Attrs::new());
1027 list.add_span(3..7, Attrs::new().font_size(20.0));
1028
1029 let right = list.split_off(5);
1030
1031 assert_eq!(list.spans().len(), 1);
1033 assert_eq!(list.spans()[0].0, 3..5);
1034
1035 assert_eq!(right.spans().len(), 1);
1037 assert_eq!(right.spans()[0].0, 0..2);
1038 assert_eq!(right.spans()[0].1.font_size, 20.0);
1039 }
1040
1041 #[test]
1042 fn attrs_list_split_off_empty() {
1043 let mut list = AttrsList::new(Attrs::new().font_size(14.0));
1044 let right = list.split_off(0);
1045 assert!(list.spans().is_empty());
1046 assert!(right.spans().is_empty());
1047 assert_eq!(right.defaults().font_size, 14.0);
1048 }
1049}