Skip to main content

nagisa_render/
model.rs

1//! 文档模型(IR)—— 两个前端(标记解析 [`markup`](crate::parse_markup) / 构建器
2//! [`Doc`](crate::Doc))的共同产物,也是版式引擎的唯一输入。块级 + 行内 + 可叠加的文字样式,
3//! 全是普通数据,不含渲染状态。一般不直接构造,用构建器或标记文本得到。
4
5use std::path::PathBuf;
6
7/// 一份文档:从上到下排布的块序列。
8#[derive(Clone, Debug, Default)]
9pub struct Document {
10    /// 块序列。
11    pub blocks: Vec<Block>,
12}
13
14/// 块级元素。
15#[derive(Clone, Debug)]
16pub enum Block {
17    /// 标题(h1–h6)。
18    Heading {
19        /// 级别,取 1..=6。
20        level: u8,
21        /// 行内内容。
22        inlines: Vec<Inline>,
23        /// 水平对齐。
24        align: Align,
25    },
26    /// 段落。
27    Paragraph {
28        /// 行内内容。
29        inlines: Vec<Inline>,
30        /// 水平对齐。
31        align: Align,
32    },
33    /// 有序 / 无序列表(项内容是块序列,可嵌套、可多段)。
34    List(List),
35    /// 引用块(裹块,可嵌套)。
36    Quote(Vec<Block>),
37    /// 代码块(等宽、带底色;不做语法高亮)。
38    Code {
39        /// 语言标签;有则在代码盒右上角渲染成小标签,可缺。
40        lang: Option<String>,
41        /// 代码原文(保留换行)。
42        text: String,
43    },
44    /// 分割线。
45    Divider,
46    /// 块级图片(可带宽度 / 对齐 / 图注)。
47    Image(BlockImage),
48    /// 多栏并排。
49    Columns(Columns),
50    /// 表格。
51    Table(Table),
52}
53
54/// 表格。`cols` 给各列对齐与可选限宽(短于列数时,缺的列按默认:左对齐 + 自适应)。
55#[derive(Clone, Debug)]
56pub struct Table {
57    /// 表头行;`None` = 无表头。
58    pub header: Option<Vec<Cell>>,
59    /// 数据行。
60    pub rows: Vec<Vec<Cell>>,
61    /// 各列规格(对齐 / 限宽)。
62    pub cols: Vec<ColSpec>,
63    /// 紧凑度与网格样式。
64    pub style: TableStyle,
65}
66
67/// 表格的紧凑度与网格样式。
68#[derive(Clone, Debug)]
69pub struct TableStyle {
70    /// 单元格左右内边距(逻辑像素);`None` = 默认。越小列越紧凑。
71    pub pad_x: Option<f32>,
72    /// 单元格上下内边距(逻辑像素);`None` = 默认。越小行越紧凑(行距越小)。
73    pub pad_y: Option<f32>,
74    /// 网格线开关。
75    pub grid: TableGrid,
76    /// 表头浅底,默认开。
77    pub header_fill: bool,
78}
79
80impl Default for TableStyle {
81    fn default() -> Self {
82        Self { pad_x: None, pad_y: None, grid: TableGrid::default(), header_fill: true }
83    }
84}
85
86/// 网格线开关,默认全开。
87#[derive(Clone, Copy, Debug)]
88pub struct TableGrid {
89    /// 外框线。
90    pub outer: bool,
91    /// 列竖线。
92    pub vertical: bool,
93    /// 行横线。
94    pub horizontal: bool,
95}
96
97impl Default for TableGrid {
98    fn default() -> Self {
99        Self { outer: true, vertical: true, horizontal: true }
100    }
101}
102
103/// 列规格:对齐 + 可选限宽。
104#[derive(Clone, Debug)]
105pub struct ColSpec {
106    /// 该列对齐。
107    pub align: Align,
108    /// 限宽;`None` = 按内容自适应。
109    pub width: Option<Length>,
110}
111
112impl Default for ColSpec {
113    fn default() -> Self {
114        Self { align: Align::Left, width: None }
115    }
116}
117
118/// 单元格:行内内容(按列宽自动换行)+ 可选背景填色。
119#[derive(Clone, Debug)]
120pub struct Cell {
121    /// 单元格的行内内容。
122    pub inlines: Vec<Inline>,
123    /// 背景填色;`None` = 无(随表)。
124    pub bg: Option<Color>,
125}
126
127/// 多栏容器:各栏并排,行高取最高栏。
128#[derive(Clone, Debug)]
129pub struct Columns {
130    /// 各栏。
131    pub cols: Vec<Column>,
132    /// 栏间距(逻辑像素);`None` = 主题默认。
133    pub gap: Option<f32>,
134}
135
136/// 一栏:块内容 + 宽度权重(按权重瓜分可用宽,默认 1.0)。
137#[derive(Clone, Debug)]
138pub struct Column {
139    /// 栏内容。
140    pub blocks: Vec<Block>,
141    /// 宽度权重。
142    pub weight: f32,
143}
144
145/// 列表。
146#[derive(Clone, Debug)]
147pub struct List {
148    /// 有序 / 无序。
149    pub kind: ListKind,
150    /// 有序列表的起始序号(无序忽略)。
151    pub start: u32,
152    /// 列表项。
153    pub items: Vec<ListItem>,
154}
155
156/// 列表种类。
157#[derive(Clone, Copy, Debug, PartialEq, Eq)]
158pub enum ListKind {
159    /// 无序(项目符号)。
160    Unordered,
161    /// 有序(序号)。
162    Ordered,
163}
164
165/// 列表项:内容是块序列,故支持多段与嵌套子列表。
166#[derive(Clone, Debug)]
167pub struct ListItem {
168    /// 项内容。
169    pub blocks: Vec<Block>,
170    /// 任务复选标记:`None` = 普通项;`Some(已完成)` = 渲染成复选标记(`□` / `✓`),
171    /// 对应标记文本的 `- [ ]` / `- [x]`。
172    pub check: Option<bool>,
173}
174
175/// 块级图片。
176#[derive(Clone, Debug)]
177pub struct BlockImage {
178    /// 图片来源。
179    pub src: ImageSource,
180    /// 显示宽度;`None` = 适配内容宽(不超出)。
181    pub width: Option<Length>,
182    /// 水平对齐。
183    pub align: Align,
184    /// 图注(排在图下方,居中小字);`None` = 无。
185    pub caption: Option<Vec<Inline>>,
186}
187
188/// 行内元素。
189#[derive(Clone, Debug)]
190pub enum Inline {
191    /// 一段带样式的文字。
192    Text {
193        /// 文字。
194        text: String,
195        /// 样式。
196        style: TextStyle,
197    },
198    /// 行内代码(等宽 + 浅底)。
199    Code(String),
200    /// 硬换行。
201    LineBreak,
202}
203
204/// 可叠加的文字样式。span 嵌套时逐字段合并。
205#[derive(Clone, Debug, PartialEq)]
206pub struct TextStyle {
207    /// 字重(CSS 习惯值:细 300 / 常规 400 / 粗 700,内置字体 100–900 都有真实例)。
208    /// `None` = 跟随语境:正文常规,标题 / 表头加粗。
209    pub weight: Option<u16>,
210    /// 斜体。
211    pub italic: bool,
212    /// 下划线。
213    pub underline: bool,
214    /// 删除线。
215    pub strike: bool,
216    /// 文字色;`None` = 用主题文字色。
217    pub color: Option<Color>,
218    /// 高亮底色;`None` = 无高亮。
219    pub highlight: Option<Highlight>,
220    /// 相对基准字号的倍率(默认 1.0)。
221    pub size: f32,
222    /// 字族角色。
223    pub font: FontRole,
224    /// 链接文字(标记文本 `[文字](URL)` 的产物):无显式 `color` 时按主题强调色渲染。
225    /// 图片不可点,URL 本身不展示。
226    pub link: bool,
227}
228
229impl Default for TextStyle {
230    fn default() -> Self {
231        Self {
232            weight: None,
233            italic: false,
234            underline: false,
235            strike: false,
236            color: None,
237            highlight: None,
238            size: 1.0,
239            font: FontRole::Sans,
240            link: false,
241        }
242    }
243}
244
245/// 高亮底色来源。
246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247pub enum Highlight {
248    /// 跟随主题默认高亮色(随亮 / 暗变)。
249    Theme,
250    /// 指定具体色。
251    Custom(Color),
252}
253
254/// 字族角色。`Named` 直接按字族名匹配,匹配不到回退 Sans。
255#[derive(Clone, Debug, PartialEq)]
256pub enum FontRole {
257    /// 无衬线(默认正文)。
258    Sans,
259    /// 衬线。
260    Serif,
261    /// 等宽。
262    Mono,
263    /// 楷体。
264    Kai,
265    /// 指定字族名。
266    Named(String),
267}
268
269/// 水平对齐。
270#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
271pub enum Align {
272    /// 左对齐(默认)。
273    #[default]
274    Left,
275    /// 居中。
276    Center,
277    /// 右对齐。
278    Right,
279    /// 两端对齐。
280    Justify,
281}
282
283/// RGBA 颜色(每通道 8 位,非预乘)。
284#[derive(Clone, Copy, Debug, PartialEq, Eq)]
285pub struct Color {
286    /// 红。
287    pub r: u8,
288    /// 绿。
289    pub g: u8,
290    /// 蓝。
291    pub b: u8,
292    /// 不透明度(255 = 不透明)。
293    pub a: u8,
294}
295
296impl Color {
297    /// 不透明色。
298    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
299        Self { r, g, b, a: 255 }
300    }
301
302    /// 带 alpha 的色。
303    pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
304        Self { r, g, b, a }
305    }
306
307    /// 解析十六进制色:`#rgb` / `#rrggbb` / `#rrggbbaa`(井号可省,大小写不限)。
308    /// 非法返回 `None`。`#rgb` 每位扩成两位(`f` → `ff`)。
309    pub fn hex(s: &str) -> Option<Self> {
310        let h = s.strip_prefix('#').unwrap_or(s);
311        if !h.bytes().all(|b| b.is_ascii_hexdigit()) {
312            return None;
313        }
314        let n = |slice: &str| u8::from_str_radix(slice, 16).ok();
315        match h.len() {
316            3 => {
317                let b = h.as_bytes();
318                let dup = |c: u8| {
319                    let d = (c as char).to_digit(16)? as u8;
320                    Some(d << 4 | d)
321                };
322                Some(Self::rgb(dup(b[0])?, dup(b[1])?, dup(b[2])?))
323            }
324            6 => Some(Self::rgb(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?)),
325            8 => Some(Self::rgba(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?, n(&h[6..8])?)),
326            _ => None,
327        }
328    }
329}
330
331/// 长度:绝对像素,或相对内容宽的百分比。
332#[derive(Clone, Copy, Debug, PartialEq)]
333pub enum Length {
334    /// 绝对逻辑像素。
335    Px(f32),
336    /// 内容宽的百分比(0–100)。
337    Percent(f32),
338}
339
340/// 图片来源。引擎不联网:URL 由调用方下好,以 `Bytes` 传入。
341#[derive(Clone, Debug)]
342pub enum ImageSource {
343    /// 已加载的图片字节。
344    Bytes(Vec<u8>),
345    /// 磁盘路径,渲染时读取。
346    Path(PathBuf),
347    /// 具名引用(标记文本里的 `@名字`),渲染时从 [`RenderOptions::images`](crate::RenderOptions::images) 取字节。
348    Named(String),
349}