1use crate::{parser::Style, utils::CoreError, Result};
22use alloc::{string::String, string::ToString};
23
24bitflags::bitflags! {
25 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
27 pub struct TextFormatting: u8 {
28 const BOLD = 1 << 0;
30 const ITALIC = 1 << 1;
32 const UNDERLINE = 1 << 2;
34 const STRIKE_OUT = 1 << 3;
36 }
37}
38
39#[derive(Debug, Clone, PartialEq)]
45pub struct ResolvedStyle<'a> {
46 pub name: &'a str,
48 font_name: String,
50 font_size: f32,
52 primary_color: [u8; 4],
54 secondary_color: [u8; 4],
56 outline_color: [u8; 4],
58 back_color: [u8; 4],
60 formatting: TextFormatting,
62 scale_x: f32,
65 scale_y: f32,
67 spacing: f32,
69 angle: f32,
71 border_style: u8,
73 outline: f32,
75 shadow: f32,
77 alignment: u8,
79 margin_l: u16,
82 margin_r: u16,
84 margin_t: u16,
86 margin_b: u16,
88 encoding: u8,
90 complexity_score: u8,
92}
93
94impl<'a> ResolvedStyle<'a> {
95 pub fn from_style(style: &'a Style<'a>) -> Result<Self> {
124 let font_name = if style.fontname.is_empty() {
125 "Arial".to_string()
126 } else {
127 style.fontname.to_string()
128 };
129
130 let font_size = parse_font_size(style.fontsize)?;
131 let primary_color = parse_color_with_default(style.primary_colour)?;
132 let secondary_color = parse_color_with_default(style.secondary_colour)?;
133 let outline_color = parse_color_with_default(style.outline_colour)?;
134 let back_color = parse_color_with_default(style.back_colour)?;
135
136 let mut formatting = TextFormatting::empty();
137 if parse_bool_flag(style.bold)? {
138 formatting |= TextFormatting::BOLD;
139 }
140 if parse_bool_flag(style.italic)? {
141 formatting |= TextFormatting::ITALIC;
142 }
143 if parse_bool_flag(style.underline)? {
144 formatting |= TextFormatting::UNDERLINE;
145 }
146 if parse_bool_flag(style.strikeout)? {
147 formatting |= TextFormatting::STRIKE_OUT;
148 }
149
150 let scale_x = parse_percentage(style.scale_x)?;
151 let scale_y = parse_percentage(style.scale_y)?;
152 let spacing = parse_float(style.spacing)?;
153 let angle = parse_float(style.angle)?;
154
155 let border_style = parse_u8(style.border_style)?;
156 let outline = parse_float(style.outline)?;
157 let shadow = parse_float(style.shadow)?;
158
159 let alignment = parse_u8(style.alignment)?;
160 let margin_l = parse_u16(style.margin_l)?;
161 let margin_r = parse_u16(style.margin_r)?;
162
163 let (margin_t, margin_b) = if let (Some(t), Some(b)) = (style.margin_t, style.margin_b) {
165 (parse_u16(t)?, parse_u16(b)?)
167 } else {
168 let margin_v = parse_u16(style.margin_v)?;
170 (margin_v, margin_v)
171 };
172
173 let encoding = parse_u8(style.encoding)?;
174
175 let resolved = Self {
176 name: style.name,
177 font_name,
178 font_size,
179 primary_color,
180 secondary_color,
181 outline_color,
182 back_color,
183 formatting,
184 scale_x,
185 scale_y,
186 spacing,
187 angle,
188 border_style,
189 outline,
190 shadow,
191 alignment,
192 margin_l,
193 margin_r,
194 margin_t,
195 margin_b,
196 encoding,
197 complexity_score: 0, };
199
200 Ok(Self {
201 complexity_score: Self::calculate_complexity(&resolved),
202 ..resolved
203 })
204 }
205
206 #[must_use]
208 pub fn font_name(&self) -> &str {
209 &self.font_name
210 }
211
212 #[must_use]
214 pub const fn font_size(&self) -> f32 {
215 self.font_size
216 }
217
218 #[must_use]
220 pub const fn primary_color(&self) -> [u8; 4] {
221 self.primary_color
222 }
223
224 #[must_use]
226 pub const fn complexity_score(&self) -> u8 {
227 self.complexity_score
228 }
229
230 #[must_use]
232 pub const fn has_performance_issues(&self) -> bool {
233 self.complexity_score > 70
234 }
235
236 #[must_use]
238 pub const fn formatting(&self) -> TextFormatting {
239 self.formatting
240 }
241
242 #[must_use]
244 pub const fn is_bold(&self) -> bool {
245 self.formatting.contains(TextFormatting::BOLD)
246 }
247
248 #[must_use]
250 pub const fn is_italic(&self) -> bool {
251 self.formatting.contains(TextFormatting::ITALIC)
252 }
253
254 #[must_use]
256 pub const fn is_underline(&self) -> bool {
257 self.formatting.contains(TextFormatting::UNDERLINE)
258 }
259
260 #[must_use]
262 pub const fn is_strike_out(&self) -> bool {
263 self.formatting.contains(TextFormatting::STRIKE_OUT)
264 }
265
266 #[must_use]
268 pub const fn margin_l(&self) -> u16 {
269 self.margin_l
270 }
271
272 #[must_use]
274 pub const fn margin_r(&self) -> u16 {
275 self.margin_r
276 }
277
278 #[must_use]
280 pub const fn margin_t(&self) -> u16 {
281 self.margin_t
282 }
283
284 #[must_use]
286 pub const fn margin_b(&self) -> u16 {
287 self.margin_b
288 }
289
290 #[must_use]
292 pub const fn outline(&self) -> f32 {
293 self.outline
294 }
295
296 #[must_use]
298 pub const fn shadow(&self) -> f32 {
299 self.shadow
300 }
301
302 #[must_use]
304 pub const fn secondary_color(&self) -> [u8; 4] {
305 self.secondary_color
306 }
307
308 #[must_use]
310 pub const fn outline_color(&self) -> [u8; 4] {
311 self.outline_color
312 }
313
314 #[must_use]
316 pub const fn spacing(&self) -> f32 {
317 self.spacing
318 }
319
320 #[must_use]
322 pub const fn angle(&self) -> f32 {
323 self.angle
324 }
325
326 #[allow(clippy::cognitive_complexity)]
341 pub fn from_style_with_parent(style: &'a Style<'a>, parent: &Self) -> Result<Self> {
342 let mut resolved = parent.clone();
344
345 resolved.name = style.name;
347
348 if !style.fontname.is_empty() {
350 resolved.font_name = style.fontname.to_string();
351 }
352
353 if !style.fontsize.is_empty() && style.fontsize != "0" {
354 resolved.font_size = parse_font_size(style.fontsize)?;
355 }
356
357 if !style.primary_colour.is_empty() {
358 resolved.primary_color = parse_color_with_default(style.primary_colour)?;
359 }
360
361 if !style.secondary_colour.is_empty() {
362 resolved.secondary_color = parse_color_with_default(style.secondary_colour)?;
363 }
364
365 if !style.outline_colour.is_empty() {
366 resolved.outline_color = parse_color_with_default(style.outline_colour)?;
367 }
368
369 if !style.back_colour.is_empty() {
370 resolved.back_color = parse_color_with_default(style.back_colour)?;
371 }
372
373 let mut formatting = resolved.formatting;
375 if !style.bold.is_empty() {
376 if style.bold == "0" {
377 formatting &= !TextFormatting::BOLD;
378 } else if style.bold == "1" {
379 formatting |= TextFormatting::BOLD;
380 }
381 }
382 if !style.italic.is_empty() {
383 if style.italic == "0" {
384 formatting &= !TextFormatting::ITALIC;
385 } else if style.italic == "1" {
386 formatting |= TextFormatting::ITALIC;
387 }
388 }
389 if !style.underline.is_empty() {
390 if style.underline == "0" {
391 formatting &= !TextFormatting::UNDERLINE;
392 } else if style.underline == "1" {
393 formatting |= TextFormatting::UNDERLINE;
394 }
395 }
396 if !style.strikeout.is_empty() {
397 if style.strikeout == "0" {
398 formatting &= !TextFormatting::STRIKE_OUT;
399 } else if style.strikeout == "1" {
400 formatting |= TextFormatting::STRIKE_OUT;
401 }
402 }
403 resolved.formatting = formatting;
404
405 if !style.scale_x.is_empty() && style.scale_x != "100" {
406 resolved.scale_x = parse_percentage(style.scale_x)?;
407 }
408
409 if !style.scale_y.is_empty() && style.scale_y != "100" {
410 resolved.scale_y = parse_percentage(style.scale_y)?;
411 }
412
413 if !style.spacing.is_empty() && style.spacing != "0" {
414 resolved.spacing = parse_float(style.spacing)?;
415 }
416
417 if !style.angle.is_empty() && style.angle != "0" {
418 resolved.angle = parse_float(style.angle)?;
419 }
420
421 if !style.border_style.is_empty() {
422 resolved.border_style = parse_u8(style.border_style)?;
423 }
424
425 if !style.outline.is_empty() && style.outline != "0" {
426 resolved.outline = parse_float(style.outline)?;
427 }
428
429 if !style.shadow.is_empty() && style.shadow != "0" {
430 resolved.shadow = parse_float(style.shadow)?;
431 }
432
433 if !style.alignment.is_empty() {
434 resolved.alignment = parse_u8(style.alignment)?;
435 }
436
437 if !style.margin_l.is_empty() {
438 resolved.margin_l = parse_u16(style.margin_l)?;
439 }
440
441 if !style.margin_r.is_empty() {
442 resolved.margin_r = parse_u16(style.margin_r)?;
443 }
444
445 if let (Some(t), Some(b)) = (style.margin_t, style.margin_b) {
447 if !t.is_empty() {
448 resolved.margin_t = parse_u16(t)?;
449 }
450 if !b.is_empty() {
451 resolved.margin_b = parse_u16(b)?;
452 }
453 } else if !style.margin_v.is_empty() && style.margin_v != "0" {
454 let margin_v = parse_u16(style.margin_v)?;
455 resolved.margin_t = margin_v;
456 resolved.margin_b = margin_v;
457 }
458 if !style.encoding.is_empty() {
461 resolved.encoding = parse_u8(style.encoding)?;
462 }
463
464 resolved.complexity_score = Self::calculate_complexity(&resolved);
466
467 Ok(resolved)
468 }
469
470 pub fn apply_resolution_scaling(&mut self, scale_x: f32, scale_y: f32) {
480 let avg_scale = (scale_x + scale_y) / 2.0;
482 self.font_size *= avg_scale;
483
484 self.spacing *= scale_x;
486
487 self.outline *= avg_scale;
489 self.shadow *= avg_scale;
490
491 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
493 {
494 self.margin_l = (f32::from(self.margin_l) * scale_x) as u16;
495 self.margin_r = (f32::from(self.margin_r) * scale_x) as u16;
496 self.margin_t = (f32::from(self.margin_t) * scale_y) as u16;
497 self.margin_b = (f32::from(self.margin_b) * scale_y) as u16;
498 }
499
500 self.complexity_score = Self::calculate_complexity(self);
502 }
503
504 fn calculate_complexity(style: &Self) -> u8 {
506 const EPSILON: f32 = 0.001;
507 let mut score = 0u8;
508
509 if style.font_size > 72.0 {
510 score += 20;
511 } else if style.font_size > 48.0 {
512 score += 10;
513 }
514
515 if style.outline > 4.0 {
516 score += 15;
517 } else if style.outline > 2.0 {
518 score += 8;
519 }
520
521 if style.shadow > 3.0 {
522 score += 10;
523 } else if style.shadow > 1.0 {
524 score += 5;
525 }
526
527 if (style.scale_x - 100.0).abs() > EPSILON || (style.scale_y - 100.0).abs() > EPSILON {
528 score += 10;
529 }
530
531 if style.angle.abs() > EPSILON {
532 score += 15;
533 }
534
535 if style.formatting.contains(TextFormatting::BOLD) {
536 score += 2;
537 }
538 if style.formatting.contains(TextFormatting::ITALIC) {
539 score += 2;
540 }
541 if style
542 .formatting
543 .intersects(TextFormatting::UNDERLINE | TextFormatting::STRIKE_OUT)
544 {
545 score += 5;
546 }
547
548 score.min(100)
549 }
550}
551
552fn parse_font_size(size_str: &str) -> Result<f32> {
554 let size = parse_float(size_str)?;
555 if size <= 0.0 || size > 1000.0 {
556 Err(CoreError::parse("Invalid font size"))
557 } else {
558 Ok(size)
559 }
560}
561
562fn parse_color_with_default(color_str: &str) -> Result<[u8; 4]> {
564 if color_str.trim().is_empty() {
565 Ok([255, 255, 255, 255]) } else {
567 crate::utils::parse_bgr_color(color_str)
568 }
569}
570
571fn parse_bool_flag(flag_str: &str) -> Result<bool> {
573 match flag_str {
574 "0" => Ok(false),
575 "1" => Ok(true),
576 _ => Err(CoreError::parse("Invalid boolean flag")),
577 }
578}
579
580fn parse_percentage(percent_str: &str) -> Result<f32> {
582 let value = parse_float(percent_str)?;
583 if (0.0..=1000.0).contains(&value) {
584 Ok(value)
585 } else {
586 Err(CoreError::parse("Invalid percentage"))
587 }
588}
589
590fn parse_float(float_str: &str) -> Result<f32> {
592 float_str
593 .parse::<f32>()
594 .map_err(|_| CoreError::parse("Invalid float value"))
595}
596
597fn parse_u8(u8_str: &str) -> Result<u8> {
599 u8_str
600 .parse::<u8>()
601 .map_err(|_| CoreError::parse("Invalid u8 value"))
602}
603
604fn parse_u16(u16_str: &str) -> Result<u16> {
606 u16_str
607 .parse::<u16>()
608 .map_err(|_| CoreError::parse("Invalid u16 value"))
609}
610
611#[cfg(test)]
612mod tests {
613 use super::*;
614 use crate::parser::ast::Span;
615 #[cfg(not(feature = "std"))]
616 fn create_test_style() -> Style<'static> {
617 Style {
618 name: "Test",
619 parent: None,
620 fontname: "Arial",
621 fontsize: "20",
622 primary_colour: "&H00FFFFFF",
623 secondary_colour: "&H000000FF",
624 outline_colour: "&H00000000",
625 back_colour: "&H00000000",
626 bold: "0",
627 italic: "0",
628 underline: "0",
629 strikeout: "0",
630 scale_x: "100",
631 scale_y: "100",
632 spacing: "0",
633 angle: "0",
634 border_style: "1",
635 outline: "2",
636 shadow: "0",
637 alignment: "2",
638 margin_l: "10",
639 margin_r: "10",
640 margin_v: "10",
641 margin_t: None,
642 margin_b: None,
643 encoding: "1",
644 relative_to: None,
645 span: Span::new(0, 0, 0, 0),
646 }
647 }
648
649 #[cfg(feature = "std")]
650 fn create_test_style() -> Style<'static> {
651 Style {
652 name: "Test",
653 parent: None,
654 fontname: "Arial",
655 fontsize: "20",
656 primary_colour: "&H00FFFFFF",
657 secondary_colour: "&H000000FF",
658 outline_colour: "&H00000000",
659 back_colour: "&H00000000",
660 bold: "0",
661 italic: "0",
662 underline: "0",
663 strikeout: "0",
664 scale_x: "100",
665 scale_y: "100",
666 spacing: "0",
667 angle: "0",
668 border_style: "1",
669 outline: "2",
670 shadow: "0",
671 alignment: "2",
672 margin_l: "10",
673 margin_r: "10",
674 margin_v: "10",
675 margin_t: None,
676 margin_b: None,
677 encoding: "1",
678 relative_to: None,
679 span: Span::new(0, 0, 0, 0),
680 }
681 }
682
683 #[test]
684 fn resolved_style_creation() {
685 let style = create_test_style();
686 let resolved = ResolvedStyle::from_style(&style).unwrap();
687
688 assert_eq!(resolved.name, "Test");
689 assert_eq!(resolved.font_name(), "Arial");
690 assert!((resolved.font_size() - 20.0).abs() < f32::EPSILON);
691 assert_eq!(resolved.primary_color(), [255, 255, 255, 0]);
692 }
693
694 #[test]
695 fn color_parsing() {
696 assert_eq!(
698 crate::utils::parse_bgr_color("&H000000FF").unwrap(),
699 [255, 0, 0, 0]
700 ); assert_eq!(
702 crate::utils::parse_bgr_color("&H0000FF00").unwrap(),
703 [0, 255, 0, 0]
704 ); assert_eq!(
706 crate::utils::parse_bgr_color("&H00FF0000").unwrap(),
707 [0, 0, 255, 0]
708 ); assert_eq!(
712 crate::utils::parse_bgr_color("&h000000FF").unwrap(),
713 [255, 0, 0, 0]
714 ); assert_eq!(
718 crate::utils::parse_bgr_color("&HFF0000").unwrap(),
719 [0, 0, 255, 0]
720 ); assert_eq!(
722 crate::utils::parse_bgr_color("&H00FF00").unwrap(),
723 [0, 255, 0, 0]
724 ); assert_eq!(
726 crate::utils::parse_bgr_color("&H0000FF").unwrap(),
727 [255, 0, 0, 0]
728 ); }
730
731 #[test]
732 fn complexity_scoring() {
733 let mut style = create_test_style();
734
735 let resolved = ResolvedStyle::from_style(&style).unwrap();
736 assert!(resolved.complexity_score() < 50);
737
738 style.fontsize = "100";
739 let resolved = ResolvedStyle::from_style(&style).unwrap();
740 assert!(resolved.complexity_score() >= 20);
741 }
742
743 #[test]
744 fn performance_issues_detection() {
745 let mut style = create_test_style();
746
747 let resolved = ResolvedStyle::from_style(&style).unwrap();
748 assert!(!resolved.has_performance_issues());
749
750 style.fontsize = "120"; style.outline = "8"; style.shadow = "5"; style.angle = "45"; style.scale_x = "150"; style.bold = "1"; style.italic = "1"; style.underline = "1"; let resolved = ResolvedStyle::from_style(&style).unwrap();
762 assert!(resolved.has_performance_issues());
763 }
764
765 #[test]
766 fn parse_font_size_edge_cases() {
767 assert!(parse_font_size("-10").is_err()); assert!(parse_font_size("0").is_err()); assert!(parse_font_size("1001").is_err()); assert!(parse_font_size("abc").is_err()); assert!(parse_font_size("").is_err()); assert!(parse_font_size("1").is_ok());
776 assert!(parse_font_size("72").is_ok());
777 assert!(parse_font_size("1000").is_ok());
778 }
779
780 #[test]
781 fn parse_color_with_default_invalid_formats() {
782 assert!(parse_color_with_default("invalid").is_err());
784 assert!(parse_color_with_default("&H").is_err());
785 assert!(parse_color_with_default("&HZZZZZ").is_err());
786 assert!(parse_color_with_default("12345G").is_err()); let default_color = parse_color_with_default("").unwrap();
790 assert_eq!(default_color, [255, 255, 255, 255]);
791
792 let whitespace_color = parse_color_with_default(" ").unwrap();
794 assert_eq!(whitespace_color, [255, 255, 255, 255]);
795 }
796
797 #[test]
798 fn parse_bool_flag_invalid_values() {
799 assert!(parse_bool_flag("2").is_err());
801 assert!(parse_bool_flag("-1").is_err());
802 assert!(parse_bool_flag("true").is_err());
803 assert!(parse_bool_flag("false").is_err());
804 assert!(parse_bool_flag("yes").is_err());
805 assert!(parse_bool_flag("no").is_err());
806 assert!(parse_bool_flag("").is_err());
807
808 assert!(!parse_bool_flag("0").unwrap());
810 assert!(parse_bool_flag("1").unwrap());
811 }
812
813 #[test]
814 #[allow(clippy::float_cmp)]
815 fn parse_percentage_invalid_values() {
816 assert!(parse_percentage("-10").is_err()); assert!(parse_percentage("1001").is_err()); assert!(parse_percentage("abc").is_err()); assert!(parse_percentage("").is_err()); assert_eq!(parse_percentage("0").unwrap(), 0.0);
824 assert_eq!(parse_percentage("100").unwrap(), 100.0);
825 assert_eq!(parse_percentage("1000").unwrap(), 1000.0);
826 }
827
828 #[test]
829 #[allow(clippy::float_cmp)]
830 fn parse_float_invalid_values() {
831 assert!(parse_float("abc").is_err());
832 assert!(parse_float("").is_err());
833 assert!(parse_float("1.2.3").is_err());
834 assert!(parse_float("1.2.3.4").is_err());
835 assert!(parse_float("not_a_number").is_err());
836
837 assert_eq!(parse_float("0").unwrap(), 0.0);
839 assert_eq!(parse_float("-10.5").unwrap(), -10.5);
840 assert_eq!(parse_float("123.456").unwrap(), 123.456);
841 }
842
843 #[test]
844 fn parse_u8_invalid_values() {
845 assert!(parse_u8("256").is_err()); assert!(parse_u8("-1").is_err()); assert!(parse_u8("abc").is_err()); assert!(parse_u8("").is_err()); assert_eq!(parse_u8("0").unwrap(), 0);
852 assert_eq!(parse_u8("255").unwrap(), 255);
853 }
854
855 #[test]
856 fn parse_u16_invalid_values() {
857 assert!(parse_u16("65536").is_err()); assert!(parse_u16("-1").is_err()); assert!(parse_u16("abc").is_err()); assert!(parse_u16("").is_err()); assert_eq!(parse_u16("0").unwrap(), 0);
864 assert_eq!(parse_u16("65535").unwrap(), 65535);
865 }
866
867 #[test]
868 fn resolved_style_from_style_with_invalid_values() {
869 let mut style = create_test_style();
870
871 style.fontsize = "-10";
873 assert!(ResolvedStyle::from_style(&style).is_err());
874
875 style.fontsize = "abc";
876 assert!(ResolvedStyle::from_style(&style).is_err());
877
878 style.fontsize = "20"; style.primary_colour = "invalid_color";
881 assert!(ResolvedStyle::from_style(&style).is_err());
882
883 style.primary_colour = "&HFFFFFF"; style.bold = "2";
886 assert!(ResolvedStyle::from_style(&style).is_err());
887 }
888
889 #[test]
890 fn complexity_calculation_all_branches() {
891 let mut style = create_test_style();
892
893 let resolved = ResolvedStyle::from_style(&style).unwrap();
895 let baseline_score = resolved.complexity_score();
896
897 style.fontsize = "100"; let resolved = ResolvedStyle::from_style(&style).unwrap();
900 assert!(resolved.complexity_score() > baseline_score);
901
902 style = create_test_style(); style.outline = "5"; let resolved = ResolvedStyle::from_style(&style).unwrap();
906 assert!(resolved.complexity_score() > baseline_score);
907
908 style = create_test_style(); style.shadow = "5"; let resolved = ResolvedStyle::from_style(&style).unwrap();
912 assert!(resolved.complexity_score() > baseline_score);
913
914 style = create_test_style(); style.scale_x = "200"; let resolved = ResolvedStyle::from_style(&style).unwrap();
918 assert!(resolved.complexity_score() > baseline_score);
919
920 style = create_test_style(); style.angle = "45"; let resolved = ResolvedStyle::from_style(&style).unwrap();
924 assert!(resolved.complexity_score() > baseline_score);
925
926 style = create_test_style(); style.bold = "1";
929 style.italic = "1";
930 style.underline = "1";
931 let resolved = ResolvedStyle::from_style(&style).unwrap();
932 assert!(resolved.complexity_score() > baseline_score);
933 }
934
935 #[test]
936 fn complexity_score_capped_at_100() {
937 let mut style = create_test_style();
938
939 style.fontsize = "200"; style.outline = "10"; style.shadow = "10"; style.scale_x = "200"; style.angle = "180"; style.bold = "1";
946 style.italic = "1";
947 style.underline = "1";
948 style.strikeout = "1";
949
950 let resolved = ResolvedStyle::from_style(&style).unwrap();
951 assert!(resolved.complexity_score() <= 100); assert!(resolved.complexity_score() > 50); }
954
955 #[test]
956 fn text_formatting_flags_comprehensive() {
957 let mut style = create_test_style();
958
959 style.bold = "1";
961 style.italic = "0";
962 style.underline = "0";
963 style.strikeout = "0";
964 let resolved = ResolvedStyle::from_style(&style).unwrap();
965 assert!(resolved.is_bold());
966 assert!(!resolved.is_italic());
967 assert!(!resolved.is_underline());
968 assert!(!resolved.is_strike_out());
969 assert_eq!(resolved.formatting(), TextFormatting::BOLD);
970
971 style.bold = "0";
973 style.italic = "1";
974 let resolved = ResolvedStyle::from_style(&style).unwrap();
975 assert!(!resolved.is_bold());
976 assert!(resolved.is_italic());
977 assert_eq!(resolved.formatting(), TextFormatting::ITALIC);
978
979 style.italic = "0";
981 style.underline = "1";
982 let resolved = ResolvedStyle::from_style(&style).unwrap();
983 assert!(resolved.is_underline());
984 assert_eq!(resolved.formatting(), TextFormatting::UNDERLINE);
985
986 style.underline = "0";
988 style.strikeout = "1";
989 let resolved = ResolvedStyle::from_style(&style).unwrap();
990 assert!(resolved.is_strike_out());
991 assert_eq!(resolved.formatting(), TextFormatting::STRIKE_OUT);
992
993 style.bold = "1";
995 style.italic = "1";
996 style.underline = "1";
997 style.strikeout = "1";
998 let resolved = ResolvedStyle::from_style(&style).unwrap();
999 assert!(resolved.is_bold());
1000 assert!(resolved.is_italic());
1001 assert!(resolved.is_underline());
1002 assert!(resolved.is_strike_out());
1003 let expected = TextFormatting::BOLD
1004 | TextFormatting::ITALIC
1005 | TextFormatting::UNDERLINE
1006 | TextFormatting::STRIKE_OUT;
1007 assert_eq!(resolved.formatting(), expected);
1008 }
1009
1010 #[test]
1011 fn resolved_style_empty_font_name_uses_default() {
1012 let mut style = create_test_style();
1013 style.fontname = "";
1014
1015 let resolved = ResolvedStyle::from_style(&style).unwrap();
1016 assert_eq!(resolved.font_name(), "Arial");
1017 }
1018
1019 #[test]
1020 #[allow(clippy::float_cmp)]
1021 fn resolved_style_getters_comprehensive() {
1022 let style = create_test_style();
1023 let resolved = ResolvedStyle::from_style(&style).unwrap();
1024
1025 assert_eq!(resolved.font_name(), "Arial");
1027 assert_eq!(resolved.font_size(), 20.0);
1028 assert_eq!(resolved.primary_color(), [255, 255, 255, 0]); assert!(!resolved.has_performance_issues()); let formatting = resolved.formatting();
1032 assert!(!resolved.is_bold());
1033 assert!(!resolved.is_italic());
1034 assert!(!resolved.is_underline());
1035 assert!(!resolved.is_strike_out());
1036 assert_eq!(formatting, TextFormatting::empty());
1037 }
1038
1039 #[test]
1040 fn resolved_style_apply_resolution_scaling_symmetric() {
1041 let style = create_test_style();
1042 let mut resolved = ResolvedStyle::from_style(&style).unwrap();
1043
1044 resolved.apply_resolution_scaling(2.0, 2.0);
1046
1047 assert!((resolved.font_size() - 40.0).abs() < f32::EPSILON); assert!((resolved.spacing() - 0.0).abs() < f32::EPSILON); assert!((resolved.outline() - 4.0).abs() < f32::EPSILON); assert!((resolved.shadow() - 0.0).abs() < f32::EPSILON); assert_eq!(resolved.margin_l(), 20); assert_eq!(resolved.margin_r(), 20); assert_eq!(resolved.margin_t(), 20); assert_eq!(resolved.margin_b(), 20); }
1056
1057 #[test]
1058 fn resolved_style_apply_resolution_scaling_asymmetric() {
1059 let mut style = create_test_style();
1060 style.spacing = "4";
1061 style.shadow = "2";
1062 style.margin_l = "10";
1063 style.margin_r = "20";
1064 style.margin_v = "30";
1065
1066 let mut resolved = ResolvedStyle::from_style(&style).unwrap();
1067
1068 resolved.apply_resolution_scaling(3.0, 2.0);
1070
1071 assert!((resolved.font_size() - 50.0).abs() < f32::EPSILON); assert!((resolved.spacing() - 12.0).abs() < f32::EPSILON); assert!((resolved.outline() - 5.0).abs() < f32::EPSILON); assert!((resolved.shadow() - 5.0).abs() < f32::EPSILON); assert_eq!(resolved.margin_l(), 30); assert_eq!(resolved.margin_r(), 60); assert_eq!(resolved.margin_t(), 60); assert_eq!(resolved.margin_b(), 60); }
1081
1082 #[test]
1083 fn resolved_style_apply_resolution_scaling_downscale() {
1084 let style = create_test_style();
1085 let mut resolved = ResolvedStyle::from_style(&style).unwrap();
1086
1087 resolved.apply_resolution_scaling(0.5, 0.5);
1089
1090 assert!((resolved.font_size() - 10.0).abs() < f32::EPSILON); assert!((resolved.spacing() - 0.0).abs() < f32::EPSILON); assert!((resolved.outline() - 1.0).abs() < f32::EPSILON); assert!((resolved.shadow() - 0.0).abs() < f32::EPSILON); assert_eq!(resolved.margin_l(), 5); assert_eq!(resolved.margin_r(), 5); assert_eq!(resolved.margin_t(), 5); assert_eq!(resolved.margin_b(), 5); }
1099
1100 #[test]
1101 fn resolved_style_apply_resolution_scaling_updates_complexity() {
1102 let mut style = create_test_style();
1103 style.fontsize = "30"; let mut resolved = ResolvedStyle::from_style(&style).unwrap();
1106 let initial_complexity = resolved.complexity_score();
1107
1108 resolved.apply_resolution_scaling(3.0, 3.0);
1110
1111 assert!((resolved.font_size() - 90.0).abs() < f32::EPSILON); assert!(resolved.complexity_score() > initial_complexity); }
1114
1115 #[test]
1116 fn resolved_style_apply_resolution_scaling_preserves_other_properties() {
1117 let mut style = create_test_style();
1118 style.bold = "1";
1119 style.italic = "1";
1120 style.primary_colour = "&H00FF0000"; style.angle = "45";
1122
1123 let mut resolved = ResolvedStyle::from_style(&style).unwrap();
1124 let initial_color = resolved.primary_color();
1125 let initial_angle = resolved.angle;
1126 let initial_formatting = resolved.formatting();
1127
1128 resolved.apply_resolution_scaling(2.0, 2.0);
1130
1131 assert_eq!(resolved.primary_color(), initial_color);
1133 assert!((resolved.angle - initial_angle).abs() < f32::EPSILON);
1134 assert_eq!(resolved.formatting(), initial_formatting);
1135 assert!(resolved.is_bold());
1136 assert!(resolved.is_italic());
1137 }
1138
1139 #[test]
1140 fn resolved_style_spacing_getter() {
1141 let mut style = create_test_style();
1142 style.spacing = "5.5";
1143
1144 let resolved = ResolvedStyle::from_style(&style).unwrap();
1145 assert!((resolved.spacing() - 5.5).abs() < f32::EPSILON);
1146 }
1147
1148 #[test]
1149 fn resolved_style_angle_getter() {
1150 let mut style = create_test_style();
1151 style.angle = "45.5";
1152
1153 let resolved = ResolvedStyle::from_style(&style).unwrap();
1154 assert!((resolved.angle() - 45.5).abs() < f32::EPSILON);
1155 }
1156}