1use super::style::*;
12use crate::visual::Color;
13
14#[derive(Debug, Clone, PartialEq)]
18pub enum Selector {
19 Universal,
21 Tag(String),
23 Class(String),
25 Descendant(Vec<SimpleSelector>),
27}
28
29#[derive(Debug, Clone, PartialEq)]
31pub enum SimpleSelector {
32 Universal,
33 Tag(String),
34 Class(String),
35}
36
37#[derive(Debug, Clone)]
41pub enum AtRule {
42 Page { declarations: Vec<Declaration> },
44}
45
46#[derive(Debug, Clone)]
50pub struct Declaration {
51 pub property: String,
52 pub value: String,
53}
54
55#[derive(Debug, Clone)]
57pub struct CssRule {
58 pub selectors: Vec<Selector>,
59 pub declarations: Vec<Declaration>,
60}
61
62#[derive(Debug, Clone)]
64pub struct Stylesheet {
65 pub rules: Vec<CssRule>,
66 pub at_rules: Vec<AtRule>,
67}
68
69impl Stylesheet {
70 pub fn new() -> Self {
71 Self {
72 rules: Vec::new(),
73 at_rules: Vec::new(),
74 }
75 }
76
77 pub fn is_empty(&self) -> bool {
78 self.rules.is_empty() && self.at_rules.is_empty()
79 }
80}
81
82impl Default for Stylesheet {
83 fn default() -> Self {
84 Self::new()
85 }
86}
87
88pub fn parse_css(css: &str) -> Result<Stylesheet, String> {
92 let css = strip_comments(css);
93 let mut rules = Vec::new();
94 let mut at_rules = Vec::new();
95 let mut pos = 0;
96 let bytes = css.as_bytes();
97 let len = bytes.len();
98
99 while pos < len {
100 while pos < len && bytes[pos].is_ascii_whitespace() {
102 pos += 1;
103 }
104 if pos >= len {
105 break;
106 }
107
108 let selector_start = pos;
110 while pos < len && bytes[pos] != b'{' {
111 pos += 1;
112 }
113 if pos >= len {
114 break;
115 }
116 let selector_str = &css[selector_start..pos].trim();
117 pos += 1; let decl_start = pos;
121 while pos < len && bytes[pos] != b'}' {
122 pos += 1;
123 }
124 if pos >= len {
125 break;
126 }
127 let decl_str = &css[decl_start..pos].trim();
128 pos += 1; if selector_str.is_empty() {
131 continue;
132 }
133
134 if selector_str.starts_with('@') {
136 if selector_str.eq_ignore_ascii_case("@page") {
137 let declarations = parse_declarations(decl_str);
138 if !declarations.is_empty() {
139 at_rules.push(AtRule::Page { declarations });
140 }
141 }
142 continue;
144 }
145
146 let selectors: Vec<Selector> = selector_str
148 .split(',')
149 .filter_map(|s| parse_selector(s.trim()))
150 .collect();
151
152 if selectors.is_empty() {
153 continue;
154 }
155
156 let declarations = parse_declarations(decl_str);
158
159 if !declarations.is_empty() {
160 rules.push(CssRule {
161 selectors,
162 declarations,
163 });
164 }
165 }
166
167 Ok(Stylesheet { rules, at_rules })
168}
169
170fn strip_comments(css: &str) -> String {
172 let mut result = String::with_capacity(css.len());
173 let bytes = css.as_bytes();
174 let len = bytes.len();
175 let mut i = 0;
176
177 while i < len {
178 if i + 1 < len && bytes[i] == b'/' && bytes[i + 1] == b'*' {
179 let mut j = i + 2;
181 while j + 1 < len && !(bytes[j] == b'*' && bytes[j + 1] == b'/') {
182 j += 1;
183 }
184 i = j + 2;
185 } else {
186 result.push(bytes[i] as char);
187 i += 1;
188 }
189 }
190
191 result
192}
193
194fn parse_selector(s: &str) -> Option<Selector> {
196 let s = s.trim();
197 if s.is_empty() {
198 return None;
199 }
200
201 let parts: Vec<&str> = s.split_whitespace().collect();
203 if parts.len() > 1 {
204 let mut simple_selectors = Vec::new();
205 for part in parts {
206 match parse_simple_selector(part) {
207 Some(ss) => simple_selectors.push(ss),
208 None => return None,
209 }
210 }
211 return Some(Selector::Descendant(simple_selectors));
212 }
213
214 let s = s.trim();
216 if s == "*" {
217 return Some(Selector::Universal);
218 }
219 if s.starts_with('.') {
220 return Some(Selector::Class(s[1..].to_string()));
221 }
222 if s.chars()
224 .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
225 {
226 return Some(Selector::Tag(s.to_string()));
227 }
228
229 None
230}
231
232fn parse_simple_selector(s: &str) -> Option<SimpleSelector> {
234 let s = s.trim();
235 if s.is_empty() {
236 return None;
237 }
238
239 if s == "*" {
240 return Some(SimpleSelector::Universal);
241 }
242
243 if let Some(dot_pos) = s.find('.') {
246 let tag = &s[..dot_pos];
247 if tag.is_empty() || tag.chars().all(|c| c.is_alphanumeric() || c == '-') {
248 return Some(SimpleSelector::Tag(tag.to_string()));
249 }
250 return None;
251 }
252
253 if s.starts_with('.') {
254 return Some(SimpleSelector::Class(s[1..].to_string()));
255 }
256
257 if s.chars()
258 .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
259 {
260 return Some(SimpleSelector::Tag(s.to_string()));
261 }
262
263 None
264}
265
266pub(crate) fn parse_declarations(s: &str) -> Vec<Declaration> {
268 let mut declarations = Vec::new();
269 let s = s.trim();
270
271 if s.is_empty() {
272 return declarations;
273 }
274
275 for decl in s.split(';') {
277 let decl = decl.trim();
278 if decl.is_empty() {
279 continue;
280 }
281
282 if let Some(colon_pos) = decl.find(':') {
284 let property = decl[..colon_pos].trim().to_string();
285 let value = decl[colon_pos + 1..].trim().to_string();
286
287 if !property.is_empty() && !value.is_empty() {
288 declarations.push(Declaration { property, value });
289 }
290 }
291 }
292
293 declarations
294}
295
296fn selector_specificity(selector: &Selector) -> u32 {
303 match selector {
304 Selector::Universal => 0,
305 Selector::Tag(_) => 1,
306 Selector::Class(_) => 10,
307 Selector::Descendant(parts) => {
308 let mut spec = 0u32;
309 for part in parts {
310 spec += match part {
311 SimpleSelector::Universal => 0,
312 SimpleSelector::Tag(_) => 1,
313 SimpleSelector::Class(_) => 10,
314 };
315 }
316 spec
317 }
318 }
319}
320
321fn rule_specificity(rule: &CssRule) -> u32 {
323 rule.selectors
324 .iter()
325 .map(selector_specificity)
326 .max()
327 .unwrap_or(0)
328}
329
330pub fn match_selector(
344 selector: &Selector,
345 tag: &str,
346 classes: &[String],
347 ancestor_tags: &[String],
348) -> Option<u32> {
349 match selector {
350 Selector::Universal => Some(0),
351 Selector::Tag(s) => {
352 if s == tag {
353 Some(1)
354 } else {
355 None
356 }
357 }
358 Selector::Class(s) => {
359 if classes.iter().any(|c| c == s) {
360 Some(10)
361 } else {
362 None
363 }
364 }
365 Selector::Descendant(parts) => {
366 let last = parts.last()?;
368 if !match_simple_selector(last, tag, classes) {
369 return None;
370 }
371
372 if parts.len() == 1 {
374 return Some(selector_specificity(selector));
375 }
376
377 let ancestors_to_match = &parts[..parts.len() - 1];
378 let mut ancestor_idx = ancestor_tags.len();
379
380 for part in ancestors_to_match.iter().rev() {
381 let found = loop {
383 if ancestor_idx == 0 {
384 break false;
385 }
386 ancestor_idx -= 1;
387 if match_simple_selector_to_tag(part, &ancestor_tags[ancestor_idx]) {
388 break true;
389 }
390 };
392 if !found {
393 return None;
394 }
395 }
396
397 Some(selector_specificity(selector))
398 }
399 }
400}
401
402fn match_simple_selector(sel: &SimpleSelector, tag: &str, classes: &[String]) -> bool {
404 match sel {
405 SimpleSelector::Universal => true,
406 SimpleSelector::Tag(s) => s == tag,
407 SimpleSelector::Class(s) => classes.iter().any(|c| c == s),
408 }
409}
410
411fn match_simple_selector_to_tag(sel: &SimpleSelector, ancestor_tag: &str) -> bool {
413 match sel {
414 SimpleSelector::Universal => true,
415 SimpleSelector::Tag(s) => s == ancestor_tag,
416 SimpleSelector::Class(_) => {
417 true
421 }
422 }
423}
424
425pub fn node_tag_name(kind: &super::NodeKind) -> &'static str {
429 match kind {
430 super::NodeKind::Document { .. } => "body",
431 super::NodeKind::Heading { level, .. } => match level {
432 1 => "h1",
433 2 => "h2",
434 3 => "h3",
435 4 => "h4",
436 5 => "h5",
437 6 => "h6",
438 _ => "h1",
439 },
440 super::NodeKind::Paragraph { .. } => "p",
441 super::NodeKind::List { ordered, .. } => {
442 if *ordered {
443 "ol"
444 } else {
445 "ul"
446 }
447 }
448 super::NodeKind::ListItem { .. } | super::NodeKind::TaskListItem { .. } => "li",
449 super::NodeKind::Image { .. } => "img",
450 super::NodeKind::CodeBlock { .. } => "pre",
451 super::NodeKind::Blockquote { .. } => "blockquote",
452 super::NodeKind::ThematicBreak => "hr",
453 super::NodeKind::Table { .. } => "table",
454 super::NodeKind::TableRow { .. } => "tr",
455 super::NodeKind::Text { .. } => "span",
456 super::NodeKind::Strong { .. } => "strong",
457 super::NodeKind::Emphasis { .. } => "em",
458 super::NodeKind::InlineCode { .. } => "code",
459 super::NodeKind::Link { .. } => "a",
460 super::NodeKind::Delete { .. } => "del",
461 super::NodeKind::Span { .. } => "span",
462 super::NodeKind::Center { .. } => "center",
463 }
464}
465
466fn apply_declaration(style: &mut Style, property: &str, value: &str) {
470 match property {
471 "font-family" => {
472 let families = parse_font_family(value);
474 if !families.is_empty() {
475 style.font_family = families;
476 }
477 }
478 "font-size" => {
479 if let Some(pt) = parse_length(value, style.font_size_pt) {
480 style.font_size_pt = pt;
481 }
482 }
483 "font-weight" => {
484 style.font_weight = parse_font_weight(value);
485 }
486 "font-style" => {
487 style.font_style = parse_font_style(value);
488 }
489 "color" => {
490 if let Some(c) = parse_color(value) {
491 style.color = c;
492 }
493 }
494 "line-height" => {
495 if let Some(lh) = parse_line_height(value, style.font_size_pt) {
496 style.line_height_pt = lh;
497 }
498 }
499 "text-align" => {
500 style.text_align = parse_text_align(value);
501 }
502 "margin-top" => {
503 if let Some(v) = parse_length(value, style.font_size_pt) {
504 style.margin_top_pt = v;
505 }
506 }
507 "margin-bottom" => {
508 if let Some(v) = parse_length(value, style.font_size_pt) {
509 style.margin_bottom_pt = v;
510 }
511 }
512 "display" => {
513 style.display = parse_display(value);
514 }
515 "width" => {
516 if value == "auto" {
517 style.width = None;
518 } else if let Some(v) = parse_length(value, style.font_size_pt) {
519 style.width = Some(v);
520 }
521 }
522 "background-color" => {
523 if let Some(c) = parse_color(value) {
524 style.background_color = Some(c);
525 }
526 }
527 "page-break-before" => {
528 style.page_break_before = parse_page_break(value);
529 }
530 "page-break-after" => {
531 style.page_break_after = parse_page_break(value);
532 }
533 "letter-spacing" => {
534 if let Some(v) = parse_length(value, style.font_size_pt) {
535 style.letter_spacing = v;
536 }
537 }
538 "padding-top" => {
539 if let Some(v) = parse_length(value, style.font_size_pt) {
540 style.padding_top_pt = v;
541 }
542 }
543 "padding-bottom" => {
544 if let Some(v) = parse_length(value, style.font_size_pt) {
545 style.padding_bottom_pt = v;
546 }
547 }
548 "margin-left" => {
549 if let Some(v) = parse_length(value, style.font_size_pt) {
550 style.margin_left_pt = v;
551 }
552 }
553 "margin-right" => {
554 if let Some(v) = parse_length(value, style.font_size_pt) {
555 style.margin_right_pt = v;
556 }
557 }
558 "padding-left" => {
559 if let Some(v) = parse_length(value, style.font_size_pt) {
560 style.padding_left_pt = v;
561 }
562 }
563 "padding-right" => {
564 if let Some(v) = parse_length(value, style.font_size_pt) {
565 style.padding_right_pt = v;
566 }
567 }
568 "height" => {
569 if value == "auto" {
570 style.height = None;
571 } else if let Some(v) = parse_length(value, style.font_size_pt) {
572 style.height = Some(v);
573 }
574 }
575 "object-fit" => {
576 style.object_fit = parse_object_fit(value);
577 }
578 "border-collapse" => {
579 }
581 "table-header-background" | "table-header-bg" => {
582 if let Some(c) = parse_color(value) {
583 style.table_header_bg = Some(c);
584 }
585 }
586 "table-alt-row-background" | "table-alt-row-bg" => {
587 if let Some(c) = parse_color(value) {
588 style.table_alt_row_bg = Some(c);
589 }
590 }
591 "table-border-color" => {
592 if let Some(c) = parse_color(value) {
593 style.table_border_color = c;
594 }
595 }
596 "table-border-width" => {
597 if let Some(v) = parse_length(value, style.font_size_pt) {
598 style.table_border_width_pt = v;
599 }
600 }
601 "table-cell-padding-horizontal" | "table-cell-padding-h" => {
602 if let Some(v) = parse_length(value, style.font_size_pt) {
603 style.table_cell_padding_h_pt = v;
604 }
605 }
606 "table-cell-padding-vertical" | "table-cell-padding-v" => {
607 if let Some(v) = parse_length(value, style.font_size_pt) {
608 style.table_cell_padding_v_pt = v;
609 }
610 }
611 "list-indent" => {
612 if let Some(v) = parse_length(value, style.font_size_pt) {
613 style.list_indent_pt = Some(v);
614 }
615 }
616 "text-decoration" => {
617 let v = value.trim().to_lowercase();
618 if v == "line-through" {
619 style.text_decoration = crate::ast::TextDecoration::LineThrough;
620 } else if v == "underline" {
621 style.text_decoration = crate::ast::TextDecoration::Underline;
622 } else if v == "none" {
623 style.text_decoration = crate::ast::TextDecoration::None;
624 }
625 }
626 _ => {
627 }
629 }
630}
631
632fn parse_font_family(value: &str) -> Vec<String> {
636 let mut families = Vec::new();
637 let mut current = String::new();
638 let mut in_quote = false;
639 let mut quote_char = ' ';
640 let chars: Vec<char> = value.chars().collect();
641
642 for &c in &chars {
643 if in_quote {
644 if c == quote_char {
645 in_quote = false;
646 if !current.is_empty() {
647 families.push(current.trim().to_string());
648 current = String::new();
649 }
650 } else {
651 current.push(c);
652 }
653 } else if c == '"' || c == '\'' {
654 in_quote = true;
655 quote_char = c;
656 } else if c == ',' {
657 if !current.is_empty() {
658 families.push(current.trim().to_string());
659 current = String::new();
660 }
661 } else if !c.is_whitespace() || !current.is_empty() {
662 current.push(c);
663 }
664 }
665
666 if !current.is_empty() {
667 families.push(current.trim().to_string());
668 }
669
670 families
671}
672
673pub(crate) fn parse_length(value: &str, parent_font_size: f32) -> Option<f32> {
675 let value = value.trim();
676
677 if value == "0" {
679 return Some(0.0);
680 }
681
682 if let Some(v) = value.strip_suffix("pt") {
683 v.trim().parse::<f32>().ok()
684 } else if let Some(v) = value.strip_suffix("px") {
685 v.trim().parse::<f32>().ok()
686 } else if let Some(v) = value.strip_suffix("em") {
687 let em = v.trim().parse::<f32>().ok()?;
688 Some(em * parent_font_size)
689 } else if let Some(v) = value.strip_suffix("mm") {
690 let mm = v.trim().parse::<f32>().ok()?;
691 Some(mm * 72.0 / 25.4)
692 } else if let Some(v) = value.strip_suffix("cm") {
693 let cm = v.trim().parse::<f32>().ok()?;
694 Some(cm * 72.0 / 2.54)
695 } else if let Some(v) = value.strip_suffix("in") {
696 let inches = v.trim().parse::<f32>().ok()?;
697 Some(inches * 72.0)
698 } else if let Some(v) = value.strip_suffix('%') {
699 let pct = v.trim().parse::<f32>().ok()?;
700 Some(pct * parent_font_size / 100.0)
701 } else {
702 value.parse::<f32>().ok()
704 }
705}
706
707fn parse_line_height(value: &str, font_size: f32) -> Option<f32> {
709 let value = value.trim();
710
711 if let Ok(num) = value.parse::<f32>() {
713 return Some(num * font_size);
714 }
715
716 parse_length(value, font_size)
718}
719
720fn parse_font_weight(value: &str) -> FontWeight {
722 match value.trim() {
723 "bold" | "700" | "800" | "900" => FontWeight::Bold,
724 _ => FontWeight::Normal,
725 }
726}
727
728fn parse_font_style(value: &str) -> FontStyle {
730 match value.trim() {
731 "italic" | "oblique" => FontStyle::Italic,
732 _ => FontStyle::Normal,
733 }
734}
735
736fn parse_color(value: &str) -> Option<Color> {
738 let value = value.trim().to_lowercase();
739
740 let named = match value.as_str() {
742 "black" => Color::new(0, 0, 0),
743 "white" => Color::new(255, 255, 255),
744 "red" => Color::new(255, 0, 0),
745 "green" | "lime" => Color::new(0, 128, 0),
746 "blue" => Color::new(0, 0, 255),
747 "yellow" => Color::new(255, 255, 0),
748 "gray" | "grey" => Color::new(128, 128, 128),
749 "silver" => Color::new(192, 192, 192),
750 "maroon" => Color::new(128, 0, 0),
751 "purple" => Color::new(128, 0, 128),
752 "fuchsia" | "magenta" => Color::new(255, 0, 255),
753 "teal" => Color::new(0, 128, 128),
754 "aqua" | "cyan" => Color::new(0, 255, 255),
755 "navy" => Color::new(0, 0, 128),
756 "orange" => Color::new(255, 165, 0),
757 "transparent" => Color::new(0, 0, 0),
758 _ => return parse_hex_color(&value).or_else(|| parse_rgb_color(&value)),
759 };
760
761 Some(named)
762}
763
764fn parse_hex_color(value: &str) -> Option<Color> {
766 let value = value.trim_start_matches('#');
767 if value.len() == 3 {
768 let r = u8::from_str_radix(&value[0..1], 16).ok()? * 17;
769 let g = u8::from_str_radix(&value[1..2], 16).ok()? * 17;
770 let b = u8::from_str_radix(&value[2..3], 16).ok()? * 17;
771 Some(Color::new(r, g, b))
772 } else if value.len() == 6 {
773 let r = u8::from_str_radix(&value[0..2], 16).ok()?;
774 let g = u8::from_str_radix(&value[2..4], 16).ok()?;
775 let b = u8::from_str_radix(&value[4..6], 16).ok()?;
776 Some(Color::new(r, g, b))
777 } else {
778 None
779 }
780}
781
782fn parse_rgb_color(value: &str) -> Option<Color> {
784 let value = value.trim();
785 if !value.starts_with("rgb(") || !value.ends_with(')') {
786 return None;
787 }
788 let inner = value[4..value.len() - 1].trim();
789 let parts: Vec<&str> = inner.split(',').collect();
790 if parts.len() != 3 {
791 return None;
792 }
793 let r = parts[0].trim().parse::<u8>().ok()?;
794 let g = parts[1].trim().parse::<u8>().ok()?;
795 let b = parts[2].trim().parse::<u8>().ok()?;
796 Some(Color::new(r, g, b))
797}
798
799fn parse_text_align(value: &str) -> TextAlign {
801 match value.trim() {
802 "center" => TextAlign::Center,
803 "right" => TextAlign::Right,
804 "justify" => TextAlign::Justify,
805 _ => TextAlign::Left,
806 }
807}
808
809fn parse_display(value: &str) -> Display {
811 match value.trim() {
812 "inline" => Display::Inline,
813 "inline-block" => Display::InlineBlock,
814 "none" => Display::None,
815 _ => Display::Block,
816 }
817}
818
819fn parse_page_break(value: &str) -> PageBreak {
821 match value.trim() {
822 "always" | "page" => PageBreak::Always,
823 "avoid" => PageBreak::Avoid,
824 "left" => PageBreak::Left,
825 "right" => PageBreak::Right,
826 _ => PageBreak::Auto,
827 }
828}
829
830fn parse_object_fit(value: &str) -> ObjectFit {
832 match value.trim() {
833 "cover" => ObjectFit::Cover,
834 "fill" => ObjectFit::Fill,
835 "none" => ObjectFit::None,
836 _ => ObjectFit::Contain,
837 }
838}
839
840fn apply_page_declaration(config: &mut PageConfig, property: &str, value: &str) {
844 match property {
845 "margin-top" => {
846 if let Some(v) = parse_length(value, 10.5) {
847 config.margin_top = Some(v);
848 }
849 }
850 "margin-bottom" => {
851 if let Some(v) = parse_length(value, 10.5) {
852 config.margin_bottom = Some(v);
853 }
854 }
855 "margin-left" => {
856 if let Some(v) = parse_length(value, 10.5) {
857 config.margin_left = Some(v);
858 }
859 }
860 "margin-right" => {
861 if let Some(v) = parse_length(value, 10.5) {
862 config.margin_right = Some(v);
863 }
864 }
865 "margin" => {
866 let parts: Vec<&str> = value.split_whitespace().collect();
867 let vals: Vec<f32> = parts.iter().filter_map(|s| parse_length(s, 10.5)).collect();
868 match vals.len() {
869 1 => {
870 config.margin_top = Some(vals[0]);
871 config.margin_bottom = Some(vals[0]);
872 config.margin_left = Some(vals[0]);
873 config.margin_right = Some(vals[0]);
874 }
875 2 => {
876 config.margin_top = Some(vals[0]);
877 config.margin_bottom = Some(vals[0]);
878 config.margin_left = Some(vals[1]);
879 config.margin_right = Some(vals[1]);
880 }
881 3 => {
882 config.margin_top = Some(vals[0]);
883 config.margin_left = Some(vals[1]);
884 config.margin_right = Some(vals[1]);
885 config.margin_bottom = Some(vals[2]);
886 }
887 _ => {
888 if vals.len() >= 4 {
889 config.margin_top = Some(vals[0]);
890 config.margin_right = Some(vals[1]);
891 config.margin_bottom = Some(vals[2]);
892 config.margin_left = Some(vals[3]);
893 }
894 }
895 }
896 }
897 "size" => {
898 let parts: Vec<&str> = value.split_whitespace().collect();
899 let mut width: Option<f32> = None;
900 let mut height: Option<f32> = None;
901 let mut is_landscape = false;
902 let mut is_portrait = false;
903
904 for part in &parts {
905 let lower = part.to_ascii_lowercase();
906 match lower.as_str() {
907 "landscape" => {
908 is_landscape = true;
909 }
910 "portrait" => {
911 is_portrait = true;
912 }
913 "a3" => {
914 width = Some(841.890);
915 height = Some(1190.551);
916 }
917 "a4" => {
918 width = Some(595.276);
919 height = Some(841.890);
920 }
921 "a5" => {
922 width = Some(419.528);
923 height = Some(595.276);
924 }
925 "a6" => {
926 width = Some(297.638);
927 height = Some(419.528);
928 }
929 "letter" => {
930 width = Some(612.0);
931 height = Some(792.0);
932 }
933 "legal" => {
934 width = Some(612.0);
935 height = Some(1008.0);
936 }
937 "tabloid" | "ledger" => {
938 width = Some(792.0);
939 height = Some(1224.0);
940 }
941 _ => {
942 if let Some(v) = parse_length(part, 10.5) {
943 if width.is_none() {
944 width = Some(v);
945 } else if height.is_none() {
946 height = Some(v);
947 }
948 }
949 }
950 }
951 }
952
953 if is_landscape {
954 if let (Some(w), Some(h)) = (width, height) {
955 config.width = Some(w.max(h));
956 config.height = Some(w.min(h));
957 } else {
958 config.width = Some(841.890);
959 config.height = Some(595.276);
960 }
961 } else if is_portrait {
962 if let (Some(w), Some(h)) = (width, height) {
963 config.width = Some(w.min(h));
964 config.height = Some(w.max(h));
965 }
966 } else {
967 if let Some(w) = width {
968 config.width = Some(w);
969 }
970 if let Some(h) = height {
971 config.height = Some(h);
972 }
973 }
974 }
975 "header" => {
976 let trimmed = value.trim();
977 if trimmed.len() >= 2 && trimmed.starts_with('"') && trimmed.ends_with('"') {
978 config.header = Some(trimmed[1..trimmed.len() - 1].to_string());
979 } else {
980 config.header = Some(trimmed.to_string());
981 }
982 }
983 "footer" => {
984 let trimmed = value.trim();
985 if trimmed.len() >= 2 && trimmed.starts_with('"') && trimmed.ends_with('"') {
986 config.footer = Some(trimmed[1..trimmed.len() - 1].to_string());
987 } else {
988 config.footer = Some(trimmed.to_string());
989 }
990 }
991 "header-font-size" => {
992 config.header_font_size = parse_length(value, 10.5);
993 }
994 "footer-font-size" => {
995 config.footer_font_size = parse_length(value, 10.5);
996 }
997 _ => {}
998 }
999}
1000
1001impl Stylesheet {
1002 pub fn extract_page_config(&self) -> PageConfig {
1004 let mut config = PageConfig::default();
1005 for at_rule in &self.at_rules {
1006 let AtRule::Page { declarations } = at_rule;
1007 for decl in declarations {
1008 apply_page_declaration(&mut config, &decl.property, &decl.value);
1009 }
1010 }
1011 config
1012 }
1013}
1014
1015pub struct StyleResolver {
1026 builtin: Stylesheet,
1028 user: Stylesheet,
1030 strict: bool,
1034 page_config: PageConfig,
1036}
1037
1038impl StyleResolver {
1039 pub fn new(builtin_css: &str) -> Result<Self, String> {
1044 let builtin = parse_css(builtin_css)?;
1045 let page_config = builtin.extract_page_config();
1046 Ok(Self {
1047 builtin,
1048 user: Stylesheet::new(),
1049 strict: false,
1050 page_config,
1051 })
1052 }
1053
1054 pub fn with_strict_mode(mut self, strict: bool) -> Self {
1059 self.strict = strict;
1060 self
1061 }
1062
1063 pub fn with_user_css(self, user_css: &str) -> Result<Self, String> {
1067 if user_css.trim().is_empty() {
1068 return Ok(self);
1069 }
1070 match parse_css(user_css) {
1071 Ok(user) => {
1072 let mut page_config = self.builtin.extract_page_config();
1074 let user_page = user.extract_page_config();
1075 if let Some(v) = user_page.margin_top {
1076 page_config.margin_top = Some(v);
1077 }
1078 if let Some(v) = user_page.margin_bottom {
1079 page_config.margin_bottom = Some(v);
1080 }
1081 if let Some(v) = user_page.margin_left {
1082 page_config.margin_left = Some(v);
1083 }
1084 if let Some(v) = user_page.margin_right {
1085 page_config.margin_right = Some(v);
1086 }
1087 if let Some(v) = user_page.width {
1088 page_config.width = Some(v);
1089 }
1090 if let Some(v) = user_page.height {
1091 page_config.height = Some(v);
1092 }
1093 if let Some(v) = user_page.header {
1094 page_config.header = Some(v);
1095 }
1096 if let Some(v) = user_page.footer {
1097 page_config.footer = Some(v);
1098 }
1099 if let Some(v) = user_page.header_font_size {
1100 page_config.header_font_size = Some(v);
1101 }
1102 if let Some(v) = user_page.footer_font_size {
1103 page_config.footer_font_size = Some(v);
1104 }
1105 Ok(Self {
1106 user,
1107 page_config,
1108 ..self
1109 })
1110 }
1111 Err(e) => {
1112 if self.strict {
1113 Err(e)
1114 } else {
1115 Ok(self)
1116 }
1117 }
1118 }
1119 }
1120
1121 fn all_rules(&self) -> Vec<&CssRule> {
1123 let mut rules: Vec<&CssRule> = self.builtin.rules.iter().collect();
1124 rules.extend(self.user.rules.iter());
1125 rules
1126 }
1127
1128 pub fn page_config(&self) -> &PageConfig {
1130 &self.page_config
1131 }
1132
1133 pub fn resolve_style(
1141 &self,
1142 tag: &str,
1143 classes: &[String],
1144 ancestor_tags: &[String],
1145 parent_style: &Style,
1146 ) -> Style {
1147 let mut style = Style::inherit_from(parent_style);
1148
1149 let mut matches: Vec<(&CssRule, u32)> = Vec::new();
1151 for rule in &self.all_rules() {
1152 for selector in &rule.selectors {
1153 if let Some(specificity) = match_selector(selector, tag, classes, ancestor_tags) {
1154 matches.push((rule, specificity));
1155 break; }
1157 }
1158 }
1159
1160 matches.sort_by_key(|(_, spec)| *spec);
1162
1163 for (rule, _) in &matches {
1165 for decl in &rule.declarations {
1166 apply_declaration(&mut style, &decl.property, &decl.value);
1167 }
1168 }
1169
1170 style
1171 }
1172
1173 pub fn apply_inline_style(&self, style: &mut Style, inline_css: &str) {
1177 let declarations = parse_declarations(inline_css);
1178 for decl in declarations {
1179 apply_declaration(style, &decl.property, &decl.value);
1180 }
1181 }
1182}
1183
1184#[cfg(test)]
1185mod tests {
1186 use super::*;
1187
1188 #[test]
1189 fn test_parse_simple_rules() {
1190 let css = r#"
1191 h1 { font-size: 24pt; font-weight: bold; }
1192 p { font-size: 10.5pt; line-height: 1.5; }
1193 "#;
1194 let stylesheet = parse_css(css).unwrap();
1195 assert_eq!(stylesheet.rules.len(), 2);
1196 assert_eq!(stylesheet.rules[0].declarations.len(), 2);
1197 assert_eq!(stylesheet.rules[1].declarations.len(), 2);
1198 }
1199
1200 #[test]
1201 fn test_parse_descendant_selector() {
1202 let css = r#"
1203 article p { color: #333; }
1204 blockquote p { font-style: italic; }
1205 "#;
1206 let stylesheet = parse_css(css).unwrap();
1207 assert_eq!(stylesheet.rules.len(), 2);
1208 match &stylesheet.rules[0].selectors[0] {
1209 Selector::Descendant(parts) => assert_eq!(parts.len(), 2),
1210 _ => panic!("Expected Descendant selector"),
1211 }
1212 }
1213
1214 #[test]
1215 fn test_parse_comments() {
1216 let css = r#"
1217 /* This is a comment */
1218 h1 { color: red; /* inline comment */ }
1219 "#;
1220 let stylesheet = parse_css(css).unwrap();
1221 assert_eq!(stylesheet.rules.len(), 1);
1222 }
1223
1224 #[test]
1225 fn test_match_tag_selector() {
1226 let css = "h1 { font-size: 24pt; }";
1227 let stylesheet = parse_css(css).unwrap();
1228 let tag = "h1";
1229 let result = match_selector(&stylesheet.rules[0].selectors[0], tag, &[], &[]);
1230 assert!(result.is_some());
1231 }
1232
1233 #[test]
1234 fn test_match_universal_selector() {
1235 let sel = Selector::Universal;
1236 let result = match_selector(&sel, "p", &[], &[]);
1237 assert!(result.is_some());
1238 }
1239
1240 #[test]
1241 fn test_parse_font_family() {
1242 let result = parse_font_family("serif");
1243 assert_eq!(result, vec!["serif"]);
1244
1245 let result = parse_font_family("'Times New Roman', serif");
1246 assert_eq!(result, vec!["Times New Roman", "serif"]);
1247 }
1248
1249 #[test]
1250 fn test_parse_color_hex() {
1251 let c = parse_color("#ff0000").unwrap();
1252 assert_eq!(c.r, 255);
1253 assert_eq!(c.g, 0);
1254 assert_eq!(c.b, 0);
1255
1256 let c = parse_color("#f00").unwrap();
1257 assert_eq!(c.r, 255);
1258 assert_eq!(c.g, 0);
1259 assert_eq!(c.b, 0);
1260 }
1261
1262 #[test]
1263 fn test_parse_color_named() {
1264 let c = parse_color("red").unwrap();
1265 assert_eq!(c.r, 255);
1266
1267 let c = parse_color("black").unwrap();
1268 assert_eq!(c.r, 0);
1269 assert_eq!(c.g, 0);
1270 assert_eq!(c.b, 0);
1271 }
1272
1273 #[test]
1274 fn test_parse_length() {
1275 assert_eq!(parse_length("12pt", 10.0), Some(12.0));
1276 assert_eq!(parse_length("1.5em", 10.0), Some(15.0));
1277 assert_eq!(parse_length("150%", 10.0), Some(15.0));
1278 assert_eq!(parse_length("0", 10.0), Some(0.0));
1279 }
1280
1281 #[test]
1282 fn test_style_resolver() {
1283 let builtin = r#"
1284 body { font-family: serif; font-size: 10.5pt; color: #000; line-height: 1.5; }
1285 h1 { font-size: 24pt; font-weight: bold; margin-bottom: 12pt; }
1286 "#;
1287 let resolver = StyleResolver::new(builtin).unwrap();
1288
1289 let parent = Style::default();
1290 let ancestors = vec!["body".to_string()];
1291
1292 let style = resolver.resolve_style("h1", &[], &ancestors, &parent);
1293 assert_eq!(style.font_size_pt, 24.0);
1294 assert_eq!(style.font_weight, FontWeight::Bold);
1295 assert_eq!(style.font_family[0], "serif");
1297 }
1298
1299 #[test]
1300 fn test_user_css_override() {
1301 let builtin = r#"
1302 body { font-family: serif; font-size: 10.5pt; }
1303 h1 { font-size: 24pt; color: #000; }
1304 "#;
1305 let user = r#"
1306 h1 { font-size: 28pt; color: #333; }
1307 "#;
1308 let resolver = StyleResolver::new(builtin)
1309 .unwrap()
1310 .with_user_css(user)
1311 .unwrap();
1312
1313 let parent = Style::default();
1314 let ancestors = vec!["body".to_string()];
1315
1316 let style = resolver.resolve_style("h1", &[], &ancestors, &parent);
1317 assert_eq!(style.font_size_pt, 28.0);
1319 assert_eq!(style.color.r, 0x33);
1320 assert_eq!(style.color.g, 0x33);
1321 assert_eq!(style.color.b, 0x33);
1322 assert_eq!(style.font_family[0], "serif");
1324 }
1325
1326 #[test]
1327 fn test_specificity_order() {
1328 let builtin = r#"
1329 p { color: red; }
1330 .special { color: blue; }
1331 "#;
1332 let resolver = StyleResolver::new(builtin).unwrap();
1333
1334 let parent = Style::default();
1335 let style = resolver.resolve_style("p", &["special".to_string()], &[], &parent);
1336 assert_eq!(style.color.r, 0);
1338 assert_eq!(style.color.g, 0);
1339 assert_eq!(style.color.b, 255);
1340 }
1341
1342 #[test]
1343 fn test_node_tag_name() {
1344 use super::super::NodeKind;
1345 assert_eq!(
1346 node_tag_name(&NodeKind::Paragraph { children: vec![] }),
1347 "p"
1348 );
1349 assert_eq!(
1350 node_tag_name(&NodeKind::Heading {
1351 level: 1,
1352 children: vec![]
1353 }),
1354 "h1"
1355 );
1356 assert_eq!(
1357 node_tag_name(&NodeKind::Heading {
1358 level: 3,
1359 children: vec![]
1360 }),
1361 "h3"
1362 );
1363 assert_eq!(
1364 node_tag_name(&NodeKind::Strong { children: vec![] }),
1365 "strong"
1366 );
1367 assert_eq!(
1368 node_tag_name(&NodeKind::Image {
1369 src: String::new(),
1370 alt: String::new(),
1371 title: None
1372 }),
1373 "img"
1374 );
1375 assert_eq!(
1376 node_tag_name(&NodeKind::CodeBlock {
1377 code: String::new(),
1378 lang: None
1379 }),
1380 "pre"
1381 );
1382 }
1383
1384 #[test]
1385 fn test_page_at_rule() {
1386 let css = r#"
1387 @page {
1388 margin: 36pt 54pt;
1389 size: A4;
1390 }
1391 "#;
1392 let sheet = parse_css(css).unwrap();
1393 let config = sheet.extract_page_config();
1394 assert_eq!(config.margin_top, Some(36.0));
1395 assert_eq!(config.margin_bottom, Some(36.0));
1396 assert_eq!(config.margin_left, Some(54.0));
1397 assert_eq!(config.margin_right, Some(54.0));
1398 assert_eq!(config.width, Some(595.276));
1399 assert_eq!(config.height, Some(841.890));
1400 }
1401
1402 #[test]
1403 fn test_page_at_rule_margin_shorthand() {
1404 let css = r#"
1405 @page {
1406 margin: 10px 20px 30px 40px;
1407 }
1408 "#;
1409 let sheet = parse_css(css).unwrap();
1410 let config = sheet.extract_page_config();
1411 assert_eq!(config.margin_top, Some(10.0));
1412 assert_eq!(config.margin_right, Some(20.0));
1413 assert_eq!(config.margin_bottom, Some(30.0));
1414 assert_eq!(config.margin_left, Some(40.0));
1415 }
1416
1417 #[test]
1418 fn test_page_at_rule_size_named() {
1419 let css = r#"
1420 @page {
1421 size: Letter;
1422 }
1423 "#;
1424 let sheet = parse_css(css).unwrap();
1425 let config = sheet.extract_page_config();
1426 assert_eq!(config.width, Some(612.0));
1427 assert_eq!(config.height, Some(792.0));
1428 }
1429
1430 #[test]
1431 fn test_page_at_rule_size_a3() {
1432 let css = r#"
1433 @page {
1434 size: A3;
1435 }
1436 "#;
1437 let sheet = parse_css(css).unwrap();
1438 let config = sheet.extract_page_config();
1439 assert_eq!(config.width, Some(841.890));
1440 assert_eq!(config.height, Some(1190.551));
1441 }
1442
1443 #[test]
1444 fn test_page_at_rule_size_legal() {
1445 let css = r#"
1446 @page {
1447 size: Legal;
1448 }
1449 "#;
1450 let sheet = parse_css(css).unwrap();
1451 let config = sheet.extract_page_config();
1452 assert_eq!(config.width, Some(612.0));
1453 assert_eq!(config.height, Some(1008.0));
1454 }
1455
1456 #[test]
1457 fn test_page_at_rule_size_tabloid() {
1458 let css = r#"
1459 @page {
1460 size: Tabloid;
1461 }
1462 "#;
1463 let sheet = parse_css(css).unwrap();
1464 let config = sheet.extract_page_config();
1465 assert_eq!(config.width, Some(792.0));
1466 assert_eq!(config.height, Some(1224.0));
1467 }
1468
1469 #[test]
1470 fn test_page_at_rule_size_landscape() {
1471 let css = r#"
1472 @page {
1473 size: landscape;
1474 }
1475 "#;
1476 let sheet = parse_css(css).unwrap();
1477 let config = sheet.extract_page_config();
1478 assert_eq!(config.width, Some(841.890));
1480 assert_eq!(config.height, Some(595.276));
1481 }
1482
1483 #[test]
1484 fn test_page_at_rule_size_a4_landscape() {
1485 let css = r#"
1486 @page {
1487 size: A4 landscape;
1488 }
1489 "#;
1490 let sheet = parse_css(css).unwrap();
1491 let config = sheet.extract_page_config();
1492 assert_eq!(config.width, Some(841.890));
1493 assert_eq!(config.height, Some(595.276));
1494 }
1495
1496 #[test]
1497 fn test_page_at_rule_size_custom_landscape() {
1498 let css = r#"
1499 @page {
1500 size: 600pt 800pt landscape;
1501 }
1502 "#;
1503 let sheet = parse_css(css).unwrap();
1504 let config = sheet.extract_page_config();
1505 assert_eq!(config.width, Some(800.0));
1506 assert_eq!(config.height, Some(600.0));
1507 }
1508
1509 #[test]
1510 fn test_page_at_rule_size_portrait() {
1511 let css = r#"
1512 @page {
1513 size: Letter portrait;
1514 }
1515 "#;
1516 let sheet = parse_css(css).unwrap();
1517 let config = sheet.extract_page_config();
1518 assert_eq!(config.width, Some(612.0));
1519 assert_eq!(config.height, Some(792.0));
1520 }
1521}