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 corety::{AzString, U8Vec},
26 codegen::format::{FormatAsRustCode, GetHash},
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
60
61impl PrintAsCssValue for StyleFontWeight {
62 fn print_as_css_value(&self) -> String {
63 match self {
64 Self::Lighter => "lighter".to_string(),
65 Self::W100 => "100".to_string(),
66 Self::W200 => "200".to_string(),
67 Self::W300 => "300".to_string(),
68 Self::Normal => "normal".to_string(),
69 Self::W500 => "500".to_string(),
70 Self::W600 => "600".to_string(),
71 Self::Bold => "bold".to_string(),
72 Self::W800 => "800".to_string(),
73 Self::W900 => "900".to_string(),
74 Self::Bolder => "bolder".to_string(),
75 }
76 }
77}
78
79impl FormatAsRustCode for StyleFontWeight {
80 fn format_as_rust_code(&self, _tabs: usize) -> String {
81 use StyleFontWeight::{Lighter, W100, W200, W300, Normal, W500, W600, Bold, W800, W900, Bolder};
82 format!(
83 "StyleFontWeight::{}",
84 match self {
85 Lighter => "Lighter",
86 W100 => "W100",
87 W200 => "W200",
88 W300 => "W300",
89 Normal => "Normal",
90 W500 => "W500",
91 W600 => "W600",
92 Bold => "Bold",
93 W800 => "W800",
94 W900 => "W900",
95 Bolder => "Bolder",
96 }
97 )
98 }
99}
100
101#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
105#[repr(C)]
106#[derive(Default)]
107pub enum StyleFontStyle {
108 #[default]
109 Normal,
110 Italic,
111 Oblique,
112}
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::{Normal, Italic, 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] pub const fn get_parsed(&self) -> *const c_void {
234 self.parsed
235 }
236}
237impl_option!(
238 FontRef,
239 OptionFontRef,
240 copy = false,
241 [Debug, Clone, PartialEq, Eq, Hash]
242);
243unsafe impl Send for FontRef {}
244unsafe impl Sync for FontRef {}
245impl PartialEq for FontRef {
248 fn eq(&self, rhs: &Self) -> bool {
249 self.id == rhs.id
250 }
251}
252impl PartialOrd for FontRef {
253 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
254 Some(self.id.cmp(&other.id))
255 }
256}
257impl Ord for FontRef {
258 fn cmp(&self, other: &Self) -> Ordering {
259 self.id.cmp(&other.id)
260 }
261}
262impl Eq for FontRef {}
263impl Hash for FontRef {
264 fn hash<H: Hasher>(&self, state: &mut H) {
265 self.id.hash(state);
266 }
267}
268impl Clone for FontRef {
269 fn clone(&self) -> Self {
270 if !self.copies.is_null() {
271 unsafe {
272 (*self.copies).fetch_add(1, AtomicOrdering::SeqCst);
273 }
274 }
275 Self {
276 parsed: self.parsed,
277 copies: self.copies,
278 id: self.id, run_destructor: self.run_destructor,
280 parsed_destructor: self.parsed_destructor,
281 }
282 }
283}
284impl Drop for FontRef {
285 fn drop(&mut self) {
286 if self.run_destructor && !self.copies.is_null()
287 && unsafe { (*self.copies).fetch_sub(1, AtomicOrdering::SeqCst) } == 1 {
288 unsafe {
289 (self.parsed_destructor)(self.parsed.cast_mut());
290 drop(Box::from_raw(self.copies.cast_mut()));
291 }
292 }
293 }
294}
295
296#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
306#[repr(C, u8)]
307pub enum StyleFontFamily {
308 System(AzString),
310 SystemType(SystemFontType),
313 File(AzString),
315 Ref(FontRef),
317}
318
319impl_option!(
320 StyleFontFamily,
321 OptionStyleFontFamily,
322 copy = false,
323 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
324);
325
326impl StyleFontFamily {
327 pub fn as_string(&self) -> String {
328 match &self {
329 Self::System(s) => {
330 let owned = s.clone().into_library_owned_string();
331 if owned.contains(char::is_whitespace) {
332 format!("\"{owned}\"")
333 } else {
334 owned
335 }
336 }
337 Self::SystemType(st) => st.as_css_str().to_string(),
338 Self::File(s) => format!("url({})", s.clone().into_library_owned_string()),
339 Self::Ref(s) => format!("font-ref(0x{:x})", s.parsed as usize),
340 }
341 }
342
343 #[must_use] pub fn as_query_string(&self) -> String {
348 match &self {
349 Self::System(s) | Self::File(s) => s.clone().into_library_owned_string(),
350 Self::SystemType(st) => st.as_css_str().to_string(),
351 Self::Ref(s) => format!("font-ref(0x{:x})", s.parsed as usize),
352 }
353 }
354}
355
356impl_vec!(StyleFontFamily, StyleFontFamilyVec, StyleFontFamilyVecDestructor, StyleFontFamilyVecDestructorType, StyleFontFamilyVecSlice, OptionStyleFontFamily);
357impl_vec_clone!(
358 StyleFontFamily,
359 StyleFontFamilyVec,
360 StyleFontFamilyVecDestructor
361);
362impl_vec_debug!(StyleFontFamily, StyleFontFamilyVec);
363impl_vec_eq!(StyleFontFamily, StyleFontFamilyVec);
364impl_vec_ord!(StyleFontFamily, StyleFontFamilyVec);
365impl_vec_hash!(StyleFontFamily, StyleFontFamilyVec);
366impl_vec_partialeq!(StyleFontFamily, StyleFontFamilyVec);
367impl_vec_partialord!(StyleFontFamily, StyleFontFamilyVec);
368
369impl PrintAsCssValue for StyleFontFamilyVec {
370 fn print_as_css_value(&self) -> String {
371 self.iter()
372 .map(StyleFontFamily::as_string)
373 .collect::<Vec<_>>()
374 .join(", ")
375 }
376}
377
378impl FormatAsRustCode for StyleFontFamilyVec {
380 fn format_as_rust_code(&self, _tabs: usize) -> String {
381 format!(
382 "StyleFontFamilyVec::from_const_slice(STYLE_FONT_FAMILY_{}_ITEMS)",
383 self.get_hash()
384 )
385 }
386}
387
388#[derive(Clone, PartialEq, Eq)]
393pub enum CssFontWeightParseError<'a> {
394 InvalidValue(InvalidValueErr<'a>),
395 InvalidNumber(ParseIntError),
396}
397
398impl FormatAsRustCode for StyleFontFamily {
400 fn format_as_rust_code(&self, _tabs: usize) -> String {
401 match self {
402 Self::System(id) => {
403 format!("StyleFontFamily::System(STRING_{})", id.get_hash())
404 }
405 Self::SystemType(st) => {
406 format!("StyleFontFamily::SystemType(SystemFontType::{st:?})")
407 }
408 Self::File(path) => {
409 format!("StyleFontFamily::File(STRING_{})", path.get_hash())
410 }
411 Self::Ref(font_ref) => {
412 format!("StyleFontFamily::Ref({:0x})", font_ref.parsed as usize)
413 }
414 }
415 }
416}
417impl_debug_as_display!(CssFontWeightParseError<'a>);
418impl_display! { CssFontWeightParseError<'a>, {
419 InvalidValue(e) => format!("Invalid font-weight keyword: \"{}\"", e.0),
420 InvalidNumber(e) => format!("Invalid font-weight number: {}", e),
421}}
422impl<'a> From<InvalidValueErr<'a>> for CssFontWeightParseError<'a> {
423 fn from(e: InvalidValueErr<'a>) -> Self {
424 CssFontWeightParseError::InvalidValue(e)
425 }
426}
427impl From<ParseIntError> for CssFontWeightParseError<'_> {
428 fn from(e: ParseIntError) -> Self {
429 CssFontWeightParseError::InvalidNumber(e)
430 }
431}
432#[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq, Eq)]
434#[repr(C, u8)]
435pub enum CssFontWeightParseErrorOwned {
436 InvalidValue(InvalidValueErrOwned),
437 InvalidNumber(crate::props::basic::error::ParseIntError),
438}
439
440impl CssFontWeightParseError<'_> {
441 #[must_use] pub fn to_contained(&self) -> CssFontWeightParseErrorOwned {
442 match self {
443 Self::InvalidValue(e) => CssFontWeightParseErrorOwned::InvalidValue(e.to_contained()),
444 Self::InvalidNumber(e) => CssFontWeightParseErrorOwned::InvalidNumber(e.clone().into()),
445 }
446 }
447}
448
449impl CssFontWeightParseErrorOwned {
450 #[must_use] pub fn to_shared(&self) -> CssFontWeightParseError<'_> {
451 match self {
452 Self::InvalidValue(e) => CssFontWeightParseError::InvalidValue(e.to_shared()),
453 Self::InvalidNumber(e) => CssFontWeightParseError::InvalidNumber(e.to_std()),
454 }
455 }
456}
457
458#[cfg(feature = "parser")]
459pub fn parse_font_weight(
463 input: &str,
464) -> Result<StyleFontWeight, CssFontWeightParseError<'_>> {
465 let input = input.trim();
466 match input {
467 "lighter" => Ok(StyleFontWeight::Lighter),
468 "normal" | "400" => Ok(StyleFontWeight::Normal),
469 "bold" | "700" => Ok(StyleFontWeight::Bold),
470 "bolder" => Ok(StyleFontWeight::Bolder),
471 "100" => Ok(StyleFontWeight::W100),
472 "200" => Ok(StyleFontWeight::W200),
473 "300" => Ok(StyleFontWeight::W300),
474 "500" => Ok(StyleFontWeight::W500),
475 "600" => Ok(StyleFontWeight::W600),
476 "800" => Ok(StyleFontWeight::W800),
477 "900" => Ok(StyleFontWeight::W900),
478 _ => Err(InvalidValueErr(input).into()),
479 }
480}
481
482#[derive(Clone, PartialEq, Eq)]
485pub enum CssFontStyleParseError<'a> {
486 InvalidValue(InvalidValueErr<'a>),
487}
488impl_debug_as_display!(CssFontStyleParseError<'a>);
489impl_display! { CssFontStyleParseError<'a>, {
490 InvalidValue(e) => format!("Invalid font-style: \"{}\"", e.0),
491}}
492impl_from! { InvalidValueErr<'a>, CssFontStyleParseError::InvalidValue }
493
494#[derive(Debug, Clone, PartialEq, Eq)]
495#[repr(C, u8)]
496pub enum CssFontStyleParseErrorOwned {
497 InvalidValue(InvalidValueErrOwned),
498}
499impl CssFontStyleParseError<'_> {
500 #[must_use] pub fn to_contained(&self) -> CssFontStyleParseErrorOwned {
501 match self {
502 Self::InvalidValue(e) => CssFontStyleParseErrorOwned::InvalidValue(e.to_contained()),
503 }
504 }
505}
506impl CssFontStyleParseErrorOwned {
507 #[must_use] pub fn to_shared(&self) -> CssFontStyleParseError<'_> {
508 match self {
509 Self::InvalidValue(e) => CssFontStyleParseError::InvalidValue(e.to_shared()),
510 }
511 }
512}
513
514#[cfg(feature = "parser")]
515pub fn parse_font_style(input: &str) -> Result<StyleFontStyle, CssFontStyleParseError<'_>> {
519 match input.trim() {
520 "normal" => Ok(StyleFontStyle::Normal),
521 "italic" => Ok(StyleFontStyle::Italic),
522 "oblique" => Ok(StyleFontStyle::Oblique),
523 other => Err(InvalidValueErr(other).into()),
524 }
525}
526
527#[derive(Clone, PartialEq, Eq)]
530pub enum CssStyleFontSizeParseError<'a> {
531 PixelValue(CssPixelValueParseError<'a>),
532}
533impl_debug_as_display!(CssStyleFontSizeParseError<'a>);
534impl_display! { CssStyleFontSizeParseError<'a>, {
535 PixelValue(e) => format!("Invalid font-size: {}", e),
536}}
537impl_from! { CssPixelValueParseError<'a>, CssStyleFontSizeParseError::PixelValue }
538
539#[derive(Debug, Clone, PartialEq, Eq)]
540#[repr(C, u8)]
541pub enum CssStyleFontSizeParseErrorOwned {
542 PixelValue(CssPixelValueParseErrorOwned),
543}
544impl CssStyleFontSizeParseError<'_> {
545 #[must_use] pub fn to_contained(&self) -> CssStyleFontSizeParseErrorOwned {
546 match self {
547 Self::PixelValue(e) => CssStyleFontSizeParseErrorOwned::PixelValue(e.to_contained()),
548 }
549 }
550}
551impl CssStyleFontSizeParseErrorOwned {
552 #[must_use] pub fn to_shared(&self) -> CssStyleFontSizeParseError<'_> {
553 match self {
554 Self::PixelValue(e) => CssStyleFontSizeParseError::PixelValue(e.to_shared()),
555 }
556 }
557}
558
559#[cfg(feature = "parser")]
560pub fn parse_style_font_size(
564 input: &str,
565) -> Result<StyleFontSize, CssStyleFontSizeParseError<'_>> {
566 Ok(StyleFontSize {
567 inner: parse_pixel_value(input)?,
568 })
569}
570
571#[derive(PartialEq, Eq, Clone)]
574pub enum CssStyleFontFamilyParseError<'a> {
575 InvalidStyleFontFamily(&'a str),
576 UnclosedQuotes(UnclosedQuotesError<'a>),
577}
578impl_debug_as_display!(CssStyleFontFamilyParseError<'a>);
579impl_display! { CssStyleFontFamilyParseError<'a>, {
580 InvalidStyleFontFamily(val) => format!("Invalid font-family: \"{}\"", val),
581 UnclosedQuotes(val) => format!("Unclosed quotes in font-family: \"{}\"", val.0),
582}}
583impl<'a> From<UnclosedQuotesError<'a>> for CssStyleFontFamilyParseError<'a> {
584 fn from(err: UnclosedQuotesError<'a>) -> Self {
585 CssStyleFontFamilyParseError::UnclosedQuotes(err)
586 }
587}
588
589#[derive(Debug, Clone, PartialEq, Eq)]
590#[repr(C, u8)]
591pub enum CssStyleFontFamilyParseErrorOwned {
592 InvalidStyleFontFamily(AzString),
593 UnclosedQuotes(AzString),
594}
595impl CssStyleFontFamilyParseError<'_> {
596 #[must_use] pub fn to_contained(&self) -> CssStyleFontFamilyParseErrorOwned {
597 match self {
598 CssStyleFontFamilyParseError::InvalidStyleFontFamily(s) => {
599 CssStyleFontFamilyParseErrorOwned::InvalidStyleFontFamily((*s).to_string().into())
600 }
601 CssStyleFontFamilyParseError::UnclosedQuotes(e) => {
602 CssStyleFontFamilyParseErrorOwned::UnclosedQuotes(e.0.to_string().into())
603 }
604 }
605 }
606}
607impl CssStyleFontFamilyParseErrorOwned {
608 #[must_use] pub fn to_shared(&self) -> CssStyleFontFamilyParseError<'_> {
609 match self {
610 Self::InvalidStyleFontFamily(s) => {
611 CssStyleFontFamilyParseError::InvalidStyleFontFamily(s)
612 }
613 Self::UnclosedQuotes(s) => {
614 CssStyleFontFamilyParseError::UnclosedQuotes(UnclosedQuotesError(s))
615 }
616 }
617 }
618}
619
620#[cfg(feature = "parser")]
621pub fn parse_style_font_family(
625 input: &str,
626) -> Result<StyleFontFamilyVec, CssStyleFontFamilyParseError<'_>> {
627 let multiple_fonts = input.split(',');
628 let mut fonts = Vec::with_capacity(1);
629
630 for font in multiple_fonts {
631 let font = font.trim();
632
633 if font.starts_with("system:") {
635 if let Some(system_type) = SystemFontType::from_css_str(font) {
636 fonts.push(StyleFontFamily::SystemType(system_type));
637 continue;
638 }
639 }
641
642 if let Ok(stripped) = strip_quotes(font) {
643 fonts.push(StyleFontFamily::System(stripped.0.to_string().into()));
644 } else {
645 fonts.push(StyleFontFamily::System(font.to_string().into()));
647 }
648 }
649
650 Ok(fonts.into())
651}
652
653use crate::corety::{OptionI16, OptionU16, OptionU32};
656
657#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
660#[repr(C)]
661#[derive(Default)]
662pub struct Panose {
663 pub family_type: u8,
664 pub serif_style: u8,
665 pub weight: u8,
666 pub proportion: u8,
667 pub contrast: u8,
668 pub stroke_variation: u8,
669 pub arm_style: u8,
670 pub letterform: u8,
671 pub midline: u8,
672 pub x_height: u8,
673}
674
675
676impl Panose {
677 #[must_use] pub const fn zero() -> Self {
678 Self {
679 family_type: 0,
680 serif_style: 0,
681 weight: 0,
682 proportion: 0,
683 contrast: 0,
684 stroke_variation: 0,
685 arm_style: 0,
686 letterform: 0,
687 midline: 0,
688 x_height: 0,
689 }
690 }
691}
692
693#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
696#[repr(C)]
697pub struct FontMetrics {
698 pub ul_code_page_range1: OptionU32,
700 pub ul_code_page_range2: OptionU32,
701
702 pub ul_unicode_range1: u32,
704 pub ul_unicode_range2: u32,
705 pub ul_unicode_range3: u32,
706 pub ul_unicode_range4: u32,
707 pub ach_vend_id: u32,
708
709 pub s_typo_ascender: OptionI16,
711 pub s_typo_descender: OptionI16,
712 pub s_typo_line_gap: OptionI16,
713 pub us_win_ascent: OptionU16,
714 pub us_win_descent: OptionU16,
715
716 pub sx_height: OptionI16,
719 pub s_cap_height: OptionI16,
720 pub us_default_char: OptionU16,
721 pub us_break_char: OptionU16,
722 pub us_max_context: OptionU16,
723
724 pub us_lower_optical_point_size: OptionU16,
726 pub us_upper_optical_point_size: OptionU16,
727
728 pub units_per_em: u16,
730 pub font_flags: u16,
731 pub x_min: i16,
732 pub y_min: i16,
733 pub x_max: i16,
734 pub y_max: i16,
735
736 pub ascender: i16,
738 pub descender: i16,
739 pub line_gap: i16,
740 pub advance_width_max: u16,
741 pub min_left_side_bearing: i16,
742 pub min_right_side_bearing: i16,
743 pub x_max_extent: i16,
744 pub caret_slope_rise: i16,
745 pub caret_slope_run: i16,
746 pub caret_offset: i16,
747 pub num_h_metrics: u16,
748
749 pub x_avg_char_width: i16,
751 pub us_weight_class: u16,
752 pub us_width_class: u16,
753 pub fs_type: u16,
754 pub y_subscript_x_size: i16,
755 pub y_subscript_y_size: i16,
756 pub y_subscript_x_offset: i16,
757 pub y_subscript_y_offset: i16,
758 pub y_superscript_x_size: i16,
759 pub y_superscript_y_size: i16,
760 pub y_superscript_x_offset: i16,
761 pub y_superscript_y_offset: i16,
762 pub y_strikeout_size: i16,
763 pub y_strikeout_position: i16,
764 pub s_family_class: i16,
765 pub fs_selection: u16,
766 pub us_first_char_index: u16,
767 pub us_last_char_index: u16,
768
769 pub panose: Panose,
771}
772
773impl Default for FontMetrics {
774 fn default() -> Self {
775 Self::zero()
776 }
777}
778
779impl FontMetrics {
780 #[must_use] pub const fn zero() -> Self {
783 Self {
784 ul_code_page_range1: OptionU32::None,
785 ul_code_page_range2: OptionU32::None,
786 ul_unicode_range1: 0,
787 ul_unicode_range2: 0,
788 ul_unicode_range3: 0,
789 ul_unicode_range4: 0,
790 ach_vend_id: 0,
791 s_typo_ascender: OptionI16::None,
792 s_typo_descender: OptionI16::None,
793 s_typo_line_gap: OptionI16::None,
794 us_win_ascent: OptionU16::None,
795 us_win_descent: OptionU16::None,
796 sx_height: OptionI16::None,
797 s_cap_height: OptionI16::None,
798 us_default_char: OptionU16::None,
799 us_break_char: OptionU16::None,
800 us_max_context: OptionU16::None,
801 us_lower_optical_point_size: OptionU16::None,
802 us_upper_optical_point_size: OptionU16::None,
803 units_per_em: 1000,
804 font_flags: 0,
805 x_min: 0,
806 y_min: 0,
807 x_max: 0,
808 y_max: 0,
809 ascender: 0,
810 descender: 0,
811 line_gap: 0,
812 advance_width_max: 0,
813 min_left_side_bearing: 0,
814 min_right_side_bearing: 0,
815 x_max_extent: 0,
816 caret_slope_rise: 0,
817 caret_slope_run: 0,
818 caret_offset: 0,
819 num_h_metrics: 0,
820 x_avg_char_width: 0,
821 us_weight_class: 400,
822 us_width_class: 5,
823 fs_type: 0,
824 y_subscript_x_size: 0,
825 y_subscript_y_size: 0,
826 y_subscript_x_offset: 0,
827 y_subscript_y_offset: 0,
828 y_superscript_x_size: 0,
829 y_superscript_y_size: 0,
830 y_superscript_x_offset: 0,
831 y_superscript_y_offset: 0,
832 y_strikeout_size: 0,
833 y_strikeout_position: 0,
834 s_family_class: 0,
835 fs_selection: 0,
836 us_first_char_index: 0,
837 us_last_char_index: 0,
838 panose: Panose::zero(),
839 }
840 }
841
842 #[must_use] pub const fn get_ascender(&self) -> i16 {
844 self.ascender
845 }
846
847 #[must_use] pub const fn get_descender(&self) -> i16 {
849 self.descender
850 }
851
852 #[must_use] pub const fn get_line_gap(&self) -> i16 {
854 self.line_gap
855 }
856
857 #[must_use] pub const fn get_advance_width_max(&self) -> u16 {
859 self.advance_width_max
860 }
861
862 #[must_use] pub const fn get_min_left_side_bearing(&self) -> i16 {
864 self.min_left_side_bearing
865 }
866
867 #[must_use] pub const fn get_min_right_side_bearing(&self) -> i16 {
869 self.min_right_side_bearing
870 }
871
872 #[must_use] pub const fn get_x_min(&self) -> i16 {
874 self.x_min
875 }
876
877 #[must_use] pub const fn get_y_min(&self) -> i16 {
879 self.y_min
880 }
881
882 #[must_use] pub const fn get_x_max(&self) -> i16 {
884 self.x_max
885 }
886
887 #[must_use] pub const fn get_y_max(&self) -> i16 {
889 self.y_max
890 }
891
892 #[must_use] pub const fn get_x_max_extent(&self) -> i16 {
894 self.x_max_extent
895 }
896
897 #[must_use] pub const fn get_x_avg_char_width(&self) -> i16 {
899 self.x_avg_char_width
900 }
901
902 #[must_use] pub const fn get_y_subscript_x_size(&self) -> i16 {
904 self.y_subscript_x_size
905 }
906
907 #[must_use] pub const fn get_y_subscript_y_size(&self) -> i16 {
909 self.y_subscript_y_size
910 }
911
912 #[must_use] pub const fn get_y_subscript_x_offset(&self) -> i16 {
914 self.y_subscript_x_offset
915 }
916
917 #[must_use] pub const fn get_y_subscript_y_offset(&self) -> i16 {
919 self.y_subscript_y_offset
920 }
921
922 #[must_use] pub const fn get_y_superscript_x_size(&self) -> i16 {
924 self.y_superscript_x_size
925 }
926
927 #[must_use] pub const fn get_y_superscript_y_size(&self) -> i16 {
929 self.y_superscript_y_size
930 }
931
932 #[must_use] pub const fn get_y_superscript_x_offset(&self) -> i16 {
934 self.y_superscript_x_offset
935 }
936
937 #[must_use] pub const fn get_y_superscript_y_offset(&self) -> i16 {
939 self.y_superscript_y_offset
940 }
941
942 #[must_use] pub const fn get_y_strikeout_size(&self) -> i16 {
944 self.y_strikeout_size
945 }
946
947 #[must_use] pub const fn get_y_strikeout_position(&self) -> i16 {
949 self.y_strikeout_position
950 }
951
952 #[must_use] pub const fn use_typo_metrics(&self) -> bool {
954 (self.fs_selection & 0x0080) != 0
956 }
957}
958
959#[cfg(all(test, feature = "parser"))]
960mod tests {
961 use super::*;
962
963 #[test]
964 fn test_parse_font_weight_keywords() {
965 assert_eq!(
966 parse_font_weight("normal").unwrap(),
967 StyleFontWeight::Normal
968 );
969 assert_eq!(parse_font_weight("bold").unwrap(), StyleFontWeight::Bold);
970 assert_eq!(
971 parse_font_weight("lighter").unwrap(),
972 StyleFontWeight::Lighter
973 );
974 assert_eq!(
975 parse_font_weight("bolder").unwrap(),
976 StyleFontWeight::Bolder
977 );
978 }
979
980 #[test]
981 fn test_parse_font_weight_numbers() {
982 assert_eq!(parse_font_weight("100").unwrap(), StyleFontWeight::W100);
983 assert_eq!(parse_font_weight("400").unwrap(), StyleFontWeight::Normal);
984 assert_eq!(parse_font_weight("700").unwrap(), StyleFontWeight::Bold);
985 assert_eq!(parse_font_weight("900").unwrap(), StyleFontWeight::W900);
986 }
987
988 #[test]
989 fn test_parse_font_weight_invalid() {
990 assert!(parse_font_weight("thin").is_err());
991 assert!(parse_font_weight("").is_err());
992 assert!(parse_font_weight("450").is_err());
993 assert!(parse_font_weight("boldest").is_err());
994 }
995
996 #[test]
997 fn test_parse_font_style() {
998 assert_eq!(parse_font_style("normal").unwrap(), StyleFontStyle::Normal);
999 assert_eq!(parse_font_style("italic").unwrap(), StyleFontStyle::Italic);
1000 assert_eq!(
1001 parse_font_style("oblique").unwrap(),
1002 StyleFontStyle::Oblique
1003 );
1004 assert_eq!(
1005 parse_font_style(" italic ").unwrap(),
1006 StyleFontStyle::Italic
1007 );
1008 assert!(parse_font_style("slanted").is_err());
1009 }
1010
1011 #[test]
1012 fn test_parse_font_size() {
1013 assert_eq!(
1014 parse_style_font_size("16px").unwrap().inner,
1015 PixelValue::px(16.0)
1016 );
1017 assert_eq!(
1018 parse_style_font_size("1.2em").unwrap().inner,
1019 PixelValue::em(1.2)
1020 );
1021 assert_eq!(
1022 parse_style_font_size("12pt").unwrap().inner,
1023 PixelValue::pt(12.0)
1024 );
1025 assert_eq!(
1026 parse_style_font_size("120%").unwrap().inner,
1027 PixelValue::percent(120.0)
1028 );
1029 assert!(parse_style_font_size("medium").is_err());
1030 }
1031
1032 #[test]
1033 fn test_parse_font_family() {
1034 let result = parse_style_font_family("Arial").unwrap();
1036 assert_eq!(result.len(), 1);
1037 assert_eq!(
1038 result.as_slice()[0],
1039 StyleFontFamily::System("Arial".into())
1040 );
1041
1042 let result = parse_style_font_family("\"Times New Roman\"").unwrap();
1044 assert_eq!(result.len(), 1);
1045 assert_eq!(
1046 result.as_slice()[0],
1047 StyleFontFamily::System("Times New Roman".into())
1048 );
1049
1050 let result = parse_style_font_family("Georgia, serif").unwrap();
1052 assert_eq!(result.len(), 2);
1053 assert_eq!(
1054 result.as_slice()[0],
1055 StyleFontFamily::System("Georgia".into())
1056 );
1057 assert_eq!(
1058 result.as_slice()[1],
1059 StyleFontFamily::System("serif".into())
1060 );
1061
1062 let result = parse_style_font_family(" 'Courier New' , monospace ").unwrap();
1064 assert_eq!(result.len(), 2);
1065 assert_eq!(
1066 result.as_slice()[0],
1067 StyleFontFamily::System("Courier New".into())
1068 );
1069 assert_eq!(
1070 result.as_slice()[1],
1071 StyleFontFamily::System("monospace".into())
1072 );
1073 }
1074
1075 #[test]
1076 fn test_parse_system_font_type() {
1077 use crate::system::SystemFontType;
1078
1079 let result = parse_style_font_family("system:ui").unwrap();
1081 assert_eq!(result.len(), 1);
1082 assert_eq!(result.as_slice()[0], StyleFontFamily::SystemType(SystemFontType::Ui));
1083
1084 let result = parse_style_font_family("system:monospace:bold").unwrap();
1086 assert_eq!(result.len(), 1);
1087 assert_eq!(result.as_slice()[0], StyleFontFamily::SystemType(SystemFontType::MonospaceBold));
1088
1089 let result = parse_style_font_family("system:monospace:italic").unwrap();
1091 assert_eq!(result.len(), 1);
1092 assert_eq!(result.as_slice()[0], StyleFontFamily::SystemType(SystemFontType::MonospaceItalic));
1093
1094 let result = parse_style_font_family("system:ui, Arial, sans-serif").unwrap();
1096 assert_eq!(result.len(), 3);
1097 assert_eq!(result.as_slice()[0], StyleFontFamily::SystemType(SystemFontType::Ui));
1098 assert_eq!(result.as_slice()[1], StyleFontFamily::System("Arial".into()));
1099 assert_eq!(result.as_slice()[2], StyleFontFamily::System("sans-serif".into()));
1100
1101 assert!(parse_style_font_family("system:ui").is_ok());
1103 assert!(parse_style_font_family("system:ui:bold").is_ok());
1104 assert!(parse_style_font_family("system:monospace").is_ok());
1105 assert!(parse_style_font_family("system:monospace:bold").is_ok());
1106 assert!(parse_style_font_family("system:monospace:italic").is_ok());
1107 assert!(parse_style_font_family("system:title").is_ok());
1108 assert!(parse_style_font_family("system:title:bold").is_ok());
1109 assert!(parse_style_font_family("system:menu").is_ok());
1110 assert!(parse_style_font_family("system:small").is_ok());
1111 assert!(parse_style_font_family("system:serif").is_ok());
1112 assert!(parse_style_font_family("system:serif:bold").is_ok());
1113
1114 let result = parse_style_font_family("system:invalid").unwrap();
1116 assert_eq!(result.len(), 1);
1117 assert_eq!(result.as_slice()[0], StyleFontFamily::System("system:invalid".into()));
1118 }
1119
1120 #[test]
1121 fn test_system_font_type_css_roundtrip() {
1122 use crate::system::SystemFontType;
1123
1124 let types = [
1126 SystemFontType::Ui,
1127 SystemFontType::UiBold,
1128 SystemFontType::Monospace,
1129 SystemFontType::MonospaceBold,
1130 SystemFontType::MonospaceItalic,
1131 SystemFontType::Title,
1132 SystemFontType::TitleBold,
1133 SystemFontType::Menu,
1134 SystemFontType::Small,
1135 SystemFontType::Serif,
1136 SystemFontType::SerifBold,
1137 ];
1138
1139 for ft in &types {
1140 let css = ft.as_css_str();
1141 let parsed = SystemFontType::from_css_str(css).unwrap();
1142 assert_eq!(*ft, parsed, "Roundtrip failed for {ft:?}");
1143 }
1144 }
1145}
1146
1147#[cfg(test)]
1148#[allow(clippy::too_many_lines, clippy::float_cmp)]
1149mod autotest_generated {
1150 use std::collections::hash_map::DefaultHasher;
1151
1152 use super::*;
1153 use crate::props::basic::{
1154 error::ParseIntError as CParseIntError, length::SizeMetric,
1155 };
1156
1157 fn hash_of<T: Hash>(value: &T) -> u64 {
1158 let mut hasher = DefaultHasher::new();
1159 value.hash(&mut hasher);
1160 hasher.finish()
1161 }
1162
1163 fn boxed_font_data(value: u64) -> *const c_void {
1165 Box::into_raw(Box::new(value)).cast::<c_void>().cast_const()
1166 }
1167
1168 extern "C" fn noop_destructor(_ptr: *mut c_void) {}
1169
1170 static SINGLE_DTOR_CALLS: AtomicUsize = AtomicUsize::new(0);
1173 extern "C" fn single_counting_destructor(ptr: *mut c_void) {
1174 SINGLE_DTOR_CALLS.fetch_add(1, AtomicOrdering::SeqCst);
1175 if !ptr.is_null() {
1176 unsafe { drop(Box::from_raw(ptr.cast::<u64>())) };
1177 }
1178 }
1179
1180 static CLONE_DTOR_CALLS: AtomicUsize = AtomicUsize::new(0);
1181 extern "C" fn clone_counting_destructor(ptr: *mut c_void) {
1182 CLONE_DTOR_CALLS.fetch_add(1, AtomicOrdering::SeqCst);
1183 if !ptr.is_null() {
1184 unsafe { drop(Box::from_raw(ptr.cast::<u64>())) };
1185 }
1186 }
1187
1188 static MANY_DTOR_CALLS: AtomicUsize = AtomicUsize::new(0);
1189 extern "C" fn many_counting_destructor(ptr: *mut c_void) {
1190 MANY_DTOR_CALLS.fetch_add(1, AtomicOrdering::SeqCst);
1191 if !ptr.is_null() {
1192 unsafe { drop(Box::from_raw(ptr.cast::<u64>())) };
1193 }
1194 }
1195
1196 #[test]
1201 fn next_font_ref_id_is_monotonic_and_never_zero() {
1202 let a = next_font_ref_id();
1203 let b = next_font_ref_id();
1204 assert!(a >= 1, "id 0 is reserved as the null-handle sentinel");
1207 assert!(b > a, "ids must be strictly increasing ({a} -> {b})");
1208 }
1209
1210 #[test]
1215 fn font_ref_new_post_construction_invariants() {
1216 let ptr = boxed_font_data(0xDEAD);
1217 let font = FontRef::new(ptr, single_counting_destructor);
1218
1219 assert_eq!(font.get_parsed(), ptr, "get_parsed must return the pointer passed to new()");
1220 assert!(font.run_destructor);
1221 assert!(font.id >= 1);
1222 assert!(!font.copies.is_null());
1223 assert_eq!(unsafe { (*font.copies).load(AtomicOrdering::SeqCst) }, 1);
1224
1225 assert_eq!(SINGLE_DTOR_CALLS.load(AtomicOrdering::SeqCst), 0);
1226 drop(font);
1227 assert_eq!(
1228 SINGLE_DTOR_CALLS.load(AtomicOrdering::SeqCst),
1229 1,
1230 "the destructor must run exactly once when the last handle drops"
1231 );
1232 }
1233
1234 #[test]
1235 fn font_ref_new_accepts_null_pointer_without_panicking() {
1236 let font = FontRef::new(core::ptr::null(), noop_destructor);
1237 assert!(font.get_parsed().is_null());
1238 assert!(font.id >= 1);
1239 let dbg = format!("{font:?}");
1241 assert!(dbg.starts_with("FontRef(0x0"), "unexpected Debug output: {dbg}");
1242 assert!(dbg.contains("copies: 1"), "unexpected Debug output: {dbg}");
1243 }
1244
1245 #[test]
1246 fn font_ref_clone_shares_identity_and_defers_the_destructor() {
1247 let ptr = boxed_font_data(42);
1248 let original = FontRef::new(ptr, clone_counting_destructor);
1249 let copy = original.clone();
1250
1251 assert_eq!(original, copy, "shallow clones are the same font");
1252 assert_eq!(original.id, copy.id);
1253 assert_eq!(hash_of(&original), hash_of(©));
1254 assert_eq!(original.cmp(©), Ordering::Equal);
1255 assert_eq!(original.get_parsed(), copy.get_parsed());
1256 assert_eq!(unsafe { (*original.copies).load(AtomicOrdering::SeqCst) }, 2);
1257
1258 drop(copy);
1259 assert_eq!(
1260 CLONE_DTOR_CALLS.load(AtomicOrdering::SeqCst),
1261 0,
1262 "dropping one of two handles must not free the parsed data"
1263 );
1264 drop(original);
1265 assert_eq!(CLONE_DTOR_CALLS.load(AtomicOrdering::SeqCst), 1);
1266 }
1267
1268 #[test]
1269 fn font_ref_many_clones_run_the_destructor_exactly_once() {
1270 let ptr = boxed_font_data(7);
1271 let original = FontRef::new(ptr, many_counting_destructor);
1272 let clones: Vec<FontRef> = (0..1000).map(|_| original.clone()).collect();
1273
1274 assert_eq!(unsafe { (*original.copies).load(AtomicOrdering::SeqCst) }, 1001);
1275 assert!(clones.iter().all(|c| *c == original));
1276
1277 drop(clones);
1278 assert_eq!(MANY_DTOR_CALLS.load(AtomicOrdering::SeqCst), 0);
1279 drop(original);
1280 assert_eq!(MANY_DTOR_CALLS.load(AtomicOrdering::SeqCst), 1);
1281 }
1282
1283 #[test]
1284 fn font_ref_identity_is_the_id_not_the_pointer() {
1285 let a = FontRef::new(core::ptr::null(), noop_destructor);
1289 let b = FontRef::new(core::ptr::null(), noop_destructor);
1290
1291 assert_eq!(a.get_parsed(), b.get_parsed(), "same (null) pointer");
1292 assert_ne!(a, b, "same pointer must not forge identity");
1293 assert_ne!(a.id, b.id);
1294 assert_ne!(hash_of(&a), hash_of(&b));
1295 assert_eq!(a.cmp(&b), Ordering::Less, "ids are handed out in increasing order");
1296 assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
1297 }
1298
1299 #[test]
1300 fn font_ref_raw_zero_handle_is_drop_safe() {
1301 let make = || FontRef {
1304 parsed: core::ptr::null(),
1305 copies: core::ptr::null(),
1306 id: 0,
1307 run_destructor: false,
1308 parsed_destructor: noop_destructor,
1309 };
1310 let raw = make();
1311 let raw2 = make();
1312
1313 assert!(raw.get_parsed().is_null());
1314 assert_eq!(format!("{raw:?}"), "FontRef(0x0)");
1315 assert_eq!(raw, raw2, "both carry the id==0 sentinel");
1316
1317 let cloned = raw.clone();
1318 assert_eq!(cloned.id, 0);
1319 assert!(cloned.copies.is_null(), "cloning must not allocate a refcount for a raw handle");
1320
1321 drop(cloned);
1322 drop(raw2);
1323 drop(raw); }
1325
1326 #[test]
1331 fn style_font_family_as_string_quotes_only_when_whitespace_is_present() {
1332 assert_eq!(StyleFontFamily::System("Arial".into()).as_string(), "Arial");
1333 assert_eq!(
1334 StyleFontFamily::System("Times New Roman".into()).as_string(),
1335 "\"Times New Roman\""
1336 );
1337 assert_eq!(StyleFontFamily::System("".into()).as_string(), "");
1339 assert_eq!(StyleFontFamily::System("a\tb".into()).as_string(), "\"a\tb\"");
1341 assert_eq!(StyleFontFamily::System("a\nb".into()).as_string(), "\"a\nb\"");
1342 }
1343
1344 #[test]
1345 fn style_font_family_as_string_handles_unicode() {
1346 assert_eq!(StyleFontFamily::System("日本語".into()).as_string(), "日本語");
1348 assert_eq!(StyleFontFamily::System("\u{1F600}".into()).as_string(), "\u{1F600}");
1349 assert_eq!(StyleFontFamily::System("e\u{0301}".into()).as_string(), "e\u{0301}");
1351 assert_eq!(
1353 StyleFontFamily::System("a\u{00A0}b".into()).as_string(),
1354 "\"a\u{00A0}b\""
1355 );
1356 }
1357
1358 #[test]
1359 fn style_font_family_as_string_file_and_systemtype_and_ref() {
1360 assert_eq!(
1362 StyleFontFamily::File("my font.ttf".into()).as_string(),
1363 "url(my font.ttf)"
1364 );
1365 assert_eq!(
1366 StyleFontFamily::SystemType(SystemFontType::MonospaceBold).as_string(),
1367 "system:monospace:bold"
1368 );
1369
1370 let ptr = 0xdead_beef_usize as *const c_void;
1371 let fam = StyleFontFamily::Ref(FontRef::new(ptr, noop_destructor));
1372 assert_eq!(fam.as_string(), "font-ref(0xdeadbeef)");
1373 }
1374
1375 #[test]
1376 fn style_font_family_as_string_on_huge_name_does_not_panic() {
1377 let huge = "x".repeat(1_000_000);
1378 let fam = StyleFontFamily::System(huge.as_str().into());
1379 assert_eq!(fam.as_string().len(), 1_000_000);
1380 }
1381
1382 #[cfg(feature = "parser")]
1387 #[test]
1388 fn parse_font_weight_valid_minimal_and_full_roundtrip() {
1389 assert_eq!(parse_font_weight("normal").unwrap(), StyleFontWeight::Normal);
1390
1391 for weight in [
1392 StyleFontWeight::Lighter,
1393 StyleFontWeight::W100,
1394 StyleFontWeight::W200,
1395 StyleFontWeight::W300,
1396 StyleFontWeight::Normal,
1397 StyleFontWeight::W500,
1398 StyleFontWeight::W600,
1399 StyleFontWeight::Bold,
1400 StyleFontWeight::W800,
1401 StyleFontWeight::W900,
1402 StyleFontWeight::Bolder,
1403 ] {
1404 let css = weight.print_as_css_value();
1405 assert_eq!(
1406 parse_font_weight(&css).unwrap(),
1407 weight,
1408 "encode==decode failed for {weight:?} (printed as {css:?})"
1409 );
1410 }
1411 }
1412
1413 #[cfg(feature = "parser")]
1414 #[test]
1415 fn parse_font_weight_numeric_aliases_collapse_onto_keywords() {
1416 assert_eq!(parse_font_weight("400").unwrap(), StyleFontWeight::Normal);
1419 assert_eq!(parse_font_weight("700").unwrap(), StyleFontWeight::Bold);
1420 assert_eq!(StyleFontWeight::Normal.print_as_css_value(), "normal");
1421 assert_eq!(StyleFontWeight::Bold.print_as_css_value(), "bold");
1422 }
1423
1424 #[cfg(feature = "parser")]
1425 #[test]
1426 fn parse_font_weight_rejects_empty_and_whitespace_only() {
1427 for input in ["", " ", " ", "\t\n", "\r\n\t "] {
1428 let err = parse_font_weight(input).unwrap_err();
1429 assert!(
1430 matches!(err, CssFontWeightParseError::InvalidValue(InvalidValueErr(""))),
1431 "expected trimmed InvalidValue(\"\") for {input:?}, got {err:?}"
1432 );
1433 }
1434 }
1435
1436 #[cfg(feature = "parser")]
1437 #[test]
1438 fn parse_font_weight_rejects_garbage_and_reports_the_trimmed_input() {
1439 assert_eq!(
1440 parse_font_weight(" thin ").unwrap_err(),
1441 CssFontWeightParseError::InvalidValue(InvalidValueErr("thin")),
1442 "the error must carry the trimmed input"
1443 );
1444 for input in ["thin", "boldest", "bold;garbage", "normal!", "\u{0}\u{1}\u{7f}", "-"] {
1445 assert!(parse_font_weight(input).is_err(), "{input:?} must not parse");
1446 }
1447 }
1448
1449 #[cfg(feature = "parser")]
1450 #[test]
1451 fn parse_font_weight_rejects_boundary_numbers_without_ever_yielding_invalidnumber() {
1452 for input in [
1455 "0",
1456 "-0",
1457 "450",
1458 "1000",
1459 "0400",
1460 "+400",
1461 "400.0",
1462 "4e2",
1463 "9223372036854775807",
1464 "-9223372036854775808",
1465 "18446744073709551616",
1466 "NaN",
1467 "inf",
1468 "-inf",
1469 "1e309",
1470 ] {
1471 let err = parse_font_weight(input).unwrap_err();
1472 assert!(
1473 matches!(err, CssFontWeightParseError::InvalidValue(_)),
1474 "{input:?} should be an InvalidValue, got {err:?}"
1475 );
1476 }
1477 }
1478
1479 #[cfg(feature = "parser")]
1480 #[test]
1481 fn parse_font_weight_trims_unicode_whitespace() {
1482 assert_eq!(
1485 parse_font_weight("\u{00A0}bold\u{00A0}").unwrap(),
1486 StyleFontWeight::Bold
1487 );
1488 assert_eq!(parse_font_weight("\u{2028}400").unwrap(), StyleFontWeight::Normal);
1489 }
1490
1491 #[cfg(feature = "parser")]
1492 #[test]
1493 fn parse_font_weight_unicode_garbage_is_rejected_and_displayable() {
1494 let err = parse_font_weight("\u{1F600}").unwrap_err();
1495 assert_eq!(
1496 err,
1497 CssFontWeightParseError::InvalidValue(InvalidValueErr("\u{1F600}"))
1498 );
1499 let msg = format!("{err}");
1501 assert!(msg.contains('\u{1F600}'), "unexpected message: {msg}");
1502 assert!(!format!("{err:?}").is_empty());
1503
1504 assert!(parse_font_weight("bold\u{0301}").is_err(), "combining mark must not be trimmed");
1505 }
1506
1507 #[cfg(feature = "parser")]
1508 #[test]
1509 fn parse_font_weight_survives_extremely_long_and_deeply_nested_input() {
1510 let long = "bold".repeat(250_000); assert!(parse_font_weight(&long).is_err());
1512
1513 let nested = "(".repeat(10_000);
1514 assert!(parse_font_weight(&nested).is_err());
1515
1516 let long_digits = "9".repeat(100_000);
1517 assert!(parse_font_weight(&long_digits).is_err());
1518 }
1519
1520 #[cfg(feature = "parser")]
1525 #[test]
1526 fn parse_font_style_valid_minimal_and_full_roundtrip() {
1527 assert_eq!(parse_font_style("normal").unwrap(), StyleFontStyle::Normal);
1528 for style in [
1529 StyleFontStyle::Normal,
1530 StyleFontStyle::Italic,
1531 StyleFontStyle::Oblique,
1532 ] {
1533 let css = style.print_as_css_value();
1534 assert_eq!(parse_font_style(&css).unwrap(), style, "encode==decode failed for {style:?}");
1535 }
1536 }
1537
1538 #[cfg(feature = "parser")]
1539 #[test]
1540 fn parse_font_style_rejects_empty_whitespace_and_garbage() {
1541 for input in ["", " ", "\t\n"] {
1542 assert_eq!(
1543 parse_font_style(input).unwrap_err(),
1544 CssFontStyleParseError::InvalidValue(InvalidValueErr(""))
1545 );
1546 }
1547 for input in [
1548 "slanted",
1549 "italics",
1550 "ITALIC",
1551 "italic;garbage",
1552 "oblique 14deg",
1553 "0",
1554 "-0",
1555 "NaN",
1556 "inf",
1557 "9223372036854775807",
1558 ] {
1559 assert!(parse_font_style(input).is_err(), "{input:?} must not parse");
1560 }
1561 }
1562
1563 #[cfg(feature = "parser")]
1564 #[test]
1565 fn parse_font_style_leading_trailing_junk_and_unicode() {
1566 assert_eq!(parse_font_style(" italic ").unwrap(), StyleFontStyle::Italic);
1567 assert_eq!(
1568 parse_font_style(" italic;").unwrap_err(),
1569 CssFontStyleParseError::InvalidValue(InvalidValueErr("italic;"))
1570 );
1571 let err = parse_font_style("\u{1F600}\u{0301}").unwrap_err();
1572 assert!(!format!("{err}").is_empty());
1573 }
1574
1575 #[cfg(feature = "parser")]
1576 #[test]
1577 fn parse_font_style_survives_extremely_long_and_deeply_nested_input() {
1578 let long = "italic".repeat(200_000); assert!(parse_font_style(&long).is_err());
1580 let nested = "[".repeat(10_000);
1581 assert!(parse_font_style(&nested).is_err());
1582 }
1583
1584 #[cfg(feature = "parser")]
1589 #[test]
1590 fn parse_style_font_size_valid_minimal_and_metric_roundtrip() {
1591 assert_eq!(parse_style_font_size("16px").unwrap().inner, PixelValue::px(16.0));
1592
1593 for metric in [
1596 SizeMetric::Px,
1597 SizeMetric::Pt,
1598 SizeMetric::Em,
1599 SizeMetric::Rem,
1600 SizeMetric::In,
1601 SizeMetric::Cm,
1602 SizeMetric::Mm,
1603 SizeMetric::Percent,
1604 SizeMetric::Vw,
1605 SizeMetric::Vh,
1606 SizeMetric::Vmax,
1607 ] {
1608 let size = StyleFontSize {
1609 inner: PixelValue::from_metric(metric, 12.0),
1610 };
1611 let css = size.print_as_css_value();
1612 assert_eq!(
1613 parse_style_font_size(&css).unwrap(),
1614 size,
1615 "encode==decode failed for {metric:?} (printed as {css:?})"
1616 );
1617 }
1618
1619 let default = StyleFontSize::default();
1621 assert_eq!(default.print_as_css_value(), "12pt");
1622 assert_eq!(parse_style_font_size("12pt").unwrap(), default);
1623 }
1624
1625 #[cfg(feature = "parser")]
1626 #[test]
1627 fn parse_style_font_size_vmin_is_shadowed_by_the_in_suffix() {
1628 let size = StyleFontSize {
1633 inner: PixelValue::from_metric(SizeMetric::Vmin, 12.0),
1634 };
1635 let css = size.print_as_css_value();
1636 assert_eq!(css, "12vmin");
1637
1638 assert_eq!(
1639 parse_style_font_size(&css).unwrap().inner,
1640 PixelValue::from_metric(SizeMetric::Vmin, 12.0)
1641 );
1642 }
1643
1644 #[cfg(feature = "parser")]
1645 #[test]
1646 fn parse_style_font_size_rejects_empty_and_whitespace_only() {
1647 for input in ["", " ", " ", "\t\n"] {
1648 let err = parse_style_font_size(input).unwrap_err();
1649 assert_eq!(
1650 err,
1651 CssStyleFontSizeParseError::PixelValue(CssPixelValueParseError::EmptyString),
1652 "unexpected error for {input:?}"
1653 );
1654 }
1655 }
1656
1657 #[cfg(feature = "parser")]
1658 #[test]
1659 fn parse_style_font_size_rejects_garbage_and_bare_units() {
1660 let err = parse_style_font_size("px").unwrap_err();
1661 assert!(
1662 matches!(
1663 err,
1664 CssStyleFontSizeParseError::PixelValue(CssPixelValueParseError::NoValueGiven(
1665 "px",
1666 SizeMetric::Px
1667 ))
1668 ),
1669 "expected NoValueGiven, got {err:?}"
1670 );
1671
1672 for input in [
1673 "medium",
1674 "larger",
1675 "16PX", "16px;junk",
1677 "16 px junk",
1678 "\u{1F600}",
1679 "--",
1680 "px16",
1681 ] {
1682 assert!(parse_style_font_size(input).is_err(), "{input:?} must not parse");
1683 }
1684 }
1685
1686 #[cfg(feature = "parser")]
1687 #[test]
1688 fn parse_style_font_size_accepts_unitless_numbers_as_px() {
1689 assert_eq!(parse_style_font_size("0").unwrap().inner, PixelValue::px(0.0));
1692 assert_eq!(parse_style_font_size("16").unwrap().inner, PixelValue::px(16.0));
1693 assert_eq!(parse_style_font_size("-16").unwrap().inner, PixelValue::px(-16.0));
1694 }
1695
1696 #[cfg(feature = "parser")]
1697 #[test]
1698 fn parse_style_font_size_boundary_numbers_saturate_instead_of_panicking() {
1699 assert_eq!(parse_style_font_size("-0px").unwrap().inner.number.get(), 0.0);
1701 assert_eq!(parse_style_font_size("0px").unwrap().inner.number.get(), 0.0);
1702
1703 let big = parse_style_font_size("1e40px").unwrap().inner.number.get();
1705 assert!(big.is_finite() && big > 0.0, "expected a saturated finite value, got {big}");
1706 let small = parse_style_font_size("-1e40px").unwrap().inner.number.get();
1707 assert!(small.is_finite() && small < 0.0, "expected a saturated finite value, got {small}");
1708
1709 let inf = parse_style_font_size("inf").unwrap().inner.number.get();
1711 assert!(inf.is_finite() && inf > 0.0);
1712 let neg_inf = parse_style_font_size("-infinitypx").unwrap().inner.number.get();
1713 assert!(neg_inf.is_finite() && neg_inf < 0.0);
1714
1715 for input in ["9223372036854775807", "18446744073709551615px"] {
1717 let v = parse_style_font_size(input).unwrap().inner.number.get();
1718 assert!(v.is_finite(), "{input:?} produced {v}");
1719 }
1720
1721 assert_eq!(parse_style_font_size("16.0004px").unwrap().inner, PixelValue::px(16.0));
1723 assert_eq!(parse_style_font_size("1e-40px").unwrap().inner.number.get(), 0.0);
1724 }
1725
1726 #[cfg(feature = "parser")]
1727 #[test]
1728 fn parse_style_font_size_nan_is_silently_coerced_to_zero() {
1729 let parsed = parse_style_font_size("NaN").unwrap();
1732 assert_eq!(parsed.inner.metric, SizeMetric::Px);
1733 assert_eq!(parsed.inner.number.get(), 0.0);
1734 assert_eq!(parse_style_font_size("nanpx").unwrap().inner.number.get(), 0.0);
1735 }
1736
1737 #[cfg(feature = "parser")]
1738 #[test]
1739 fn parse_style_font_size_leading_trailing_whitespace_is_trimmed() {
1740 assert_eq!(parse_style_font_size(" 16px ").unwrap().inner, PixelValue::px(16.0));
1741 assert_eq!(parse_style_font_size("16 px").unwrap().inner, PixelValue::px(16.0));
1743 }
1744
1745 #[cfg(feature = "parser")]
1746 #[test]
1747 fn parse_style_font_size_survives_extremely_long_and_deeply_nested_input() {
1748 let long_digits = "1".repeat(50_000);
1749 let parsed = parse_style_font_size(&long_digits).unwrap();
1750 assert!(parsed.inner.number.get().is_finite());
1751
1752 let long_garbage = "z".repeat(1_000_000);
1753 assert!(parse_style_font_size(&long_garbage).is_err());
1754
1755 let nested = "(".repeat(10_000);
1756 assert!(parse_style_font_size(&nested).is_err());
1757 }
1758
1759 #[cfg(feature = "parser")]
1764 #[test]
1765 fn parse_style_font_family_valid_minimal() {
1766 let parsed = parse_style_font_family("Arial").unwrap();
1767 assert_eq!(parsed.len(), 1);
1768 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("Arial".into()));
1769 }
1770
1771 #[cfg(feature = "parser")]
1772 #[test]
1773 fn parse_style_font_family_never_returns_err() {
1774 let nested = "(".repeat(10_000);
1778 let long = "x".repeat(1_000_000);
1779 let inputs: Vec<&str> = vec![
1780 "",
1781 " ",
1782 "\t\n",
1783 ",",
1784 ",,,",
1785 "'unclosed",
1786 "\"unclosed",
1787 "\"Arial'",
1788 "'Arial\"",
1789 "\u{1F600}",
1790 "system:",
1791 "system:bogus",
1792 "url(x.ttf)",
1793 "font-ref(0xdeadbeef)",
1794 "\u{0}\u{7f}",
1795 &nested,
1796 &long,
1797 ];
1798 for input in inputs {
1799 assert!(
1800 parse_style_font_family(input).is_ok(),
1801 "parse_style_font_family unexpectedly failed for {:?}",
1802 &input[..input.len().min(32)]
1803 );
1804 }
1805 }
1806
1807 #[cfg(feature = "parser")]
1808 #[test]
1809 fn parse_style_font_family_empty_input_yields_one_empty_family() {
1810 let parsed = parse_style_font_family("").unwrap();
1813 assert_eq!(parsed.len(), 1);
1814 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("".into()));
1815
1816 let parsed = parse_style_font_family(" ").unwrap();
1817 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("".into()));
1818
1819 let parsed = parse_style_font_family(",,,").unwrap();
1820 assert_eq!(parsed.len(), 4, "N commas produce N+1 (empty) families");
1821 assert!(parsed
1822 .iter()
1823 .all(|f| *f == StyleFontFamily::System("".into())));
1824 }
1825
1826 #[cfg(feature = "parser")]
1827 #[test]
1828 fn parse_style_font_family_unclosed_quotes_keep_the_quote_character() {
1829 let parsed = parse_style_font_family("'unclosed").unwrap();
1832 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("'unclosed".into()));
1833
1834 let parsed = parse_style_font_family("\"Arial'").unwrap();
1835 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("\"Arial'".into()));
1836
1837 let parsed = parse_style_font_family("\"\"").unwrap();
1839 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("".into()));
1840 }
1841
1842 #[cfg(feature = "parser")]
1843 #[test]
1844 fn parse_style_font_family_system_prefix_is_case_sensitive_and_falls_back() {
1845 let parsed = parse_style_font_family("system:bogus").unwrap();
1847 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("system:bogus".into()));
1848 let parsed = parse_style_font_family("system:").unwrap();
1849 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("system:".into()));
1850 let parsed = parse_style_font_family("SYSTEM:UI").unwrap();
1852 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("SYSTEM:UI".into()));
1853 let parsed = parse_style_font_family("system:UI").unwrap();
1854 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("system:UI".into()));
1855 }
1856
1857 #[cfg(feature = "parser")]
1858 #[test]
1859 fn parse_style_font_family_never_produces_file_or_ref_variants() {
1860 for input in ["url(x.ttf)", "font-ref(0x1)", "Arial", "system:ui", "'a'", "\u{1F600}"] {
1861 let parsed = parse_style_font_family(input).unwrap();
1862 assert!(
1863 parsed.iter().all(|f| matches!(
1864 f,
1865 StyleFontFamily::System(_) | StyleFontFamily::SystemType(_)
1866 )),
1867 "the parser must only ever yield System/SystemType, got {parsed:?}"
1868 );
1869 }
1870 }
1871
1872 #[cfg(feature = "parser")]
1873 #[test]
1874 fn parse_style_font_family_handles_unicode_names() {
1875 let parsed = parse_style_font_family("日本語, \u{1F600}, e\u{0301}").unwrap();
1876 assert_eq!(parsed.len(), 3);
1877 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("日本語".into()));
1878 assert_eq!(parsed.as_slice()[1], StyleFontFamily::System("\u{1F600}".into()));
1879 assert_eq!(parsed.as_slice()[2], StyleFontFamily::System("e\u{0301}".into()));
1880 }
1881
1882 #[cfg(feature = "parser")]
1883 #[test]
1884 fn parse_style_font_family_survives_extremely_long_and_deeply_nested_input() {
1885 let huge_name = "x".repeat(1_000_000);
1886 let parsed = parse_style_font_family(&huge_name).unwrap();
1887 assert_eq!(parsed.len(), 1);
1888 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System(huge_name.as_str().into()));
1889
1890 let many = "Arial,".repeat(10_000);
1891 let parsed = parse_style_font_family(&many).unwrap();
1892 assert_eq!(parsed.len(), 10_001, "trailing comma adds one empty family");
1893
1894 let nested = "(".repeat(10_000);
1895 let parsed = parse_style_font_family(&nested).unwrap();
1896 assert_eq!(parsed.len(), 1);
1897 }
1898
1899 #[cfg(feature = "parser")]
1904 #[test]
1905 fn style_font_family_as_string_roundtrips_through_the_parser() {
1906 for name in ["Arial", "Times New Roman", "", "日本語", "a\u{00A0}b", "Fo\"o", "serif"] {
1907 let family = StyleFontFamily::System(name.into());
1908 let css = family.as_string();
1909 let parsed = parse_style_font_family(&css).unwrap();
1910 assert_eq!(parsed.len(), 1, "{name:?} printed as {css:?}");
1911 assert_eq!(parsed.as_slice()[0], family, "encode==decode failed for {name:?}");
1912 }
1913
1914 for ft in [
1915 SystemFontType::Ui,
1916 SystemFontType::UiBold,
1917 SystemFontType::Monospace,
1918 SystemFontType::MonospaceBold,
1919 SystemFontType::MonospaceItalic,
1920 SystemFontType::Title,
1921 SystemFontType::TitleBold,
1922 SystemFontType::Menu,
1923 SystemFontType::Small,
1924 SystemFontType::Serif,
1925 SystemFontType::SerifBold,
1926 ] {
1927 let family = StyleFontFamily::SystemType(ft);
1928 let parsed = parse_style_font_family(&family.as_string()).unwrap();
1929 assert_eq!(parsed.as_slice()[0], family, "encode==decode failed for {ft:?}");
1930 }
1931 }
1932
1933 #[cfg(feature = "parser")]
1934 #[test]
1935 fn style_font_family_as_string_does_not_escape_commas() {
1936 let family = StyleFontFamily::System("Foo,Bar".into());
1939 assert_eq!(family.as_string(), "Foo,Bar");
1940 let parsed = parse_style_font_family(&family.as_string()).unwrap();
1941 assert_eq!(parsed.len(), 2);
1942 }
1943
1944 #[cfg(feature = "parser")]
1945 #[test]
1946 fn style_font_family_file_and_ref_do_not_roundtrip() {
1947 let file = StyleFontFamily::File("f.ttf".into());
1950 let parsed = parse_style_font_family(&file.as_string()).unwrap();
1951 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("url(f.ttf)".into()));
1952
1953 let font = StyleFontFamily::Ref(FontRef::new(core::ptr::null(), noop_destructor));
1954 let parsed = parse_style_font_family(&font.as_string()).unwrap();
1955 assert_eq!(parsed.as_slice()[0], StyleFontFamily::System("font-ref(0x0)".into()));
1956 }
1957
1958 #[cfg(feature = "parser")]
1959 #[test]
1960 fn style_font_family_vec_print_as_css_value_roundtrips() {
1961 let css = "Arial, \"Times New Roman\", system:ui";
1962 let parsed = parse_style_font_family(css).unwrap();
1963 assert_eq!(parsed.print_as_css_value(), css);
1964 let reparsed = parse_style_font_family(&parsed.print_as_css_value()).unwrap();
1965 assert_eq!(reparsed, parsed, "encode==decode failed for a font stack");
1966 }
1967
1968 #[test]
1973 fn css_font_weight_parse_error_invalid_value_roundtrips() {
1974 for value in ["", "thin", "\u{1F600}", "a\u{0}b"] {
1975 let shared = CssFontWeightParseError::InvalidValue(InvalidValueErr(value));
1976 let owned = shared.to_contained();
1977 assert_eq!(
1978 owned,
1979 CssFontWeightParseErrorOwned::InvalidValue(InvalidValueErrOwned {
1980 value: value.into()
1981 })
1982 );
1983 assert_eq!(owned.to_shared(), shared, "to_contained/to_shared must round-trip");
1984 assert!(!format!("{shared}").is_empty());
1985 }
1986 }
1987
1988 #[test]
1989 fn css_font_weight_parse_error_invalid_number_roundtrips() {
1990 let cases = [
1991 "".parse::<i32>().unwrap_err(),
1992 "x".parse::<i32>().unwrap_err(),
1993 "99999999999999999999".parse::<i32>().unwrap_err(),
1994 "-99999999999999999999".parse::<i32>().unwrap_err(),
1995 ];
1996 for err in cases {
1997 let shared = CssFontWeightParseError::InvalidNumber(err);
1998 let owned = shared.to_contained();
1999 assert_eq!(owned.to_shared(), shared, "kind must survive the FFI round-trip");
2000 assert!(!format!("{shared}").is_empty());
2001 }
2002 }
2003
2004 #[test]
2005 fn css_font_weight_parse_error_zero_kind_roundtrip_is_lossy() {
2006 let zero_err = "0".parse::<core::num::NonZeroU32>().unwrap_err();
2009 let shared = CssFontWeightParseError::InvalidNumber(zero_err);
2010 let owned = shared.to_contained();
2011 assert_eq!(
2012 owned,
2013 CssFontWeightParseErrorOwned::InvalidNumber(CParseIntError::Zero),
2014 "the Zero kind must survive into the owned form"
2015 );
2016 assert_ne!(
2017 owned.to_shared(),
2018 shared,
2019 "to_std() cannot rebuild a Zero-kind ParseIntError (documented)"
2020 );
2021 }
2022
2023 #[test]
2024 fn css_font_style_parse_error_roundtrips() {
2025 for value in ["", "slanted", "\u{1F600}"] {
2026 let shared = CssFontStyleParseError::InvalidValue(InvalidValueErr(value));
2027 let owned = shared.to_contained();
2028 assert_eq!(
2029 owned,
2030 CssFontStyleParseErrorOwned::InvalidValue(InvalidValueErrOwned {
2031 value: value.into()
2032 })
2033 );
2034 assert_eq!(owned.to_shared(), shared);
2035 assert!(!format!("{shared}").is_empty());
2036 }
2037 }
2038
2039 #[test]
2040 fn css_style_font_size_parse_error_roundtrips_every_variant() {
2041 let cases = [
2042 CssPixelValueParseError::EmptyString,
2043 CssPixelValueParseError::NoValueGiven("px", SizeMetric::Px),
2044 CssPixelValueParseError::NoValueGiven("%", SizeMetric::Percent),
2045 CssPixelValueParseError::ValueParseErr("abc".parse::<f32>().unwrap_err(), "abc"),
2046 CssPixelValueParseError::ValueParseErr("".parse::<f32>().unwrap_err(), ""),
2047 CssPixelValueParseError::InvalidPixelValue("medium"),
2048 CssPixelValueParseError::InvalidPixelValue("\u{1F600}"),
2049 ];
2050 for inner in cases {
2051 let shared = CssStyleFontSizeParseError::PixelValue(inner);
2052 let owned = shared.to_contained();
2053 assert_eq!(owned.to_shared(), shared, "to_contained/to_shared must round-trip");
2054 assert!(!format!("{shared}").is_empty());
2055 }
2056 }
2057
2058 #[cfg(feature = "parser")]
2059 #[test]
2060 fn css_style_font_family_parse_error_roundtrips_every_variant() {
2061 let cases = [
2062 CssStyleFontFamilyParseError::InvalidStyleFontFamily(""),
2063 CssStyleFontFamilyParseError::InvalidStyleFontFamily("bogus"),
2064 CssStyleFontFamilyParseError::UnclosedQuotes(UnclosedQuotesError("\"Arial")),
2065 CssStyleFontFamilyParseError::UnclosedQuotes(UnclosedQuotesError("\u{1F600}")),
2066 ];
2067 for shared in cases {
2068 let owned = shared.to_contained();
2069 assert_eq!(owned.to_shared(), shared, "to_contained/to_shared must round-trip");
2070 assert!(!format!("{shared}").is_empty());
2071 }
2072 }
2073
2074 #[test]
2079 fn font_enum_defaults_and_ordering() {
2080 assert_eq!(StyleFontWeight::default(), StyleFontWeight::Normal);
2081 assert_eq!(StyleFontStyle::default(), StyleFontStyle::Normal);
2082 assert_eq!(StyleFontSize::default().inner, PixelValue::const_pt(12));
2083
2084 assert!(StyleFontWeight::W100 < StyleFontWeight::W900);
2086 assert!(StyleFontWeight::Normal < StyleFontWeight::Bold);
2087 assert!(StyleFontWeight::Lighter < StyleFontWeight::W100);
2088 assert!(StyleFontWeight::Bolder > StyleFontWeight::W900);
2089 }
2090
2091 #[test]
2092 fn format_as_rust_code_matches_the_debug_variant_names() {
2093 for weight in [
2094 StyleFontWeight::Lighter,
2095 StyleFontWeight::W100,
2096 StyleFontWeight::W200,
2097 StyleFontWeight::W300,
2098 StyleFontWeight::Normal,
2099 StyleFontWeight::W500,
2100 StyleFontWeight::W600,
2101 StyleFontWeight::Bold,
2102 StyleFontWeight::W800,
2103 StyleFontWeight::W900,
2104 StyleFontWeight::Bolder,
2105 ] {
2106 assert_eq!(
2107 weight.format_as_rust_code(0),
2108 format!("StyleFontWeight::{weight:?}")
2109 );
2110 }
2111 for style in [
2112 StyleFontStyle::Normal,
2113 StyleFontStyle::Italic,
2114 StyleFontStyle::Oblique,
2115 ] {
2116 assert_eq!(
2117 style.format_as_rust_code(0),
2118 format!("StyleFontStyle::{style:?}")
2119 );
2120 }
2121 assert_eq!(
2122 StyleFontFamily::SystemType(SystemFontType::Ui).format_as_rust_code(0),
2123 "StyleFontFamily::SystemType(SystemFontType::Ui)"
2124 );
2125 assert!(StyleFontFamily::System("Arial".into())
2126 .format_as_rust_code(0)
2127 .starts_with("StyleFontFamily::System(STRING_"));
2128 assert!(StyleFontFamily::File("a.ttf".into())
2129 .format_as_rust_code(0)
2130 .starts_with("StyleFontFamily::File(STRING_"));
2131 }
2132
2133 #[test]
2138 fn panose_zero_is_the_neutral_element() {
2139 const P: Panose = Panose::zero();
2140 assert_eq!(P, Panose::default());
2141 assert_eq!(hash_of(&P), hash_of(&Panose::default()));
2142 assert_eq!(P.family_type, 0);
2143 assert_eq!(P.serif_style, 0);
2144 assert_eq!(P.weight, 0);
2145 assert_eq!(P.proportion, 0);
2146 assert_eq!(P.contrast, 0);
2147 assert_eq!(P.stroke_variation, 0);
2148 assert_eq!(P.arm_style, 0);
2149 assert_eq!(P.letterform, 0);
2150 assert_eq!(P.midline, 0);
2151 assert_eq!(P.x_height, 0);
2152
2153 let mut max = Panose::zero();
2154 max.family_type = u8::MAX;
2155 assert!(max > P, "derived Ord must order by the first field");
2156 }
2157
2158 #[test]
2159 fn font_metrics_zero_invariants() {
2160 const M: FontMetrics = FontMetrics::zero();
2161 assert_eq!(M, FontMetrics::default());
2162
2163 assert_eq!(M.units_per_em, 1000);
2165 assert_eq!(M.us_weight_class, 400);
2166 assert_eq!(M.us_width_class, 5);
2167 assert_eq!(M.panose, Panose::zero());
2168
2169 assert_eq!(M.get_ascender(), 0);
2170 assert_eq!(M.get_descender(), 0);
2171 assert_eq!(M.get_line_gap(), 0);
2172 assert_eq!(M.get_advance_width_max(), 0);
2173 assert_eq!(M.get_min_left_side_bearing(), 0);
2174 assert_eq!(M.get_min_right_side_bearing(), 0);
2175 assert_eq!(M.get_x_min(), 0);
2176 assert_eq!(M.get_y_min(), 0);
2177 assert_eq!(M.get_x_max(), 0);
2178 assert_eq!(M.get_y_max(), 0);
2179 assert_eq!(M.get_x_max_extent(), 0);
2180 assert_eq!(M.get_x_avg_char_width(), 0);
2181 assert_eq!(M.get_y_subscript_x_size(), 0);
2182 assert_eq!(M.get_y_subscript_y_size(), 0);
2183 assert_eq!(M.get_y_subscript_x_offset(), 0);
2184 assert_eq!(M.get_y_subscript_y_offset(), 0);
2185 assert_eq!(M.get_y_superscript_x_size(), 0);
2186 assert_eq!(M.get_y_superscript_y_size(), 0);
2187 assert_eq!(M.get_y_superscript_x_offset(), 0);
2188 assert_eq!(M.get_y_superscript_y_offset(), 0);
2189 assert_eq!(M.get_y_strikeout_size(), 0);
2190 assert_eq!(M.get_y_strikeout_position(), 0);
2191 assert!(!M.use_typo_metrics());
2192
2193 assert!(matches!(M.ul_code_page_range1, OptionU32::None));
2194 assert!(matches!(M.ul_code_page_range2, OptionU32::None));
2195 assert!(matches!(M.s_typo_ascender, OptionI16::None));
2196 assert!(matches!(M.s_typo_descender, OptionI16::None));
2197 assert!(matches!(M.s_typo_line_gap, OptionI16::None));
2198 assert!(matches!(M.us_win_ascent, OptionU16::None));
2199 assert!(matches!(M.us_win_descent, OptionU16::None));
2200 assert!(matches!(M.sx_height, OptionI16::None));
2201 assert!(matches!(M.s_cap_height, OptionI16::None));
2202 }
2203
2204 #[test]
2205 fn font_metrics_getters_return_extreme_values_unchanged() {
2206 let mut m = FontMetrics::zero();
2207 m.ascender = i16::MAX;
2208 m.descender = i16::MIN;
2209 m.line_gap = i16::MIN;
2210 m.advance_width_max = u16::MAX;
2211 m.min_left_side_bearing = i16::MIN;
2212 m.min_right_side_bearing = i16::MAX;
2213 m.x_min = i16::MIN;
2214 m.y_min = i16::MIN;
2215 m.x_max = i16::MAX;
2216 m.y_max = i16::MAX;
2217 m.x_max_extent = i16::MAX;
2218 m.x_avg_char_width = i16::MIN;
2219 m.y_subscript_x_size = i16::MAX;
2220 m.y_subscript_y_size = i16::MIN;
2221 m.y_subscript_x_offset = i16::MAX;
2222 m.y_subscript_y_offset = i16::MIN;
2223 m.y_superscript_x_size = i16::MAX;
2224 m.y_superscript_y_size = i16::MIN;
2225 m.y_superscript_x_offset = i16::MAX;
2226 m.y_superscript_y_offset = i16::MIN;
2227 m.y_strikeout_size = i16::MAX;
2228 m.y_strikeout_position = i16::MIN;
2229
2230 assert_eq!(m.get_ascender(), i16::MAX);
2232 assert_eq!(m.get_descender(), i16::MIN);
2233 assert_eq!(m.get_line_gap(), i16::MIN);
2234 assert_eq!(m.get_advance_width_max(), u16::MAX);
2235 assert_eq!(m.get_min_left_side_bearing(), i16::MIN);
2236 assert_eq!(m.get_min_right_side_bearing(), i16::MAX);
2237 assert_eq!(m.get_x_min(), i16::MIN);
2238 assert_eq!(m.get_y_min(), i16::MIN);
2239 assert_eq!(m.get_x_max(), i16::MAX);
2240 assert_eq!(m.get_y_max(), i16::MAX);
2241 assert_eq!(m.get_x_max_extent(), i16::MAX);
2242 assert_eq!(m.get_x_avg_char_width(), i16::MIN);
2243 assert_eq!(m.get_y_subscript_x_size(), i16::MAX);
2244 assert_eq!(m.get_y_subscript_y_size(), i16::MIN);
2245 assert_eq!(m.get_y_subscript_x_offset(), i16::MAX);
2246 assert_eq!(m.get_y_subscript_y_offset(), i16::MIN);
2247 assert_eq!(m.get_y_superscript_x_size(), i16::MAX);
2248 assert_eq!(m.get_y_superscript_y_size(), i16::MIN);
2249 assert_eq!(m.get_y_superscript_x_offset(), i16::MAX);
2250 assert_eq!(m.get_y_superscript_y_offset(), i16::MIN);
2251 assert_eq!(m.get_y_strikeout_size(), i16::MAX);
2252 assert_eq!(m.get_y_strikeout_position(), i16::MIN);
2253
2254 assert!(m.get_ascender() > m.get_descender());
2257 }
2258
2259 #[test]
2260 fn font_metrics_use_typo_metrics_reads_exactly_bit_7() {
2261 let mut m = FontMetrics::zero();
2262 for bit in 0..16u16 {
2263 m.fs_selection = 1 << bit;
2264 assert_eq!(
2265 m.use_typo_metrics(),
2266 bit == 7,
2267 "fs_selection bit {bit} must not affect USE_TYPO_METRICS"
2268 );
2269 }
2270 m.fs_selection = u16::MAX;
2271 assert!(m.use_typo_metrics());
2272 m.fs_selection = u16::MAX ^ 0x0080;
2273 assert!(!m.use_typo_metrics(), "clearing bit 7 must clear the flag");
2274 m.fs_selection = 0;
2275 assert!(!m.use_typo_metrics());
2276 }
2277}