Skip to main content

liepress/ast/
css.rs

1//! CSS 样式表解析与样式解析模块
2//!
3//! 实现 CSS 核心子集:标签选择器、类选择器、后代组合器、通用选择器。
4//! 支持字体、颜色、字号、边距、分页等核心属性。
5//!
6//! 设计目标:
7//! - 轻量级:只实现必要的 CSS 子集,不引入完整 CSS 引擎
8//! - 与现有 Style 结构体无缝集成
9//! - 支持内置样式 + 用户 CSS 覆盖
10
11use super::style::*;
12use crate::visual::Color;
13
14// ─── CSS 选择器 ──────────────────────────────────────────────
15
16/// CSS 选择器类型
17#[derive(Debug, Clone, PartialEq)]
18pub enum Selector {
19    /// 通用选择器 `*`
20    Universal,
21    /// 标签选择器 `h1`, `p`, `table`
22    Tag(String),
23    /// 类选择器 `.title`, `.author`
24    Class(String),
25    /// 后代组合器 `article p`, `blockquote p`
26    Descendant(Vec<SimpleSelector>),
27}
28
29/// 简单选择器(选择器序列中的一环)
30#[derive(Debug, Clone, PartialEq)]
31pub enum SimpleSelector {
32    Universal,
33    Tag(String),
34    Class(String),
35}
36
37// ─── CSS at-rule ──────────────────────────────────────────────
38
39/// CSS at-rule(如 @page)
40#[derive(Debug, Clone)]
41pub enum AtRule {
42    /// @page 规则,包含声明块
43    Page { declarations: Vec<Declaration> },
44}
45
46// ─── CSS 规则 ────────────────────────────────────────────────
47
48/// 解析后的 CSS 声明
49#[derive(Debug, Clone)]
50pub struct Declaration {
51    pub property: String,
52    pub value: String,
53}
54
55/// 解析后的 CSS 规则
56#[derive(Debug, Clone)]
57pub struct CssRule {
58    pub selectors: Vec<Selector>,
59    pub declarations: Vec<Declaration>,
60}
61
62/// 样式表
63#[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
88// ─── CSS 解析器 ─────────────────────────────────────────────
89
90/// 解析 CSS 文本为样式表
91pub 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        // 跳过空白
101        while pos < len && bytes[pos].is_ascii_whitespace() {
102            pos += 1;
103        }
104        if pos >= len {
105            break;
106        }
107
108        // 解析选择器列表(直到遇到 `{`)
109        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; // 跳过 `{`
118
119        // 解析声明(直到遇到 `}`)
120        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; // 跳过 `}`
129
130        if selector_str.is_empty() {
131            continue;
132        }
133
134        // 检查 at-rule(如 @page)
135        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            // 其他 at-rule 忽略
143            continue;
144        }
145
146        // 解析选择器(支持逗号分隔的多个选择器)
147        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        // 解析声明
157        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
170/// 去除 CSS 注释
171fn 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            // 查找注释结束
180            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
194/// 解析单个选择器
195fn parse_selector(s: &str) -> Option<Selector> {
196    let s = s.trim();
197    if s.is_empty() {
198        return None;
199    }
200
201    // 检查是否有后代组合器(空格分隔)
202    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    // 单个简单选择器
215    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    // 标签选择器,验证是合法标识符
223    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
232/// 解析简单选择器(如 `h1`, `.class`, `div.class`)
233fn 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    // 处理 `tag.class` 形式 - 目前只取 tag
244    // 未来可以扩展为同时匹配 tag 和 class
245    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
266/// 解析声明块
267pub(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    // 按分号分割声明
276    for decl in s.split(';') {
277        let decl = decl.trim();
278        if decl.is_empty() {
279            continue;
280        }
281
282        // 找冒号分割属性名和值
283        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
296// ─── 特异性计算 ─────────────────────────────────────────────
297
298/// 计算选择器的特异性
299///
300/// CSS 标准:ID=100, 类=10, 标签=1
301/// 返回值:特异性值(越高优先级越高)
302fn 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
321/// 计算规则的最大特异性(取所有选择器中的最大值)
322fn rule_specificity(rule: &CssRule) -> u32 {
323    rule.selectors
324        .iter()
325        .map(selector_specificity)
326        .max()
327        .unwrap_or(0)
328}
329
330// ─── 选择器匹配 ─────────────────────────────────────────────
331
332/// 匹配单个选择器到节点
333///
334/// # 参数
335/// - `selector`: CSS 选择器
336/// - `tag`: 节点的标签名
337/// - `classes`: 节点的类名列表
338/// - `ancestor_tags`: 祖先节点的标签名列表(从近到远)
339///
340/// # 返回值
341/// - `Some(specificity)`: 匹配成功,返回特异性值
342/// - `None`: 匹配失败
343pub 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            // 最后一个简单选择器匹配当前节点
367            let last = parts.last()?;
368            if !match_simple_selector(last, tag, classes) {
369                return None;
370            }
371
372            // 前面的部分必须在祖先链中匹配(从近到远)
373            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                // 从最近的祖先开始找
382                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                    // 对于后代组合器,继续向上搜索
391                };
392                if !found {
393                    return None;
394                }
395            }
396
397            Some(selector_specificity(selector))
398        }
399    }
400}
401
402/// 匹配简单选择器到节点
403fn 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
411/// 匹配简单选择器到祖先标签(只匹配标签名)
412fn 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
418            // 因为我们在树构建时没有完整的类名信息用于祖先匹配
419            // 这是一个简化的做法
420            true
421        }
422    }
423}
424
425// ─── 标签名映射 ─────────────────────────────────────────────
426
427/// 将 MDAST 节点类型映射为 CSS 标签名
428pub 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
466// ─── 样式解析 ───────────────────────────────────────────────
467
468/// 将 CSS 声明解析并应用到 Style 上
469fn apply_declaration(style: &mut Style, property: &str, value: &str) {
470    match property {
471        "font-family" => {
472            // 解析字体家族列表(逗号分隔,支持引号)
473            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            // 表格属性 - 简单跳过
580        }
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            // 未知属性,忽略
628        }
629    }
630}
631
632// ─── CSS 值解析器 ───────────────────────────────────────────
633
634/// 解析字体家族列表
635fn 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
673/// 解析长度值(支持 pt, px, em, %, mm, cm, in)
674pub(crate) fn parse_length(value: &str, parent_font_size: f32) -> Option<f32> {
675    let value = value.trim();
676
677    // 处理 `0`(无单位)
678    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        // 尝试直接解析为数字(视为 pt)
703        value.parse::<f32>().ok()
704    }
705}
706
707/// 解析行高
708fn parse_line_height(value: &str, font_size: f32) -> Option<f32> {
709    let value = value.trim();
710
711    // 无单位数字(相对于 font-size 的倍数)
712    if let Ok(num) = value.parse::<f32>() {
713        return Some(num * font_size);
714    }
715
716    // 带单位的值
717    parse_length(value, font_size)
718}
719
720/// 解析字重
721fn parse_font_weight(value: &str) -> FontWeight {
722    match value.trim() {
723        "bold" | "700" | "800" | "900" => FontWeight::Bold,
724        _ => FontWeight::Normal,
725    }
726}
727
728/// 解析字体样式
729fn parse_font_style(value: &str) -> FontStyle {
730    match value.trim() {
731        "italic" | "oblique" => FontStyle::Italic,
732        _ => FontStyle::Normal,
733    }
734}
735
736/// 解析颜色
737fn parse_color(value: &str) -> Option<Color> {
738    let value = value.trim().to_lowercase();
739
740    // 命名颜色
741    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
764/// 解析十六进制颜色 (#RGB, #RRGGBB)
765fn 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
782/// 解析 rgb() 颜色
783fn 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
799/// 解析文本对齐
800fn 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
809/// 解析显示类型
810fn 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
819/// 解析分页控制
820fn 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
830/// 解析 object-fit
831fn 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
840// ─── 页面配置提取 ─────────────────────────────────────────────
841
842/// 将页面相关的 CSS 声明应用到 PageConfig
843fn 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    /// 从样式表中提取页面配置(合并所有 @page 规则)
1003    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
1015// ─── 样式解析入口 ───────────────────────────────────────────
1016
1017/// 样式解析上下文
1018///
1019/// 包含解析后的样式表和可选的外部外部样式表。
1020/// 用户样式表优先级高于内置样式表。
1021///
1022/// 支持严格模式(strict mode):
1023/// - 严格模式:CSS 解析失败时返回错误
1024/// - 非严格模式(默认):CSS 解析失败时静默忽略,继续使用已有样式
1025pub struct StyleResolver {
1026    /// 内置样式表
1027    builtin: Stylesheet,
1028    /// 用户提供的覆盖样式表
1029    user: Stylesheet,
1030    /// 严格模式开关
1031    /// - true: CSS 解析错误会传播
1032    /// - false(默认): CSS 解析错误被静默忽略
1033    strict: bool,
1034    /// 从 @page 规则提取的页面配置
1035    page_config: PageConfig,
1036}
1037
1038impl StyleResolver {
1039    /// 使用内置样式表创建解析器
1040    ///
1041    /// 内置 CSS 始终以严格模式解析(如果内置 CSS 失效,说明有 bug)。
1042    /// 用户 CSS 和内联 CSS 的严格/非严格行为由 `with_strict_mode` 控制。
1043    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    /// 设置严格模式
1055    ///
1056    /// - `true`: 用户 CSS 解析失败时返回错误
1057    /// - `false`(默认): 用户 CSS 解析失败时静默忽略
1058    pub fn with_strict_mode(mut self, strict: bool) -> Self {
1059        self.strict = strict;
1060        self
1061    }
1062
1063    /// 添加用户 CSS 覆盖
1064    ///
1065    /// 在非严格模式下,如果 CSS 解析失败会静默忽略并返回自身。
1066    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                // 合并页面配置:用户覆盖内置
1073                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    /// 合并所有规则(用户规则在最后,优先级更高)
1122    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    /// 获取从 @page 规则提取的页面配置
1129    pub fn page_config(&self) -> &PageConfig {
1130        &self.page_config
1131    }
1132
1133    /// 解析节点的最终样式
1134    ///
1135    /// # 参数
1136    /// - `tag`: 节点的 CSS 标签名
1137    /// - `classes`: 节点的类名列表
1138    /// - `ancestor_tags`: 祖先标签名列表(从根到父节点)
1139    /// - `parent_style`: 父节点的计算后样式(用于继承)
1140    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        // 收集所有匹配的规则
1150        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; // 一条规则只需要匹配一个选择器
1156                }
1157            }
1158        }
1159
1160        // 按特异性排序(CSS 标准:低优先级先应用,然后被高优先级覆盖)
1161        matches.sort_by_key(|(_, spec)| *spec);
1162
1163        // 依次应用声明
1164        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    /// 解析并应用内联 CSS 样式字符串(style 属性值)
1174    ///
1175    /// 例如: `"color: red; font-size: 12pt"` → 应用到 style 上
1176    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        // 继承自 body 的 font-family
1296        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        // 用户 CSS 覆盖了 font-size 和 color
1318        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        // font-family 继承自 body
1323        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        // .special 特异性更高(10 > 1),所以 color 应该是 blue
1337        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        // landscape without size name defaults to A4 landscape (swapped)
1479        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}