Skip to main content

rapiddoc_render/
lib.rs

1//! 文本和表格输出器;保持 Unicode 与空格,不把输入文字解释为 HTML。
2
3use rapiddoc_core::{Error, ErrorCode};
4use rapiddoc_native::grid::Table;
5pub mod quality;
6pub mod structure;
7pub mod wired;
8
9/// 转义文本节点和属性值中的 HTML 特殊字符,保留 Unicode 与连续空格。
10pub fn escape_html(text: &str) -> String {
11    let mut result = String::with_capacity(text.len());
12    for value in text.chars() {
13        match value {
14            '&' => result.push_str("&"),
15            '<' => result.push_str("&lt;"),
16            '>' => result.push_str("&gt;"),
17            '"' => result.push_str("&quot;"),
18            '\'' => result.push_str("&#39;"),
19            _ => result.push(value),
20        }
21    }
22    result
23}
24
25/// 输出带 rowspan/colspan 的 HTML 表格,适合嵌入 Markdown;不猜测首行是否表头。
26pub fn table_html(table: &Table, max_cells: usize) -> Result<String, Error> {
27    let max_cells = rapiddoc_core::resources::limit(max_cells);
28    let count = table
29        .rows
30        .checked_mul(table.columns)
31        .ok_or_else(|| invalid("表格尺寸溢出"))?;
32    if count == 0 || count > max_cells {
33        return Err(invalid("表格为空或超过输出预算"));
34    }
35    let mut owner = vec![false; count];
36    let mut starts = vec![None; count];
37    for (index, cell) in table.cells.iter().enumerate() {
38        let last_row = cell
39            .row
40            .checked_add(cell.row_span)
41            .ok_or_else(|| invalid("单元格跨度溢出"))?;
42        let last_column = cell
43            .column
44            .checked_add(cell.column_span)
45            .ok_or_else(|| invalid("单元格跨度溢出"))?;
46        if cell.row_span == 0
47            || cell.column_span == 0
48            || last_row > table.rows
49            || last_column > table.columns
50        {
51            return Err(invalid("单元格跨度越界"));
52        }
53        for row in cell.row..last_row {
54            for column in cell.column..last_column {
55                let slot = &mut owner[row * table.columns + column];
56                if *slot {
57                    return Err(invalid("单元格重复覆盖"));
58                }
59                *slot = true;
60            }
61        }
62        starts[cell.row * table.columns + cell.column] = Some(index);
63    }
64    if owner.iter().any(|value| !*value) {
65        return Err(invalid("表格存在未覆盖位置"));
66    }
67    let mut html = String::from("<table>\n");
68    for row in 0..table.rows {
69        html.push_str("<tr>");
70        for column in 0..table.columns {
71            if let Some(index) = starts[row * table.columns + column] {
72                let cell = &table.cells[index];
73                html.push_str("<td");
74                if cell.row_span > 1 {
75                    html.push_str(&format!(" rowspan=\"{}\"", cell.row_span));
76                }
77                if cell.column_span > 1 {
78                    html.push_str(&format!(" colspan=\"{}\"", cell.column_span));
79                }
80                html.push('>');
81                let value = escape_html(&cell.text)
82                    .replace("\r\n", "\n")
83                    .replace('\n', "<br/>");
84                html.push_str(&value);
85                html.push_str("</td>");
86            }
87        }
88        html.push_str("</tr>\n");
89    }
90    html.push_str("</table>");
91    Ok(html)
92}
93
94/// 输出器拒绝不完整 IR,避免生成看似成功但丢格子的表格。
95fn invalid(message: &str) -> Error {
96    Error {
97        code: ErrorCode::InvalidInput,
98        message: message.into(),
99    }
100}