1use alloc::{
8 boxed::Box,
9 string::{String, ToString},
10 vec::Vec,
11};
12use core::{
13 cmp::Ordering,
14 ffi::c_void,
15 fmt,
16 hash::{Hash, Hasher},
17 num::ParseIntError,
18 sync::atomic::{AtomicU64, AtomicUsize, Ordering as AtomicOrdering},
19};
20
21#[cfg(feature = "parser")]
22use crate::props::basic::parse::{strip_quotes, UnclosedQuotesError};
23use crate::system::SystemFontType;
24use crate::{
25 codegen::format::{FormatAsRustCode, GetHash},
26 corety::{AzString, U8Vec},
27 props::{
28 basic::{
29 error::{InvalidValueErr, InvalidValueErrOwned},
30 pixel::{
31 parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned,
32 PixelValue,
33 },
34 },
35 formatter::PrintAsCssValue,
36 },
37};
38
39#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
43#[repr(C)]
44#[derive(Default)]
45pub enum StyleFontWeight {
46 Lighter,
47 W100,
48 W200,
49 W300,
50 #[default]
51 Normal,
52 W500,
53 W600,
54 Bold,
55 W800,
56 W900,
57 Bolder,
58}
59
60impl PrintAsCssValue for StyleFontWeight {
61 fn print_as_css_value(&self) -> String {
62 match self {
63 Self::Lighter => "lighter".to_string(),
64 Self::W100 => "100".to_string(),
65 Self::W200 => "200".to_string(),
66 Self::W300 => "300".to_string(),
67 Self::Normal => "normal".to_string(),
68 Self::W500 => "500".to_string(),
69 Self::W600 => "600".to_string(),
70 Self::Bold => "bold".to_string(),
71 Self::W800 => "800".to_string(),
72 Self::W900 => "900".to_string(),
73 Self::Bolder => "bolder".to_string(),
74 }
75 }
76}
77
78impl FormatAsRustCode for StyleFontWeight {
79 fn format_as_rust_code(&self, _tabs: usize) -> String {
80 use StyleFontWeight::{
81 Bold, Bolder, Lighter, Normal, W100, W200, W300, W500, W600, W800, W900,
82 };
83 format!(
84 "StyleFontWeight::{}",
85 match self {
86 Lighter => "Lighter",
87 W100 => "W100",
88 W200 => "W200",
89 W300 => "W300",
90 Normal => "Normal",
91 W500 => "W500",
92 W600 => "W600",
93 Bold => "Bold",
94 W800 => "W800",
95 W900 => "W900",
96 Bolder => "Bolder",
97 }
98 )
99 }
100}
101
102#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
106#[repr(C)]
107#[derive(Default)]
108pub enum StyleFontStyle {
109 #[default]
110 Normal,
111 Italic,
112 Oblique,
113}
114
115impl PrintAsCssValue for StyleFontStyle {
116 fn print_as_css_value(&self) -> String {
117 match self {
118 Self::Normal => "normal".to_string(),
119 Self::Italic => "italic".to_string(),
120 Self::Oblique => "oblique".to_string(),
121 }
122 }
123}
124
125impl FormatAsRustCode for StyleFontStyle {
126 fn format_as_rust_code(&self, _tabs: usize) -> String {
127 use StyleFontStyle::{Italic, Normal, Oblique};
128 format!(
129 "StyleFontStyle::{}",
130 match self {
131 Normal => "Normal",
132 Italic => "Italic",
133 Oblique => "Oblique",
134 }
135 )
136 }
137}
138
139#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
143#[repr(C)]
144pub struct StyleFontSize {
145 pub inner: PixelValue,
146}
147
148impl Default for StyleFontSize {
149 fn default() -> Self {
150 Self {
151 inner: PixelValue::const_pt(12),
153 }
154 }
155}
156
157impl_pixel_value!(StyleFontSize);
158impl PrintAsCssValue for StyleFontSize {
159 fn print_as_css_value(&self) -> String {
160 format!("{}", self.inner)
161 }
162}
163
164pub type FontRefDestructorCallbackType = extern "C" fn(*mut c_void);
168
169#[repr(C)]
176pub struct FontRef {
177 pub parsed: *const c_void,
179 pub copies: *const AtomicUsize,
181 pub id: u64,
188 pub run_destructor: bool,
190 pub parsed_destructor: FontRefDestructorCallbackType,
192}
193
194static FONT_REF_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
197
198#[must_use]
199fn next_font_ref_id() -> u64 {
200 FONT_REF_ID_COUNTER.fetch_add(1, AtomicOrdering::SeqCst)
201}
202
203impl fmt::Debug for FontRef {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 write!(f, "FontRef(0x{:x}", self.parsed as usize)?;
206 if let Some(c) = unsafe { self.copies.as_ref() } {
207 write!(f, ", copies: {})", c.load(AtomicOrdering::SeqCst))?;
208 } else {
209 write!(f, ")")?;
210 }
211 Ok(())
212 }
213}
214
215impl FontRef {
216 pub fn new(parsed: *const c_void, destructor: FontRefDestructorCallbackType) -> Self {
222 Self {
223 parsed,
224 copies: Box::into_raw(Box::new(AtomicUsize::new(1))),
225 id: next_font_ref_id(),
226 run_destructor: true,
227 parsed_destructor: destructor,
228 }
229 }
230
231 #[inline]
233 #[must_use]
234 pub const fn get_parsed(&self) -> *const c_void {
235 self.parsed
236 }
237}
238impl_option!(
239 FontRef,
240 OptionFontRef,
241 copy = false,
242 [Debug, Clone, PartialEq, Eq, Hash]
243);
244unsafe impl Send for FontRef {}
245unsafe impl Sync for FontRef {}
246impl PartialEq for FontRef {
249 fn eq(&self, rhs: &Self) -> bool {
250 self.id == rhs.id
251 }
252}
253impl PartialOrd for FontRef {
254 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
255 Some(self.id.cmp(&other.id))
256 }
257}
258impl Ord for FontRef {
259 fn cmp(&self, other: &Self) -> Ordering {
260 self.id.cmp(&other.id)
261 }
262}
263impl Eq for FontRef {}
264impl Hash for FontRef {
265 fn hash<H: Hasher>(&self, state: &mut H) {
266 self.id.hash(state);
267 }
268}
269impl Clone for FontRef {
270 fn clone(&self) -> Self {
271 if !self.copies.is_null() {
272 unsafe {
273 (*self.copies).fetch_add(1, AtomicOrdering::SeqCst);
274 }
275 }
276 Self {
277 parsed: self.parsed,
278 copies: self.copies,
279 id: self.id, run_destructor: self.run_destructor,
281 parsed_destructor: self.parsed_destructor,
282 }
283 }
284}
285impl Drop for FontRef {
286 fn drop(&mut self) {
287 if self.run_destructor
288 && !self.copies.is_null()
289 && unsafe { (*self.copies).fetch_sub(1, AtomicOrdering::SeqCst) } == 1
290 {
291 unsafe {
292 (self.parsed_destructor)(self.parsed.cast_mut());
293 drop(Box::from_raw(self.copies.cast_mut()));
294 }
295 }
296 }
297}
298
299#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
309#[repr(C, u8)]
310pub enum StyleFontFamily {
311 System(AzString),
313 SystemType(SystemFontType),
316 File(AzString),
318 Ref(FontRef),
320}
321
322impl_option!(
323 StyleFontFamily,
324 OptionStyleFontFamily,
325 copy = false,
326 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
327);
328
329impl StyleFontFamily {
330 pub fn as_string(&self) -> String {
331 match &self {
332 Self::System(s) => {
333 let owned = s.clone().into_library_owned_string();
334 if owned.contains(char::is_whitespace) {
335 format!("\"{owned}\"")
336 } else {
337 owned
338 }
339 }
340 Self::SystemType(st) => st.as_css_str().to_string(),
341 Self::File(s) => format!("url({})", s.clone().into_library_owned_string()),
342 Self::Ref(s) => format!("font-ref(0x{:x})", s.parsed as usize),
343 }
344 }
345
346 #[must_use]
351 pub fn as_query_string(&self) -> String {
352 match &self {
353 Self::System(s) | Self::File(s) => s.clone().into_library_owned_string(),
354 Self::SystemType(st) => st.as_css_str().to_string(),
355 Self::Ref(s) => format!("font-ref(0x{:x})", s.parsed as usize),
356 }
357 }
358}
359
360impl_vec!(
361 StyleFontFamily,
362 StyleFontFamilyVec,
363 StyleFontFamilyVecDestructor,
364 StyleFontFamilyVecDestructorType,
365 StyleFontFamilyVecSlice,
366 OptionStyleFontFamily
367);
368impl_vec_clone!(
369 StyleFontFamily,
370 StyleFontFamilyVec,
371 StyleFontFamilyVecDestructor
372);
373impl_vec_debug!(StyleFontFamily, StyleFontFamilyVec);
374impl_vec_eq!(StyleFontFamily, StyleFontFamilyVec);
375impl_vec_ord!(StyleFontFamily, StyleFontFamilyVec);
376impl_vec_hash!(StyleFontFamily, StyleFontFamilyVec);
377impl_vec_partialeq!(StyleFontFamily, StyleFontFamilyVec);
378impl_vec_partialord!(StyleFontFamily, StyleFontFamilyVec);
379
380impl PrintAsCssValue for StyleFontFamilyVec {
381 fn print_as_css_value(&self) -> String {
382 self.iter()
383 .map(StyleFontFamily::as_string)
384 .collect::<Vec<_>>()
385 .join(", ")
386 }
387}
388
389impl FormatAsRustCode for StyleFontFamilyVec {
391 fn format_as_rust_code(&self, _tabs: usize) -> String {
392 format!(
393 "StyleFontFamilyVec::from_const_slice(STYLE_FONT_FAMILY_{}_ITEMS)",
394 self.get_hash()
395 )
396 }
397}
398
399#[derive(Clone, PartialEq, Eq)]
404pub enum CssFontWeightParseError<'a> {
405 InvalidValue(InvalidValueErr<'a>),
406 InvalidNumber(ParseIntError),
407}
408
409impl FormatAsRustCode for StyleFontFamily {
411 fn format_as_rust_code(&self, _tabs: usize) -> String {
412 match self {
413 Self::System(id) => {
414 format!("StyleFontFamily::System(STRING_{})", id.get_hash())
415 }
416 Self::SystemType(st) => {
417 format!("StyleFontFamily::SystemType(SystemFontType::{st:?})")
418 }
419 Self::File(path) => {
420 format!("StyleFontFamily::File(STRING_{})", path.get_hash())
421 }
422 Self::Ref(font_ref) => {
423 format!("StyleFontFamily::Ref({:0x})", font_ref.parsed as usize)
424 }
425 }
426 }
427}
428impl_debug_as_display!(CssFontWeightParseError<'a>);
429impl_display! { CssFontWeightParseError<'a>, {
430 InvalidValue(e) => format!("Invalid font-weight keyword: \"{}\"", e.0),
431 InvalidNumber(e) => format!("Invalid font-weight number: {}", e),
432}}
433impl<'a> From<InvalidValueErr<'a>> for CssFontWeightParseError<'a> {
434 fn from(e: InvalidValueErr<'a>) -> Self {
435 CssFontWeightParseError::InvalidValue(e)
436 }
437}
438impl From<ParseIntError> for CssFontWeightParseError<'_> {
439 fn from(e: ParseIntError) -> Self {
440 CssFontWeightParseError::InvalidNumber(e)
441 }
442}
443#[allow(variant_size_differences)]
444#[derive(Debug, Clone, PartialEq, Eq)]
446#[repr(C, u8)]
447pub enum CssFontWeightParseErrorOwned {
448 InvalidValue(InvalidValueErrOwned),
449 InvalidNumber(crate::props::basic::error::ParseIntError),
450}
451
452impl CssFontWeightParseError<'_> {
453 #[must_use]
454 pub fn to_contained(&self) -> CssFontWeightParseErrorOwned {
455 match self {
456 Self::InvalidValue(e) => CssFontWeightParseErrorOwned::InvalidValue(e.to_contained()),
457 Self::InvalidNumber(e) => CssFontWeightParseErrorOwned::InvalidNumber(e.clone().into()),
458 }
459 }
460}
461
462impl CssFontWeightParseErrorOwned {
463 #[must_use]
464 pub fn to_shared(&self) -> CssFontWeightParseError<'_> {
465 match self {
466 Self::InvalidValue(e) => CssFontWeightParseError::InvalidValue(e.to_shared()),
467 Self::InvalidNumber(e) => CssFontWeightParseError::InvalidNumber(e.to_std()),
468 }
469 }
470}
471
472#[cfg(feature = "parser")]
473pub fn parse_font_weight(input: &str) -> Result<StyleFontWeight, CssFontWeightParseError<'_>> {
477 let input = input.trim();
478 match input {
479 "lighter" => Ok(StyleFontWeight::Lighter),
480 "normal" | "400" => Ok(StyleFontWeight::Normal),
481 "bold" | "700" => Ok(StyleFontWeight::Bold),
482 "bolder" => Ok(StyleFontWeight::Bolder),
483 "100" => Ok(StyleFontWeight::W100),
484 "200" => Ok(StyleFontWeight::W200),
485 "300" => Ok(StyleFontWeight::W300),
486 "500" => Ok(StyleFontWeight::W500),
487 "600" => Ok(StyleFontWeight::W600),
488 "800" => Ok(StyleFontWeight::W800),
489 "900" => Ok(StyleFontWeight::W900),
490 _ => Err(InvalidValueErr(input).into()),
491 }
492}
493
494#[derive(Clone, PartialEq, Eq)]
497pub enum CssFontStyleParseError<'a> {
498 InvalidValue(InvalidValueErr<'a>),
499}
500impl_debug_as_display!(CssFontStyleParseError<'a>);
501impl_display! { CssFontStyleParseError<'a>, {
502 InvalidValue(e) => format!("Invalid font-style: \"{}\"", e.0),
503}}
504impl_from! { InvalidValueErr<'a>, CssFontStyleParseError::InvalidValue }
505
506#[derive(Debug, Clone, PartialEq, Eq)]
507#[repr(C, u8)]
508pub enum CssFontStyleParseErrorOwned {
509 InvalidValue(InvalidValueErrOwned),
510}
511impl CssFontStyleParseError<'_> {
512 #[must_use]
513 pub fn to_contained(&self) -> CssFontStyleParseErrorOwned {
514 match self {
515 Self::InvalidValue(e) => CssFontStyleParseErrorOwned::InvalidValue(e.to_contained()),
516 }
517 }
518}
519impl CssFontStyleParseErrorOwned {
520 #[must_use]
521 pub fn to_shared(&self) -> CssFontStyleParseError<'_> {
522 match self {
523 Self::InvalidValue(e) => CssFontStyleParseError::InvalidValue(e.to_shared()),
524 }
525 }
526}
527
528#[cfg(feature = "parser")]
529pub fn parse_font_style(input: &str) -> Result<StyleFontStyle, CssFontStyleParseError<'_>> {
533 match input.trim() {
534 "normal" => Ok(StyleFontStyle::Normal),
535 "italic" => Ok(StyleFontStyle::Italic),
536 "oblique" => Ok(StyleFontStyle::Oblique),
537 other => Err(InvalidValueErr(other).into()),
538 }
539}
540
541#[derive(Clone, PartialEq, Eq)]
544pub enum CssStyleFontSizeParseError<'a> {
545 PixelValue(CssPixelValueParseError<'a>),
546}
547impl_debug_as_display!(CssStyleFontSizeParseError<'a>);
548impl_display! { CssStyleFontSizeParseError<'a>, {
549 PixelValue(e) => format!("Invalid font-size: {}", e),
550}}
551impl_from! { CssPixelValueParseError<'a>, CssStyleFontSizeParseError::PixelValue }
552
553#[derive(Debug, Clone, PartialEq, Eq)]
554#[repr(C, u8)]
555pub enum CssStyleFontSizeParseErrorOwned {
556 PixelValue(CssPixelValueParseErrorOwned),
557}
558impl CssStyleFontSizeParseError<'_> {
559 #[must_use]
560 pub fn to_contained(&self) -> CssStyleFontSizeParseErrorOwned {
561 match self {
562 Self::PixelValue(e) => CssStyleFontSizeParseErrorOwned::PixelValue(e.to_contained()),
563 }
564 }
565}
566impl CssStyleFontSizeParseErrorOwned {
567 #[must_use]
568 pub fn to_shared(&self) -> CssStyleFontSizeParseError<'_> {
569 match self {
570 Self::PixelValue(e) => CssStyleFontSizeParseError::PixelValue(e.to_shared()),
571 }
572 }
573}
574
575#[cfg(feature = "parser")]
576pub fn parse_style_font_size(input: &str) -> Result<StyleFontSize, CssStyleFontSizeParseError<'_>> {
580 Ok(StyleFontSize {
581 inner: parse_pixel_value(input)?,
582 })
583}
584
585#[derive(PartialEq, Eq, Clone)]
588pub enum CssStyleFontFamilyParseError<'a> {
589 InvalidStyleFontFamily(&'a str),
590 UnclosedQuotes(UnclosedQuotesError<'a>),
591}
592impl_debug_as_display!(CssStyleFontFamilyParseError<'a>);
593impl_display! { CssStyleFontFamilyParseError<'a>, {
594 InvalidStyleFontFamily(val) => format!("Invalid font-family: \"{}\"", val),
595 UnclosedQuotes(val) => format!("Unclosed quotes in font-family: \"{}\"", val.0),
596}}
597impl<'a> From<UnclosedQuotesError<'a>> for CssStyleFontFamilyParseError<'a> {
598 fn from(err: UnclosedQuotesError<'a>) -> Self {
599 CssStyleFontFamilyParseError::UnclosedQuotes(err)
600 }
601}
602
603#[derive(Debug, Clone, PartialEq, Eq)]
604#[repr(C, u8)]
605pub enum CssStyleFontFamilyParseErrorOwned {
606 InvalidStyleFontFamily(AzString),
607 UnclosedQuotes(AzString),
608}
609impl CssStyleFontFamilyParseError<'_> {
610 #[must_use]
611 pub fn to_contained(&self) -> CssStyleFontFamilyParseErrorOwned {
612 match self {
613 CssStyleFontFamilyParseError::InvalidStyleFontFamily(s) => {
614 CssStyleFontFamilyParseErrorOwned::InvalidStyleFontFamily((*s).to_string().into())
615 }
616 CssStyleFontFamilyParseError::UnclosedQuotes(e) => {
617 CssStyleFontFamilyParseErrorOwned::UnclosedQuotes(e.0.to_string().into())
618 }
619 }
620 }
621}
622impl CssStyleFontFamilyParseErrorOwned {
623 #[must_use]
624 pub fn to_shared(&self) -> CssStyleFontFamilyParseError<'_> {
625 match self {
626 Self::InvalidStyleFontFamily(s) => {
627 CssStyleFontFamilyParseError::InvalidStyleFontFamily(s)
628 }
629 Self::UnclosedQuotes(s) => {
630 CssStyleFontFamilyParseError::UnclosedQuotes(UnclosedQuotesError(s))
631 }
632 }
633 }
634}
635
636#[cfg(feature = "parser")]
637pub fn parse_style_font_family(
641 input: &str,
642) -> Result<StyleFontFamilyVec, CssStyleFontFamilyParseError<'_>> {
643 let multiple_fonts = input.split(',');
644 let mut fonts = Vec::with_capacity(1);
645
646 for font in multiple_fonts {
647 let font = font.trim();
648
649 if font.starts_with("system:") {
651 if let Some(system_type) = SystemFontType::from_css_str(font) {
652 fonts.push(StyleFontFamily::SystemType(system_type));
653 continue;
654 }
655 }
657
658 if let Ok(stripped) = strip_quotes(font) {
659 fonts.push(StyleFontFamily::System(stripped.0.to_string().into()));
660 } else {
661 fonts.push(StyleFontFamily::System(font.to_string().into()));
663 }
664 }
665
666 Ok(fonts.into())
667}
668
669use crate::corety::{OptionI16, OptionU16, OptionU32};
672
673#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
676#[repr(C)]
677#[derive(Default)]
678pub struct Panose {
679 pub family_type: u8,
680 pub serif_style: u8,
681 pub weight: u8,
682 pub proportion: u8,
683 pub contrast: u8,
684 pub stroke_variation: u8,
685 pub arm_style: u8,
686 pub letterform: u8,
687 pub midline: u8,
688 pub x_height: u8,
689}
690
691impl Panose {
692 #[must_use]
693 pub const fn zero() -> Self {
694 Self {
695 family_type: 0,
696 serif_style: 0,
697 weight: 0,
698 proportion: 0,
699 contrast: 0,
700 stroke_variation: 0,
701 arm_style: 0,
702 letterform: 0,
703 midline: 0,
704 x_height: 0,
705 }
706 }
707}
708
709#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
712#[repr(C)]
713pub struct FontMetrics {
714 pub ul_code_page_range1: OptionU32,
716 pub ul_code_page_range2: OptionU32,
717
718 pub ul_unicode_range1: u32,
720 pub ul_unicode_range2: u32,
721 pub ul_unicode_range3: u32,
722 pub ul_unicode_range4: u32,
723 pub ach_vend_id: u32,
724
725 pub s_typo_ascender: OptionI16,
727 pub s_typo_descender: OptionI16,
728 pub s_typo_line_gap: OptionI16,
729 pub us_win_ascent: OptionU16,
730 pub us_win_descent: OptionU16,
731
732 pub sx_height: OptionI16,
735 pub s_cap_height: OptionI16,
736 pub us_default_char: OptionU16,
737 pub us_break_char: OptionU16,
738 pub us_max_context: OptionU16,
739
740 pub us_lower_optical_point_size: OptionU16,
742 pub us_upper_optical_point_size: OptionU16,
743
744 pub units_per_em: u16,
746 pub font_flags: u16,
747 pub x_min: i16,
748 pub y_min: i16,
749 pub x_max: i16,
750 pub y_max: i16,
751
752 pub ascender: i16,
754 pub descender: i16,
755 pub line_gap: i16,
756 pub advance_width_max: u16,
757 pub min_left_side_bearing: i16,
758 pub min_right_side_bearing: i16,
759 pub x_max_extent: i16,
760 pub caret_slope_rise: i16,
761 pub caret_slope_run: i16,
762 pub caret_offset: i16,
763 pub num_h_metrics: u16,
764
765 pub x_avg_char_width: i16,
767 pub us_weight_class: u16,
768 pub us_width_class: u16,
769 pub fs_type: u16,
770 pub y_subscript_x_size: i16,
771 pub y_subscript_y_size: i16,
772 pub y_subscript_x_offset: i16,
773 pub y_subscript_y_offset: i16,
774 pub y_superscript_x_size: i16,
775 pub y_superscript_y_size: i16,
776 pub y_superscript_x_offset: i16,
777 pub y_superscript_y_offset: i16,
778 pub y_strikeout_size: i16,
779 pub y_strikeout_position: i16,
780 pub s_family_class: i16,
781 pub fs_selection: u16,
782 pub us_first_char_index: u16,
783 pub us_last_char_index: u16,
784
785 pub panose: Panose,
787}
788
789impl Default for FontMetrics {
790 fn default() -> Self {
791 Self::zero()
792 }
793}
794
795impl FontMetrics {
796 #[must_use]
799 pub const fn zero() -> Self {
800 Self {
801 ul_code_page_range1: OptionU32::None,
802 ul_code_page_range2: OptionU32::None,
803 ul_unicode_range1: 0,
804 ul_unicode_range2: 0,
805 ul_unicode_range3: 0,
806 ul_unicode_range4: 0,
807 ach_vend_id: 0,
808 s_typo_ascender: OptionI16::None,
809 s_typo_descender: OptionI16::None,
810 s_typo_line_gap: OptionI16::None,
811 us_win_ascent: OptionU16::None,
812 us_win_descent: OptionU16::None,
813 sx_height: OptionI16::None,
814 s_cap_height: OptionI16::None,
815 us_default_char: OptionU16::None,
816 us_break_char: OptionU16::None,
817 us_max_context: OptionU16::None,
818 us_lower_optical_point_size: OptionU16::None,
819 us_upper_optical_point_size: OptionU16::None,
820 units_per_em: 1000,
821 font_flags: 0,
822 x_min: 0,
823 y_min: 0,
824 x_max: 0,
825 y_max: 0,
826 ascender: 0,
827 descender: 0,
828 line_gap: 0,
829 advance_width_max: 0,
830 min_left_side_bearing: 0,
831 min_right_side_bearing: 0,
832 x_max_extent: 0,
833 caret_slope_rise: 0,
834 caret_slope_run: 0,
835 caret_offset: 0,
836 num_h_metrics: 0,
837 x_avg_char_width: 0,
838 us_weight_class: 400,
839 us_width_class: 5,
840 fs_type: 0,
841 y_subscript_x_size: 0,
842 y_subscript_y_size: 0,
843 y_subscript_x_offset: 0,
844 y_subscript_y_offset: 0,
845 y_superscript_x_size: 0,
846 y_superscript_y_size: 0,
847 y_superscript_x_offset: 0,
848 y_superscript_y_offset: 0,
849 y_strikeout_size: 0,
850 y_strikeout_position: 0,
851 s_family_class: 0,
852 fs_selection: 0,
853 us_first_char_index: 0,
854 us_last_char_index: 0,
855 panose: Panose::zero(),
856 }
857 }
858
859 #[must_use]
861 pub const fn get_ascender(&self) -> i16 {
862 self.ascender
863 }
864
865 #[must_use]
867 pub const fn get_descender(&self) -> i16 {
868 self.descender
869 }
870
871 #[must_use]
873 pub const fn get_line_gap(&self) -> i16 {
874 self.line_gap
875 }
876
877 #[must_use]
879 pub const fn get_advance_width_max(&self) -> u16 {
880 self.advance_width_max
881 }
882
883 #[must_use]
885 pub const fn get_min_left_side_bearing(&self) -> i16 {
886 self.min_left_side_bearing
887 }
888
889 #[must_use]
891 pub const fn get_min_right_side_bearing(&self) -> i16 {
892 self.min_right_side_bearing
893 }
894
895 #[must_use]
897 pub const fn get_x_min(&self) -> i16 {
898 self.x_min
899 }
900
901 #[must_use]
903 pub const fn get_y_min(&self) -> i16 {
904 self.y_min
905 }
906
907 #[must_use]
909 pub const fn get_x_max(&self) -> i16 {
910 self.x_max
911 }
912
913 #[must_use]
915 pub const fn get_y_max(&self) -> i16 {
916 self.y_max
917 }
918
919 #[must_use]
921 pub const fn get_x_max_extent(&self) -> i16 {
922 self.x_max_extent
923 }
924
925 #[must_use]
927 pub const fn get_x_avg_char_width(&self) -> i16 {
928 self.x_avg_char_width
929 }
930
931 #[must_use]
933 pub const fn get_y_subscript_x_size(&self) -> i16 {
934 self.y_subscript_x_size
935 }
936
937 #[must_use]
939 pub const fn get_y_subscript_y_size(&self) -> i16 {
940 self.y_subscript_y_size
941 }
942
943 #[must_use]
945 pub const fn get_y_subscript_x_offset(&self) -> i16 {
946 self.y_subscript_x_offset
947 }
948
949 #[must_use]
951 pub const fn get_y_subscript_y_offset(&self) -> i16 {
952 self.y_subscript_y_offset
953 }
954
955 #[must_use]
957 pub const fn get_y_superscript_x_size(&self) -> i16 {
958 self.y_superscript_x_size
959 }
960
961 #[must_use]
963 pub const fn get_y_superscript_y_size(&self) -> i16 {
964 self.y_superscript_y_size
965 }
966
967 #[must_use]
969 pub const fn get_y_superscript_x_offset(&self) -> i16 {
970 self.y_superscript_x_offset
971 }
972
973 #[must_use]
975 pub const fn get_y_superscript_y_offset(&self) -> i16 {
976 self.y_superscript_y_offset
977 }
978
979 #[must_use]
981 pub const fn get_y_strikeout_size(&self) -> i16 {
982 self.y_strikeout_size
983 }
984
985 #[must_use]
987 pub const fn get_y_strikeout_position(&self) -> i16 {
988 self.y_strikeout_position
989 }
990
991 #[must_use]
993 pub const fn use_typo_metrics(&self) -> bool {
994 (self.fs_selection & 0x0080) != 0
996 }
997}
998
999#[cfg(all(test, feature = "parser"))]
1000mod tests {
1001 use super::*;
1002
1003 #[test]
1004 fn test_parse_font_weight_keywords() {
1005 assert_eq!(
1006 parse_font_weight("normal").unwrap(),
1007 StyleFontWeight::Normal
1008 );
1009 assert_eq!(parse_font_weight("bold").unwrap(), StyleFontWeight::Bold);
1010 assert_eq!(
1011 parse_font_weight("lighter").unwrap(),
1012 StyleFontWeight::Lighter
1013 );
1014 assert_eq!(
1015 parse_font_weight("bolder").unwrap(),
1016 StyleFontWeight::Bolder
1017 );
1018 }
1019
1020 #[test]
1021 fn test_parse_font_weight_numbers() {
1022 assert_eq!(parse_font_weight("100").unwrap(), StyleFontWeight::W100);
1023 assert_eq!(parse_font_weight("400").unwrap(), StyleFontWeight::Normal);
1024 assert_eq!(parse_font_weight("700").unwrap(), StyleFontWeight::Bold);
1025 assert_eq!(parse_font_weight("900").unwrap(), StyleFontWeight::W900);
1026 }
1027
1028 #[test]
1029 fn test_parse_font_weight_invalid() {
1030 assert!(parse_font_weight("thin").is_err());
1031 assert!(parse_font_weight("").is_err());
1032 assert!(parse_font_weight("450").is_err());
1033 assert!(parse_font_weight("boldest").is_err());
1034 }
1035
1036 #[test]
1037 fn test_parse_font_style() {
1038 assert_eq!(parse_font_style("normal").unwrap(), StyleFontStyle::Normal);
1039 assert_eq!(parse_font_style("italic").unwrap(), StyleFontStyle::Italic);
1040 assert_eq!(
1041 parse_font_style("oblique").unwrap(),
1042 StyleFontStyle::Oblique
1043 );
1044 assert_eq!(
1045 parse_font_style(" italic ").unwrap(),
1046 StyleFontStyle::Italic
1047 );
1048 assert!(parse_font_style("slanted").is_err());
1049 }
1050
1051 #[test]
1052 fn test_parse_font_size() {
1053 assert_eq!(
1054 parse_style_font_size("16px").unwrap().inner,
1055 PixelValue::px(16.0)
1056 );
1057 assert_eq!(
1058 parse_style_font_size("1.2em").unwrap().inner,
1059 PixelValue::em(1.2)
1060 );
1061 assert_eq!(
1062 parse_style_font_size("12pt").unwrap().inner,
1063 PixelValue::pt(12.0)
1064 );
1065 assert_eq!(
1066 parse_style_font_size("120%").unwrap().inner,
1067 PixelValue::percent(120.0)
1068 );
1069 assert!(parse_style_font_size("medium").is_err());
1070 }
1071
1072 #[test]
1073 fn test_parse_font_family() {
1074 let result = parse_style_font_family("Arial").unwrap();
1076 assert_eq!(result.len(), 1);
1077 assert_eq!(
1078 result.as_slice()[0],
1079 StyleFontFamily::System("Arial".into())
1080 );
1081
1082 let result = parse_style_font_family("\"Times New Roman\"").unwrap();
1084 assert_eq!(result.len(), 1);
1085 assert_eq!(
1086 result.as_slice()[0],
1087 StyleFontFamily::System("Times New Roman".into())
1088 );
1089
1090 let result = parse_style_font_family("Georgia, serif").unwrap();
1092 assert_eq!(result.len(), 2);
1093 assert_eq!(
1094 result.as_slice()[0],
1095 StyleFontFamily::System("Georgia".into())
1096 );
1097 assert_eq!(
1098 result.as_slice()[1],
1099 StyleFontFamily::System("serif".into())
1100 );
1101
1102 let result = parse_style_font_family(" 'Courier New' , monospace ").unwrap();
1104 assert_eq!(result.len(), 2);
1105 assert_eq!(
1106 result.as_slice()[0],
1107 StyleFontFamily::System("Courier New".into())
1108 );
1109 assert_eq!(
1110 result.as_slice()[1],
1111 StyleFontFamily::System("monospace".into())
1112 );
1113 }
1114
1115 #[test]
1116 fn test_parse_system_font_type() {
1117 use crate::system::SystemFontType;
1118
1119 let result = parse_style_font_family("system:ui").unwrap();
1121 assert_eq!(result.len(), 1);
1122 assert_eq!(
1123 result.as_slice()[0],
1124 StyleFontFamily::SystemType(SystemFontType::Ui)
1125 );
1126
1127 let result = parse_style_font_family("system:monospace:bold").unwrap();
1129 assert_eq!(result.len(), 1);
1130 assert_eq!(
1131 result.as_slice()[0],
1132 StyleFontFamily::SystemType(SystemFontType::MonospaceBold)
1133 );
1134
1135 let result = parse_style_font_family("system:monospace:italic").unwrap();
1137 assert_eq!(result.len(), 1);
1138 assert_eq!(
1139 result.as_slice()[0],
1140 StyleFontFamily::SystemType(SystemFontType::MonospaceItalic)
1141 );
1142
1143 let result = parse_style_font_family("system:ui, Arial, sans-serif").unwrap();
1145 assert_eq!(result.len(), 3);
1146 assert_eq!(
1147 result.as_slice()[0],
1148 StyleFontFamily::SystemType(SystemFontType::Ui)
1149 );
1150 assert_eq!(
1151 result.as_slice()[1],
1152 StyleFontFamily::System("Arial".into())
1153 );
1154 assert_eq!(
1155 result.as_slice()[2],
1156 StyleFontFamily::System("sans-serif".into())
1157 );
1158
1159 assert!(parse_style_font_family("system:ui").is_ok());
1161 assert!(parse_style_font_family("system:ui:bold").is_ok());
1162 assert!(parse_style_font_family("system:monospace").is_ok());
1163 assert!(parse_style_font_family("system:monospace:bold").is_ok());
1164 assert!(parse_style_font_family("system:monospace:italic").is_ok());
1165 assert!(parse_style_font_family("system:title").is_ok());
1166 assert!(parse_style_font_family("system:title:bold").is_ok());
1167 assert!(parse_style_font_family("system:menu").is_ok());
1168 assert!(parse_style_font_family("system:small").is_ok());
1169 assert!(parse_style_font_family("system:serif").is_ok());
1170 assert!(parse_style_font_family("system:serif:bold").is_ok());
1171
1172 let result = parse_style_font_family("system:invalid").unwrap();
1174 assert_eq!(result.len(), 1);
1175 assert_eq!(
1176 result.as_slice()[0],
1177 StyleFontFamily::System("system:invalid".into())
1178 );
1179 }
1180
1181 #[test]
1182 fn test_system_font_type_css_roundtrip() {
1183 use crate::system::SystemFontType;
1184
1185 let types = [
1187 SystemFontType::Ui,
1188 SystemFontType::UiBold,
1189 SystemFontType::Monospace,
1190 SystemFontType::MonospaceBold,
1191 SystemFontType::MonospaceItalic,
1192 SystemFontType::Title,
1193 SystemFontType::TitleBold,
1194 SystemFontType::Menu,
1195 SystemFontType::Small,
1196 SystemFontType::Serif,
1197 SystemFontType::SerifBold,
1198 ];
1199
1200 for ft in &types {
1201 let css = ft.as_css_str();
1202 let parsed = SystemFontType::from_css_str(css).unwrap();
1203 assert_eq!(*ft, parsed, "Roundtrip failed for {ft:?}");
1204 }
1205 }
1206}
1207
1208#[cfg(test)]
1209#[allow(clippy::too_many_lines, clippy::float_cmp)]
1210mod autotest_generated {
1211 use std::collections::hash_map::DefaultHasher;
1212
1213 use super::*;
1214 use crate::props::basic::{error::ParseIntError as CParseIntError, length::SizeMetric};
1215
1216 fn hash_of<T: Hash>(value: &T) -> u64 {
1217 let mut hasher = DefaultHasher::new();
1218 value.hash(&mut hasher);
1219 hasher.finish()
1220 }
1221
1222 fn boxed_font_data(value: u64) -> *const c_void {
1224 Box::into_raw(Box::new(value)).cast::<c_void>().cast_const()
1225 }
1226
1227 extern "C" fn noop_destructor(_ptr: *mut c_void) {}
1228
1229 static SINGLE_DTOR_CALLS: AtomicUsize = AtomicUsize::new(0);
1232 extern "C" fn single_counting_destructor(ptr: *mut c_void) {
1233 SINGLE_DTOR_CALLS.fetch_add(1, AtomicOrdering::SeqCst);
1234 if !ptr.is_null() {
1235 unsafe { drop(Box::from_raw(ptr.cast::<u64>())) };
1236 }
1237 }
1238
1239 static CLONE_DTOR_CALLS: AtomicUsize = AtomicUsize::new(0);
1240 extern "C" fn clone_counting_destructor(ptr: *mut c_void) {
1241 CLONE_DTOR_CALLS.fetch_add(1, AtomicOrdering::SeqCst);
1242 if !ptr.is_null() {
1243 unsafe { drop(Box::from_raw(ptr.cast::<u64>())) };
1244 }
1245 }
1246
1247 static MANY_DTOR_CALLS: AtomicUsize = AtomicUsize::new(0);
1248 extern "C" fn many_counting_destructor(ptr: *mut c_void) {
1249 MANY_DTOR_CALLS.fetch_add(1, AtomicOrdering::SeqCst);
1250 if !ptr.is_null() {
1251 unsafe { drop(Box::from_raw(ptr.cast::<u64>())) };
1252 }
1253 }
1254
1255 #[test]
1260 fn next_font_ref_id_is_monotonic_and_never_zero() {
1261 let a = next_font_ref_id();
1262 let b = next_font_ref_id();
1263 assert!(a >= 1, "id 0 is reserved as the null-handle sentinel");
1266 assert!(b > a, "ids must be strictly increasing ({a} -> {b})");
1267 }
1268
1269 #[test]
1274 fn font_ref_new_post_construction_invariants() {
1275 let ptr = boxed_font_data(0xDEAD);
1276 let font = FontRef::new(ptr, single_counting_destructor);
1277
1278 assert_eq!(
1279 font.get_parsed(),
1280 ptr,
1281 "get_parsed must return the pointer passed to new()"
1282 );
1283 assert!(font.run_destructor);
1284 assert!(font.id >= 1);
1285 assert!(!font.copies.is_null());
1286 assert_eq!(unsafe { (*font.copies).load(AtomicOrdering::SeqCst) }, 1);
1287
1288 assert_eq!(SINGLE_DTOR_CALLS.load(AtomicOrdering::SeqCst), 0);
1289 drop(font);
1290 assert_eq!(
1291 SINGLE_DTOR_CALLS.load(AtomicOrdering::SeqCst),
1292 1,
1293 "the destructor must run exactly once when the last handle drops"
1294 );
1295 }
1296
1297 #[test]
1298 fn font_ref_new_accepts_null_pointer_without_panicking() {
1299 let font = FontRef::new(core::ptr::null(), noop_destructor);
1300 assert!(font.get_parsed().is_null());
1301 assert!(font.id >= 1);
1302 let dbg = format!("{font:?}");
1304 assert!(
1305 dbg.starts_with("FontRef(0x0"),
1306 "unexpected Debug output: {dbg}"
1307 );
1308 assert!(dbg.contains("copies: 1"), "unexpected Debug output: {dbg}");
1309 }
1310
1311 #[test]
1312 fn font_ref_clone_shares_identity_and_defers_the_destructor() {
1313 let ptr = boxed_font_data(42);
1314 let original = FontRef::new(ptr, clone_counting_destructor);
1315 let copy = original.clone();
1316
1317 assert_eq!(original, copy, "shallow clones are the same font");
1318 assert_eq!(original.id, copy.id);
1319 assert_eq!(hash_of(&original), hash_of(©));
1320 assert_eq!(original.cmp(©), Ordering::Equal);
1321 assert_eq!(original.get_parsed(), copy.get_parsed());
1322 assert_eq!(
1323 unsafe { (*original.copies).load(AtomicOrdering::SeqCst) },
1324 2
1325 );
1326
1327 drop(copy);
1328 assert_eq!(
1329 CLONE_DTOR_CALLS.load(AtomicOrdering::SeqCst),
1330 0,
1331 "dropping one of two handles must not free the parsed data"
1332 );
1333 drop(original);
1334 assert_eq!(CLONE_DTOR_CALLS.load(AtomicOrdering::SeqCst), 1);
1335 }
1336
1337 #[test]
1338 fn font_ref_many_clones_run_the_destructor_exactly_once() {
1339 let ptr = boxed_font_data(7);
1340 let original = FontRef::new(ptr, many_counting_destructor);
1341 let clones: Vec<FontRef> = (0..1000).map(|_| original.clone()).collect();
1342
1343 assert_eq!(
1344 unsafe { (*original.copies).load(AtomicOrdering::SeqCst) },
1345 1001
1346 );
1347 assert!(clones.iter().all(|c| *c == original));
1348
1349 drop(clones);
1350 assert_eq!(MANY_DTOR_CALLS.load(AtomicOrdering::SeqCst), 0);
1351 drop(original);
1352 assert_eq!(MANY_DTOR_CALLS.load(AtomicOrdering::SeqCst), 1);
1353 }
1354
1355 #[test]
1356 fn font_ref_identity_is_the_id_not_the_pointer() {
1357 let a = FontRef::new(core::ptr::null(), noop_destructor);
1361 let b = FontRef::new(core::ptr::null(), noop_destructor);
1362
1363 assert_eq!(a.get_parsed(), b.get_parsed(), "same (null) pointer");
1364 assert_ne!(a, b, "same pointer must not forge identity");
1365 assert_ne!(a.id, b.id);
1366 assert_ne!(hash_of(&a), hash_of(&b));
1367 assert_eq!(
1368 a.cmp(&b),
1369 Ordering::Less,
1370 "ids are handed out in increasing order"
1371 );
1372 assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
1373 }
1374
1375 #[test]
1376 fn font_ref_raw_zero_handle_is_drop_safe() {
1377 let make = || FontRef {
1380 parsed: core::ptr::null(),
1381 copies: core::ptr::null(),
1382 id: 0,
1383 run_destructor: false,
1384 parsed_destructor: noop_destructor,
1385 };
1386 let raw = make();
1387 let raw2 = make();
1388
1389 assert!(raw.get_parsed().is_null());
1390 assert_eq!(format!("{raw:?}"), "FontRef(0x0)");
1391 assert_eq!(raw, raw2, "both carry the id==0 sentinel");
1392
1393 let cloned = raw.clone();
1394 assert_eq!(cloned.id, 0);
1395 assert!(
1396 cloned.copies.is_null(),
1397 "cloning must not allocate a refcount for a raw handle"
1398 );
1399
1400 drop(cloned);
1401 drop(raw2);
1402 drop(raw); }
1404
1405 #[test]
1410 fn style_font_family_as_string_quotes_only_when_whitespace_is_present() {
1411 assert_eq!(StyleFontFamily::System("Arial".into()).as_string(), "Arial");
1412 assert_eq!(
1413 StyleFontFamily::System("Times New Roman".into()).as_string(),
1414 "\"Times New Roman\""
1415 );
1416 assert_eq!(StyleFontFamily::System("".into()).as_string(), "");
1418 assert_eq!(
1420 StyleFontFamily::System("a\tb".into()).as_string(),
1421 "\"a\tb\""
1422 );
1423 assert_eq!(
1424 StyleFontFamily::System("a\nb".into()).as_string(),
1425 "\"a\nb\""
1426 );
1427 }
1428
1429 #[test]
1430 fn style_font_family_as_string_handles_unicode() {
1431 assert_eq!(
1433 StyleFontFamily::System("日本語".into()).as_string(),
1434 "日本語"
1435 );
1436 assert_eq!(
1437 StyleFontFamily::System("\u{1F600}".into()).as_string(),
1438 "\u{1F600}"
1439 );
1440 assert_eq!(
1442 StyleFontFamily::System("e\u{0301}".into()).as_string(),
1443 "e\u{0301}"
1444 );
1445 assert_eq!(
1447 StyleFontFamily::System("a\u{00A0}b".into()).as_string(),
1448 "\"a\u{00A0}b\""
1449 );
1450 }
1451
1452 #[test]
1453 fn style_font_family_as_string_file_and_systemtype_and_ref() {
1454 assert_eq!(
1456 StyleFontFamily::File("my font.ttf".into()).as_string(),
1457 "url(my font.ttf)"
1458 );
1459 assert_eq!(
1460 StyleFontFamily::SystemType(SystemFontType::MonospaceBold).as_string(),
1461 "system:monospace:bold"
1462 );
1463
1464 let ptr = 0xdead_beef_usize as *const c_void;
1465 let fam = StyleFontFamily::Ref(FontRef::new(ptr, noop_destructor));
1466 assert_eq!(fam.as_string(), "font-ref(0xdeadbeef)");
1467 }
1468
1469 #[test]
1470 fn style_font_family_as_string_on_huge_name_does_not_panic() {
1471 let huge = "x".repeat(1_000_000);
1472 let fam = StyleFontFamily::System(huge.as_str().into());
1473 assert_eq!(fam.as_string().len(), 1_000_000);
1474 }
1475
1476 #[cfg(feature = "parser")]
1481 #[test]
1482 fn parse_font_weight_valid_minimal_and_full_roundtrip() {
1483 assert_eq!(
1484 parse_font_weight("normal").unwrap(),
1485 StyleFontWeight::Normal
1486 );
1487
1488 for weight in [
1489 StyleFontWeight::Lighter,
1490 StyleFontWeight::W100,
1491 StyleFontWeight::W200,
1492 StyleFontWeight::W300,
1493 StyleFontWeight::Normal,
1494 StyleFontWeight::W500,
1495 StyleFontWeight::W600,
1496 StyleFontWeight::Bold,
1497 StyleFontWeight::W800,
1498 StyleFontWeight::W900,
1499 StyleFontWeight::Bolder,
1500 ] {
1501 let css = weight.print_as_css_value();
1502 assert_eq!(
1503 parse_font_weight(&css).unwrap(),
1504 weight,
1505 "encode==decode failed for {weight:?} (printed as {css:?})"
1506 );
1507 }
1508 }
1509
1510 #[cfg(feature = "parser")]
1511 #[test]
1512 fn parse_font_weight_numeric_aliases_collapse_onto_keywords() {
1513 assert_eq!(parse_font_weight("400").unwrap(), StyleFontWeight::Normal);
1516 assert_eq!(parse_font_weight("700").unwrap(), StyleFontWeight::Bold);
1517 assert_eq!(StyleFontWeight::Normal.print_as_css_value(), "normal");
1518 assert_eq!(StyleFontWeight::Bold.print_as_css_value(), "bold");
1519 }
1520
1521 #[cfg(feature = "parser")]
1522 #[test]
1523 fn parse_font_weight_rejects_empty_and_whitespace_only() {
1524 for input in ["", " ", " ", "\t\n", "\r\n\t "] {
1525 let err = parse_font_weight(input).unwrap_err();
1526 assert!(
1527 matches!(
1528 err,
1529 CssFontWeightParseError::InvalidValue(InvalidValueErr(""))
1530 ),
1531 "expected trimmed InvalidValue(\"\") for {input:?}, got {err:?}"
1532 );
1533 }
1534 }
1535
1536 #[cfg(feature = "parser")]
1537 #[test]
1538 fn parse_font_weight_rejects_garbage_and_reports_the_trimmed_input() {
1539 assert_eq!(
1540 parse_font_weight(" thin ").unwrap_err(),
1541 CssFontWeightParseError::InvalidValue(InvalidValueErr("thin")),
1542 "the error must carry the trimmed input"
1543 );
1544 for input in [
1545 "thin",
1546 "boldest",
1547 "bold;garbage",
1548 "normal!",
1549 "\u{0}\u{1}\u{7f}",
1550 "-",
1551 ] {
1552 assert!(
1553 parse_font_weight(input).is_err(),
1554 "{input:?} must not parse"
1555 );
1556 }
1557 }
1558
1559 #[cfg(feature = "parser")]
1560 #[test]
1561 fn parse_font_weight_rejects_boundary_numbers_without_ever_yielding_invalidnumber() {
1562 for input in [
1565 "0",
1566 "-0",
1567 "450",
1568 "1000",
1569 "0400",
1570 "+400",
1571 "400.0",
1572 "4e2",
1573 "9223372036854775807",
1574 "-9223372036854775808",
1575 "18446744073709551616",
1576 "NaN",
1577 "inf",
1578 "-inf",
1579 "1e309",
1580 ] {
1581 let err = parse_font_weight(input).unwrap_err();
1582 assert!(
1583 matches!(err, CssFontWeightParseError::InvalidValue(_)),
1584 "{input:?} should be an InvalidValue, got {err:?}"
1585 );
1586 }
1587 }
1588
1589 #[cfg(feature = "parser")]
1590 #[test]
1591 fn parse_font_weight_trims_unicode_whitespace() {
1592 assert_eq!(
1595 parse_font_weight("\u{00A0}bold\u{00A0}").unwrap(),
1596 StyleFontWeight::Bold
1597 );
1598 assert_eq!(
1599 parse_font_weight("\u{2028}400").unwrap(),
1600 StyleFontWeight::Normal
1601 );
1602 }
1603
1604 #[cfg(feature = "parser")]
1605 #[test]
1606 fn parse_font_weight_unicode_garbage_is_rejected_and_displayable() {
1607 let err = parse_font_weight("\u{1F600}").unwrap_err();
1608 assert_eq!(
1609 err,
1610 CssFontWeightParseError::InvalidValue(InvalidValueErr("\u{1F600}"))
1611 );
1612 let msg = format!("{err}");
1614 assert!(msg.contains('\u{1F600}'), "unexpected message: {msg}");
1615 assert!(!format!("{err:?}").is_empty());
1616
1617 assert!(
1618 parse_font_weight("bold\u{0301}").is_err(),
1619 "combining mark must not be trimmed"
1620 );
1621 }
1622
1623 #[cfg(feature = "parser")]
1624 #[test]
1625 fn parse_font_weight_survives_extremely_long_and_deeply_nested_input() {
1626 let long = "bold".repeat(250_000); assert!(parse_font_weight(&long).is_err());
1628
1629 let nested = "(".repeat(10_000);
1630 assert!(parse_font_weight(&nested).is_err());
1631
1632 let long_digits = "9".repeat(100_000);
1633 assert!(parse_font_weight(&long_digits).is_err());
1634 }
1635
1636 #[cfg(feature = "parser")]
1641 #[test]
1642 fn parse_font_style_valid_minimal_and_full_roundtrip() {
1643 assert_eq!(parse_font_style("normal").unwrap(), StyleFontStyle::Normal);
1644 for style in [
1645 StyleFontStyle::Normal,
1646 StyleFontStyle::Italic,
1647 StyleFontStyle::Oblique,
1648 ] {
1649 let css = style.print_as_css_value();
1650 assert_eq!(
1651 parse_font_style(&css).unwrap(),
1652 style,
1653 "encode==decode failed for {style:?}"
1654 );
1655 }
1656 }
1657
1658 #[cfg(feature = "parser")]
1659 #[test]
1660 fn parse_font_style_rejects_empty_whitespace_and_garbage() {
1661 for input in ["", " ", "\t\n"] {
1662 assert_eq!(
1663 parse_font_style(input).unwrap_err(),
1664 CssFontStyleParseError::InvalidValue(InvalidValueErr(""))
1665 );
1666 }
1667 for input in [
1668 "slanted",
1669 "italics",
1670 "ITALIC",
1671 "italic;garbage",
1672 "oblique 14deg",
1673 "0",
1674 "-0",
1675 "NaN",
1676 "inf",
1677 "9223372036854775807",
1678 ] {
1679 assert!(parse_font_style(input).is_err(), "{input:?} must not parse");
1680 }
1681 }
1682
1683 #[cfg(feature = "parser")]
1684 #[test]
1685 fn parse_font_style_leading_trailing_junk_and_unicode() {
1686 assert_eq!(
1687 parse_font_style(" italic ").unwrap(),
1688 StyleFontStyle::Italic
1689 );
1690 assert_eq!(
1691 parse_font_style(" italic;").unwrap_err(),
1692 CssFontStyleParseError::InvalidValue(InvalidValueErr("italic;"))
1693 );
1694 let err = parse_font_style("\u{1F600}\u{0301}").unwrap_err();
1695 assert!(!format!("{err}").is_empty());
1696 }
1697
1698 #[cfg(feature = "parser")]
1699 #[test]
1700 fn parse_font_style_survives_extremely_long_and_deeply_nested_input() {
1701 let long = "italic".repeat(200_000); assert!(parse_font_style(&long).is_err());
1703 let nested = "[".repeat(10_000);
1704 assert!(parse_font_style(&nested).is_err());
1705 }
1706
1707 #[cfg(feature = "parser")]
1712 #[test]
1713 fn parse_style_font_size_valid_minimal_and_metric_roundtrip() {
1714 assert_eq!(
1715 parse_style_font_size("16px").unwrap().inner,
1716 PixelValue::px(16.0)
1717 );
1718
1719 for metric in [
1722 SizeMetric::Px,
1723 SizeMetric::Pt,
1724 SizeMetric::Em,
1725 SizeMetric::Rem,
1726 SizeMetric::In,
1727 SizeMetric::Cm,
1728 SizeMetric::Mm,
1729 SizeMetric::Percent,
1730 SizeMetric::Vw,
1731 SizeMetric::Vh,
1732 SizeMetric::Vmax,
1733 ] {
1734 let size = StyleFontSize {
1735 inner: PixelValue::from_metric(metric, 12.0),
1736 };
1737 let css = size.print_as_css_value();
1738 assert_eq!(
1739 parse_style_font_size(&css).unwrap(),
1740 size,
1741 "encode==decode failed for {metric:?} (printed as {css:?})"
1742 );
1743 }
1744
1745 let default = StyleFontSize::default();
1747 assert_eq!(default.print_as_css_value(), "12pt");
1748 assert_eq!(parse_style_font_size("12pt").unwrap(), default);
1749 }
1750
1751 #[cfg(feature = "parser")]
1752 #[test]
1753 fn parse_style_font_size_vmin_is_shadowed_by_the_in_suffix() {
1754 let size = StyleFontSize {
1759 inner: PixelValue::from_metric(SizeMetric::Vmin, 12.0),
1760 };
1761 let css = size.print_as_css_value();
1762 assert_eq!(css, "12vmin");
1763
1764 assert_eq!(
1765 parse_style_font_size(&css).unwrap().inner,
1766 PixelValue::from_metric(SizeMetric::Vmin, 12.0)
1767 );
1768 }
1769
1770 #[cfg(feature = "parser")]
1771 #[test]
1772 fn parse_style_font_size_rejects_empty_and_whitespace_only() {
1773 for input in ["", " ", " ", "\t\n"] {
1774 let err = parse_style_font_size(input).unwrap_err();
1775 assert_eq!(
1776 err,
1777 CssStyleFontSizeParseError::PixelValue(CssPixelValueParseError::EmptyString),
1778 "unexpected error for {input:?}"
1779 );
1780 }
1781 }
1782
1783 #[cfg(feature = "parser")]
1784 #[test]
1785 fn parse_style_font_size_rejects_garbage_and_bare_units() {
1786 let err = parse_style_font_size("px").unwrap_err();
1787 assert!(
1788 matches!(
1789 err,
1790 CssStyleFontSizeParseError::PixelValue(CssPixelValueParseError::NoValueGiven(
1791 "px",
1792 SizeMetric::Px
1793 ))
1794 ),
1795 "expected NoValueGiven, got {err:?}"
1796 );
1797
1798 for input in [
1799 "medium",
1800 "larger",
1801 "16PX", "16px;junk",
1803 "16 px junk",
1804 "\u{1F600}",
1805 "--",
1806 "px16",
1807 ] {
1808 assert!(
1809 parse_style_font_size(input).is_err(),
1810 "{input:?} must not parse"
1811 );
1812 }
1813 }
1814
1815 #[cfg(feature = "parser")]
1816 #[test]
1817 fn parse_style_font_size_accepts_unitless_numbers_as_px() {
1818 assert_eq!(
1821 parse_style_font_size("0").unwrap().inner,
1822 PixelValue::px(0.0)
1823 );
1824 assert_eq!(
1825 parse_style_font_size("16").unwrap().inner,
1826 PixelValue::px(16.0)
1827 );
1828 assert_eq!(
1829 parse_style_font_size("-16").unwrap().inner,
1830 PixelValue::px(-16.0)
1831 );
1832 }
1833
1834 #[cfg(feature = "parser")]
1835 #[test]
1836 fn parse_style_font_size_boundary_numbers_saturate_instead_of_panicking() {
1837 assert_eq!(
1839 parse_style_font_size("-0px").unwrap().inner.number.get(),
1840 0.0
1841 );
1842 assert_eq!(
1843 parse_style_font_size("0px").unwrap().inner.number.get(),
1844 0.0
1845 );
1846
1847 let big = parse_style_font_size("1e40px").unwrap().inner.number.get();
1849 assert!(
1850 big.is_finite() && big > 0.0,
1851 "expected a saturated finite value, got {big}"
1852 );
1853 let small = parse_style_font_size("-1e40px").unwrap().inner.number.get();
1854 assert!(
1855 small.is_finite() && small < 0.0,
1856 "expected a saturated finite value, got {small}"
1857 );
1858
1859 let inf = parse_style_font_size("inf").unwrap().inner.number.get();
1861 assert!(inf.is_finite() && inf > 0.0);
1862 let neg_inf = parse_style_font_size("-infinitypx")
1863 .unwrap()
1864 .inner
1865 .number
1866 .get();
1867 assert!(neg_inf.is_finite() && neg_inf < 0.0);
1868
1869 for input in ["9223372036854775807", "18446744073709551615px"] {
1871 let v = parse_style_font_size(input).unwrap().inner.number.get();
1872 assert!(v.is_finite(), "{input:?} produced {v}");
1873 }
1874
1875 assert_eq!(
1877 parse_style_font_size("16.0004px").unwrap().inner,
1878 PixelValue::px(16.0)
1879 );
1880 assert_eq!(
1881 parse_style_font_size("1e-40px").unwrap().inner.number.get(),
1882 0.0
1883 );
1884 }
1885
1886 #[cfg(feature = "parser")]
1887 #[test]
1888 fn parse_style_font_size_nan_is_silently_coerced_to_zero() {
1889 let parsed = parse_style_font_size("NaN").unwrap();
1892 assert_eq!(parsed.inner.metric, SizeMetric::Px);
1893 assert_eq!(parsed.inner.number.get(), 0.0);
1894 assert_eq!(
1895 parse_style_font_size("nanpx").unwrap().inner.number.get(),
1896 0.0
1897 );
1898 }
1899
1900 #[cfg(feature = "parser")]
1901 #[test]
1902 fn parse_style_font_size_leading_trailing_whitespace_is_trimmed() {
1903 assert_eq!(
1904 parse_style_font_size(" 16px ").unwrap().inner,
1905 PixelValue::px(16.0)
1906 );
1907 assert_eq!(
1909 parse_style_font_size("16 px").unwrap().inner,
1910 PixelValue::px(16.0)
1911 );
1912 }
1913
1914 #[cfg(feature = "parser")]
1915 #[test]
1916 fn parse_style_font_size_survives_extremely_long_and_deeply_nested_input() {
1917 let long_digits = "1".repeat(50_000);
1918 let parsed = parse_style_font_size(&long_digits).unwrap();
1919 assert!(parsed.inner.number.get().is_finite());
1920
1921 let long_garbage = "z".repeat(1_000_000);
1922 assert!(parse_style_font_size(&long_garbage).is_err());
1923
1924 let nested = "(".repeat(10_000);
1925 assert!(parse_style_font_size(&nested).is_err());
1926 }
1927
1928 #[cfg(feature = "parser")]
1933 #[test]
1934 fn parse_style_font_family_valid_minimal() {
1935 let parsed = parse_style_font_family("Arial").unwrap();
1936 assert_eq!(parsed.len(), 1);
1937 assert_eq!(
1938 parsed.as_slice()[0],
1939 StyleFontFamily::System("Arial".into())
1940 );
1941 }
1942
1943 #[cfg(feature = "parser")]
1944 #[test]
1945 fn parse_style_font_family_never_returns_err() {
1946 let nested = "(".repeat(10_000);
1950 let long = "x".repeat(1_000_000);
1951 let inputs: Vec<&str> = vec![
1952 "",
1953 " ",
1954 "\t\n",
1955 ",",
1956 ",,,",
1957 "'unclosed",
1958 "\"unclosed",
1959 "\"Arial'",
1960 "'Arial\"",
1961 "\u{1F600}",
1962 "system:",
1963 "system:bogus",
1964 "url(x.ttf)",
1965 "font-ref(0xdeadbeef)",
1966 "\u{0}\u{7f}",
1967 &nested,
1968 &long,
1969 ];
1970 for input in inputs {
1971 assert!(
1972 parse_style_font_family(input).is_ok(),
1973 "parse_style_font_family unexpectedly failed for {:?}",
1974 &input[..input.len().min(32)]
1975 );
1976 }
1977 }
1978
1979 #[cfg(feature = "parser")]
1980 #[test]
1981 fn parse_style_font_family_empty_input_yields_one_empty_family() {
1982 let parsed = parse_style_font_family("").unwrap();
1985 assert_eq!(parsed.len(), 1);
1986 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("".into()));
1987
1988 let parsed = parse_style_font_family(" ").unwrap();
1989 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("".into()));
1990
1991 let parsed = parse_style_font_family(",,,").unwrap();
1992 assert_eq!(parsed.len(), 4, "N commas produce N+1 (empty) families");
1993 assert!(parsed
1994 .iter()
1995 .all(|f| *f == StyleFontFamily::System("".into())));
1996 }
1997
1998 #[cfg(feature = "parser")]
1999 #[test]
2000 fn parse_style_font_family_unclosed_quotes_keep_the_quote_character() {
2001 let parsed = parse_style_font_family("'unclosed").unwrap();
2004 assert_eq!(
2005 parsed.as_slice()[0],
2006 StyleFontFamily::System("'unclosed".into())
2007 );
2008
2009 let parsed = parse_style_font_family("\"Arial'").unwrap();
2010 assert_eq!(
2011 parsed.as_slice()[0],
2012 StyleFontFamily::System("\"Arial'".into())
2013 );
2014
2015 let parsed = parse_style_font_family("\"\"").unwrap();
2017 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("".into()));
2018 }
2019
2020 #[cfg(feature = "parser")]
2021 #[test]
2022 fn parse_style_font_family_system_prefix_is_case_sensitive_and_falls_back() {
2023 let parsed = parse_style_font_family("system:bogus").unwrap();
2025 assert_eq!(
2026 parsed.as_slice()[0],
2027 StyleFontFamily::System("system:bogus".into())
2028 );
2029 let parsed = parse_style_font_family("system:").unwrap();
2030 assert_eq!(
2031 parsed.as_slice()[0],
2032 StyleFontFamily::System("system:".into())
2033 );
2034 let parsed = parse_style_font_family("SYSTEM:UI").unwrap();
2036 assert_eq!(
2037 parsed.as_slice()[0],
2038 StyleFontFamily::System("SYSTEM:UI".into())
2039 );
2040 let parsed = parse_style_font_family("system:UI").unwrap();
2041 assert_eq!(
2042 parsed.as_slice()[0],
2043 StyleFontFamily::System("system:UI".into())
2044 );
2045 }
2046
2047 #[cfg(feature = "parser")]
2048 #[test]
2049 fn parse_style_font_family_never_produces_file_or_ref_variants() {
2050 for input in [
2051 "url(x.ttf)",
2052 "font-ref(0x1)",
2053 "Arial",
2054 "system:ui",
2055 "'a'",
2056 "\u{1F600}",
2057 ] {
2058 let parsed = parse_style_font_family(input).unwrap();
2059 assert!(
2060 parsed.iter().all(|f| matches!(
2061 f,
2062 StyleFontFamily::System(_) | StyleFontFamily::SystemType(_)
2063 )),
2064 "the parser must only ever yield System/SystemType, got {parsed:?}"
2065 );
2066 }
2067 }
2068
2069 #[cfg(feature = "parser")]
2070 #[test]
2071 fn parse_style_font_family_handles_unicode_names() {
2072 let parsed = parse_style_font_family("日本語, \u{1F600}, e\u{0301}").unwrap();
2073 assert_eq!(parsed.len(), 3);
2074 assert_eq!(
2075 parsed.as_slice()[0],
2076 StyleFontFamily::System("日本語".into())
2077 );
2078 assert_eq!(
2079 parsed.as_slice()[1],
2080 StyleFontFamily::System("\u{1F600}".into())
2081 );
2082 assert_eq!(
2083 parsed.as_slice()[2],
2084 StyleFontFamily::System("e\u{0301}".into())
2085 );
2086 }
2087
2088 #[cfg(feature = "parser")]
2089 #[test]
2090 fn parse_style_font_family_survives_extremely_long_and_deeply_nested_input() {
2091 let huge_name = "x".repeat(1_000_000);
2092 let parsed = parse_style_font_family(&huge_name).unwrap();
2093 assert_eq!(parsed.len(), 1);
2094 assert_eq!(
2095 parsed.as_slice()[0],
2096 StyleFontFamily::System(huge_name.as_str().into())
2097 );
2098
2099 let many = "Arial,".repeat(10_000);
2100 let parsed = parse_style_font_family(&many).unwrap();
2101 assert_eq!(parsed.len(), 10_001, "trailing comma adds one empty family");
2102
2103 let nested = "(".repeat(10_000);
2104 let parsed = parse_style_font_family(&nested).unwrap();
2105 assert_eq!(parsed.len(), 1);
2106 }
2107
2108 #[cfg(feature = "parser")]
2113 #[test]
2114 fn style_font_family_as_string_roundtrips_through_the_parser() {
2115 for name in [
2116 "Arial",
2117 "Times New Roman",
2118 "",
2119 "日本語",
2120 "a\u{00A0}b",
2121 "Fo\"o",
2122 "serif",
2123 ] {
2124 let family = StyleFontFamily::System(name.into());
2125 let css = family.as_string();
2126 let parsed = parse_style_font_family(&css).unwrap();
2127 assert_eq!(parsed.len(), 1, "{name:?} printed as {css:?}");
2128 assert_eq!(
2129 parsed.as_slice()[0],
2130 family,
2131 "encode==decode failed for {name:?}"
2132 );
2133 }
2134
2135 for ft in [
2136 SystemFontType::Ui,
2137 SystemFontType::UiBold,
2138 SystemFontType::Monospace,
2139 SystemFontType::MonospaceBold,
2140 SystemFontType::MonospaceItalic,
2141 SystemFontType::Title,
2142 SystemFontType::TitleBold,
2143 SystemFontType::Menu,
2144 SystemFontType::Small,
2145 SystemFontType::Serif,
2146 SystemFontType::SerifBold,
2147 ] {
2148 let family = StyleFontFamily::SystemType(ft);
2149 let parsed = parse_style_font_family(&family.as_string()).unwrap();
2150 assert_eq!(
2151 parsed.as_slice()[0],
2152 family,
2153 "encode==decode failed for {ft:?}"
2154 );
2155 }
2156 }
2157
2158 #[cfg(feature = "parser")]
2159 #[test]
2160 fn style_font_family_as_string_does_not_escape_commas() {
2161 let family = StyleFontFamily::System("Foo,Bar".into());
2164 assert_eq!(family.as_string(), "Foo,Bar");
2165 let parsed = parse_style_font_family(&family.as_string()).unwrap();
2166 assert_eq!(parsed.len(), 2);
2167 }
2168
2169 #[cfg(feature = "parser")]
2170 #[test]
2171 fn style_font_family_file_and_ref_do_not_roundtrip() {
2172 let file = StyleFontFamily::File("f.ttf".into());
2175 let parsed = parse_style_font_family(&file.as_string()).unwrap();
2176 assert_eq!(
2177 parsed.as_slice()[0],
2178 StyleFontFamily::System("url(f.ttf)".into())
2179 );
2180
2181 let font = StyleFontFamily::Ref(FontRef::new(core::ptr::null(), noop_destructor));
2182 let parsed = parse_style_font_family(&font.as_string()).unwrap();
2183 assert_eq!(
2184 parsed.as_slice()[0],
2185 StyleFontFamily::System("font-ref(0x0)".into())
2186 );
2187 }
2188
2189 #[cfg(feature = "parser")]
2190 #[test]
2191 fn style_font_family_vec_print_as_css_value_roundtrips() {
2192 let css = "Arial, \"Times New Roman\", system:ui";
2193 let parsed = parse_style_font_family(css).unwrap();
2194 assert_eq!(parsed.print_as_css_value(), css);
2195 let reparsed = parse_style_font_family(&parsed.print_as_css_value()).unwrap();
2196 assert_eq!(reparsed, parsed, "encode==decode failed for a font stack");
2197 }
2198
2199 #[test]
2204 fn css_font_weight_parse_error_invalid_value_roundtrips() {
2205 for value in ["", "thin", "\u{1F600}", "a\u{0}b"] {
2206 let shared = CssFontWeightParseError::InvalidValue(InvalidValueErr(value));
2207 let owned = shared.to_contained();
2208 assert_eq!(
2209 owned,
2210 CssFontWeightParseErrorOwned::InvalidValue(InvalidValueErrOwned {
2211 value: value.into()
2212 })
2213 );
2214 assert_eq!(
2215 owned.to_shared(),
2216 shared,
2217 "to_contained/to_shared must round-trip"
2218 );
2219 assert!(!format!("{shared}").is_empty());
2220 }
2221 }
2222
2223 #[test]
2224 fn css_font_weight_parse_error_invalid_number_roundtrips() {
2225 let cases = [
2226 "".parse::<i32>().unwrap_err(),
2227 "x".parse::<i32>().unwrap_err(),
2228 "99999999999999999999".parse::<i32>().unwrap_err(),
2229 "-99999999999999999999".parse::<i32>().unwrap_err(),
2230 ];
2231 for err in cases {
2232 let shared = CssFontWeightParseError::InvalidNumber(err);
2233 let owned = shared.to_contained();
2234 assert_eq!(
2235 owned.to_shared(),
2236 shared,
2237 "kind must survive the FFI round-trip"
2238 );
2239 assert!(!format!("{shared}").is_empty());
2240 }
2241 }
2242
2243 #[test]
2244 fn css_font_weight_parse_error_zero_kind_roundtrip_is_lossy() {
2245 let zero_err = "0".parse::<core::num::NonZeroU32>().unwrap_err();
2248 let shared = CssFontWeightParseError::InvalidNumber(zero_err);
2249 let owned = shared.to_contained();
2250 assert_eq!(
2251 owned,
2252 CssFontWeightParseErrorOwned::InvalidNumber(CParseIntError::Zero),
2253 "the Zero kind must survive into the owned form"
2254 );
2255 assert_ne!(
2256 owned.to_shared(),
2257 shared,
2258 "to_std() cannot rebuild a Zero-kind ParseIntError (documented)"
2259 );
2260 }
2261
2262 #[test]
2263 fn css_font_style_parse_error_roundtrips() {
2264 for value in ["", "slanted", "\u{1F600}"] {
2265 let shared = CssFontStyleParseError::InvalidValue(InvalidValueErr(value));
2266 let owned = shared.to_contained();
2267 assert_eq!(
2268 owned,
2269 CssFontStyleParseErrorOwned::InvalidValue(InvalidValueErrOwned {
2270 value: value.into()
2271 })
2272 );
2273 assert_eq!(owned.to_shared(), shared);
2274 assert!(!format!("{shared}").is_empty());
2275 }
2276 }
2277
2278 #[test]
2279 fn css_style_font_size_parse_error_roundtrips_every_variant() {
2280 let cases = [
2281 CssPixelValueParseError::EmptyString,
2282 CssPixelValueParseError::NoValueGiven("px", SizeMetric::Px),
2283 CssPixelValueParseError::NoValueGiven("%", SizeMetric::Percent),
2284 CssPixelValueParseError::ValueParseErr("abc".parse::<f32>().unwrap_err(), "abc"),
2285 CssPixelValueParseError::ValueParseErr("".parse::<f32>().unwrap_err(), ""),
2286 CssPixelValueParseError::InvalidPixelValue("medium"),
2287 CssPixelValueParseError::InvalidPixelValue("\u{1F600}"),
2288 ];
2289 for inner in cases {
2290 let shared = CssStyleFontSizeParseError::PixelValue(inner);
2291 let owned = shared.to_contained();
2292 assert_eq!(
2293 owned.to_shared(),
2294 shared,
2295 "to_contained/to_shared must round-trip"
2296 );
2297 assert!(!format!("{shared}").is_empty());
2298 }
2299 }
2300
2301 #[cfg(feature = "parser")]
2302 #[test]
2303 fn css_style_font_family_parse_error_roundtrips_every_variant() {
2304 let cases = [
2305 CssStyleFontFamilyParseError::InvalidStyleFontFamily(""),
2306 CssStyleFontFamilyParseError::InvalidStyleFontFamily("bogus"),
2307 CssStyleFontFamilyParseError::UnclosedQuotes(UnclosedQuotesError("\"Arial")),
2308 CssStyleFontFamilyParseError::UnclosedQuotes(UnclosedQuotesError("\u{1F600}")),
2309 ];
2310 for shared in cases {
2311 let owned = shared.to_contained();
2312 assert_eq!(
2313 owned.to_shared(),
2314 shared,
2315 "to_contained/to_shared must round-trip"
2316 );
2317 assert!(!format!("{shared}").is_empty());
2318 }
2319 }
2320
2321 #[test]
2326 fn font_enum_defaults_and_ordering() {
2327 assert_eq!(StyleFontWeight::default(), StyleFontWeight::Normal);
2328 assert_eq!(StyleFontStyle::default(), StyleFontStyle::Normal);
2329 assert_eq!(StyleFontSize::default().inner, PixelValue::const_pt(12));
2330
2331 assert!(StyleFontWeight::W100 < StyleFontWeight::W900);
2333 assert!(StyleFontWeight::Normal < StyleFontWeight::Bold);
2334 assert!(StyleFontWeight::Lighter < StyleFontWeight::W100);
2335 assert!(StyleFontWeight::Bolder > StyleFontWeight::W900);
2336 }
2337
2338 #[test]
2339 fn format_as_rust_code_matches_the_debug_variant_names() {
2340 for weight in [
2341 StyleFontWeight::Lighter,
2342 StyleFontWeight::W100,
2343 StyleFontWeight::W200,
2344 StyleFontWeight::W300,
2345 StyleFontWeight::Normal,
2346 StyleFontWeight::W500,
2347 StyleFontWeight::W600,
2348 StyleFontWeight::Bold,
2349 StyleFontWeight::W800,
2350 StyleFontWeight::W900,
2351 StyleFontWeight::Bolder,
2352 ] {
2353 assert_eq!(
2354 weight.format_as_rust_code(0),
2355 format!("StyleFontWeight::{weight:?}")
2356 );
2357 }
2358 for style in [
2359 StyleFontStyle::Normal,
2360 StyleFontStyle::Italic,
2361 StyleFontStyle::Oblique,
2362 ] {
2363 assert_eq!(
2364 style.format_as_rust_code(0),
2365 format!("StyleFontStyle::{style:?}")
2366 );
2367 }
2368 assert_eq!(
2369 StyleFontFamily::SystemType(SystemFontType::Ui).format_as_rust_code(0),
2370 "StyleFontFamily::SystemType(SystemFontType::Ui)"
2371 );
2372 assert!(StyleFontFamily::System("Arial".into())
2373 .format_as_rust_code(0)
2374 .starts_with("StyleFontFamily::System(STRING_"));
2375 assert!(StyleFontFamily::File("a.ttf".into())
2376 .format_as_rust_code(0)
2377 .starts_with("StyleFontFamily::File(STRING_"));
2378 }
2379
2380 #[test]
2385 fn panose_zero_is_the_neutral_element() {
2386 const P: Panose = Panose::zero();
2387 assert_eq!(P, Panose::default());
2388 assert_eq!(hash_of(&P), hash_of(&Panose::default()));
2389 assert_eq!(P.family_type, 0);
2390 assert_eq!(P.serif_style, 0);
2391 assert_eq!(P.weight, 0);
2392 assert_eq!(P.proportion, 0);
2393 assert_eq!(P.contrast, 0);
2394 assert_eq!(P.stroke_variation, 0);
2395 assert_eq!(P.arm_style, 0);
2396 assert_eq!(P.letterform, 0);
2397 assert_eq!(P.midline, 0);
2398 assert_eq!(P.x_height, 0);
2399
2400 let mut max = Panose::zero();
2401 max.family_type = u8::MAX;
2402 assert!(max > P, "derived Ord must order by the first field");
2403 }
2404
2405 #[test]
2406 fn font_metrics_zero_invariants() {
2407 const M: FontMetrics = FontMetrics::zero();
2408 assert_eq!(M, FontMetrics::default());
2409
2410 assert_eq!(M.units_per_em, 1000);
2412 assert_eq!(M.us_weight_class, 400);
2413 assert_eq!(M.us_width_class, 5);
2414 assert_eq!(M.panose, Panose::zero());
2415
2416 assert_eq!(M.get_ascender(), 0);
2417 assert_eq!(M.get_descender(), 0);
2418 assert_eq!(M.get_line_gap(), 0);
2419 assert_eq!(M.get_advance_width_max(), 0);
2420 assert_eq!(M.get_min_left_side_bearing(), 0);
2421 assert_eq!(M.get_min_right_side_bearing(), 0);
2422 assert_eq!(M.get_x_min(), 0);
2423 assert_eq!(M.get_y_min(), 0);
2424 assert_eq!(M.get_x_max(), 0);
2425 assert_eq!(M.get_y_max(), 0);
2426 assert_eq!(M.get_x_max_extent(), 0);
2427 assert_eq!(M.get_x_avg_char_width(), 0);
2428 assert_eq!(M.get_y_subscript_x_size(), 0);
2429 assert_eq!(M.get_y_subscript_y_size(), 0);
2430 assert_eq!(M.get_y_subscript_x_offset(), 0);
2431 assert_eq!(M.get_y_subscript_y_offset(), 0);
2432 assert_eq!(M.get_y_superscript_x_size(), 0);
2433 assert_eq!(M.get_y_superscript_y_size(), 0);
2434 assert_eq!(M.get_y_superscript_x_offset(), 0);
2435 assert_eq!(M.get_y_superscript_y_offset(), 0);
2436 assert_eq!(M.get_y_strikeout_size(), 0);
2437 assert_eq!(M.get_y_strikeout_position(), 0);
2438 assert!(!M.use_typo_metrics());
2439
2440 assert!(matches!(M.ul_code_page_range1, OptionU32::None));
2441 assert!(matches!(M.ul_code_page_range2, OptionU32::None));
2442 assert!(matches!(M.s_typo_ascender, OptionI16::None));
2443 assert!(matches!(M.s_typo_descender, OptionI16::None));
2444 assert!(matches!(M.s_typo_line_gap, OptionI16::None));
2445 assert!(matches!(M.us_win_ascent, OptionU16::None));
2446 assert!(matches!(M.us_win_descent, OptionU16::None));
2447 assert!(matches!(M.sx_height, OptionI16::None));
2448 assert!(matches!(M.s_cap_height, OptionI16::None));
2449 }
2450
2451 #[test]
2452 fn font_metrics_getters_return_extreme_values_unchanged() {
2453 let mut m = FontMetrics::zero();
2454 m.ascender = i16::MAX;
2455 m.descender = i16::MIN;
2456 m.line_gap = i16::MIN;
2457 m.advance_width_max = u16::MAX;
2458 m.min_left_side_bearing = i16::MIN;
2459 m.min_right_side_bearing = i16::MAX;
2460 m.x_min = i16::MIN;
2461 m.y_min = i16::MIN;
2462 m.x_max = i16::MAX;
2463 m.y_max = i16::MAX;
2464 m.x_max_extent = i16::MAX;
2465 m.x_avg_char_width = i16::MIN;
2466 m.y_subscript_x_size = i16::MAX;
2467 m.y_subscript_y_size = i16::MIN;
2468 m.y_subscript_x_offset = i16::MAX;
2469 m.y_subscript_y_offset = i16::MIN;
2470 m.y_superscript_x_size = i16::MAX;
2471 m.y_superscript_y_size = i16::MIN;
2472 m.y_superscript_x_offset = i16::MAX;
2473 m.y_superscript_y_offset = i16::MIN;
2474 m.y_strikeout_size = i16::MAX;
2475 m.y_strikeout_position = i16::MIN;
2476
2477 assert_eq!(m.get_ascender(), i16::MAX);
2479 assert_eq!(m.get_descender(), i16::MIN);
2480 assert_eq!(m.get_line_gap(), i16::MIN);
2481 assert_eq!(m.get_advance_width_max(), u16::MAX);
2482 assert_eq!(m.get_min_left_side_bearing(), i16::MIN);
2483 assert_eq!(m.get_min_right_side_bearing(), i16::MAX);
2484 assert_eq!(m.get_x_min(), i16::MIN);
2485 assert_eq!(m.get_y_min(), i16::MIN);
2486 assert_eq!(m.get_x_max(), i16::MAX);
2487 assert_eq!(m.get_y_max(), i16::MAX);
2488 assert_eq!(m.get_x_max_extent(), i16::MAX);
2489 assert_eq!(m.get_x_avg_char_width(), i16::MIN);
2490 assert_eq!(m.get_y_subscript_x_size(), i16::MAX);
2491 assert_eq!(m.get_y_subscript_y_size(), i16::MIN);
2492 assert_eq!(m.get_y_subscript_x_offset(), i16::MAX);
2493 assert_eq!(m.get_y_subscript_y_offset(), i16::MIN);
2494 assert_eq!(m.get_y_superscript_x_size(), i16::MAX);
2495 assert_eq!(m.get_y_superscript_y_size(), i16::MIN);
2496 assert_eq!(m.get_y_superscript_x_offset(), i16::MAX);
2497 assert_eq!(m.get_y_superscript_y_offset(), i16::MIN);
2498 assert_eq!(m.get_y_strikeout_size(), i16::MAX);
2499 assert_eq!(m.get_y_strikeout_position(), i16::MIN);
2500
2501 assert!(m.get_ascender() > m.get_descender());
2504 }
2505
2506 #[test]
2507 fn font_metrics_use_typo_metrics_reads_exactly_bit_7() {
2508 let mut m = FontMetrics::zero();
2509 for bit in 0..16u16 {
2510 m.fs_selection = 1 << bit;
2511 assert_eq!(
2512 m.use_typo_metrics(),
2513 bit == 7,
2514 "fs_selection bit {bit} must not affect USE_TYPO_METRICS"
2515 );
2516 }
2517 m.fs_selection = u16::MAX;
2518 assert!(m.use_typo_metrics());
2519 m.fs_selection = u16::MAX ^ 0x0080;
2520 assert!(!m.use_typo_metrics(), "clearing bit 7 must clear the flag");
2521 m.fs_selection = 0;
2522 assert!(!m.use_typo_metrics());
2523 }
2524}