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    /// 等比放大)。默认关——窄表保持自然宽。
80    pub expand: bool,
81}
82
83impl Default for TableStyle {
84    fn default() -> Self {
85        Self { pad_x: None, pad_y: None, grid: TableGrid::default(), header_fill: true, expand: false }
86    }
87}
88
89/// 网格线开关,默认全开。
90#[derive(Clone, Copy, Debug)]
91pub struct TableGrid {
92    /// 外框线。
93    pub outer: bool,
94    /// 列竖线。
95    pub vertical: bool,
96    /// 行横线。
97    pub horizontal: bool,
98}
99
100impl Default for TableGrid {
101    fn default() -> Self {
102        Self { outer: true, vertical: true, horizontal: true }
103    }
104}
105
106/// 列规格:对齐 + 可选限宽。
107#[derive(Clone, Debug)]
108pub struct ColSpec {
109    /// 该列对齐。
110    pub align: Align,
111    /// 限宽;`None` = 按内容自适应。
112    pub width: Option<Length>,
113}
114
115impl Default for ColSpec {
116    fn default() -> Self {
117        Self { align: Align::Left, width: None }
118    }
119}
120
121/// 单元格:行内内容(按列宽自动换行)+ 可选背景填色。
122#[derive(Clone, Debug)]
123pub struct Cell {
124    /// 单元格的行内内容。
125    pub inlines: Vec<Inline>,
126    /// 背景填色;`None` = 无(随表)。
127    pub bg: Option<Color>,
128}
129
130/// 多栏容器:各栏并排,行高取最高栏。
131#[derive(Clone, Debug)]
132pub struct Columns {
133    /// 各栏。
134    pub cols: Vec<Column>,
135    /// 栏间距(逻辑像素);`None` = 主题默认。
136    pub gap: Option<f32>,
137}
138
139/// 一栏:块内容 + 宽度权重(按权重瓜分可用宽,默认 1.0)。
140#[derive(Clone, Debug)]
141pub struct Column {
142    /// 栏内容。
143    pub blocks: Vec<Block>,
144    /// 宽度权重。
145    pub weight: f32,
146}
147
148/// 列表。
149#[derive(Clone, Debug)]
150pub struct List {
151    /// 有序 / 无序。
152    pub kind: ListKind,
153    /// 有序列表的起始序号(无序忽略)。
154    pub start: u32,
155    /// 列表项。
156    pub items: Vec<ListItem>,
157}
158
159/// 列表种类。
160#[derive(Clone, Copy, Debug, PartialEq, Eq)]
161pub enum ListKind {
162    /// 无序(项目符号)。
163    Unordered,
164    /// 有序(序号)。
165    Ordered,
166}
167
168/// 列表项:内容是块序列,故支持多段与嵌套子列表。
169#[derive(Clone, Debug)]
170pub struct ListItem {
171    /// 项内容。
172    pub blocks: Vec<Block>,
173    /// 任务复选标记:`None` = 普通项;`Some(已完成)` = 渲染成复选标记(`□` / `✓`),
174    /// 对应标记文本的 `- [ ]` / `- [x]`。
175    pub check: Option<bool>,
176}
177
178/// 块级图片。
179#[derive(Clone, Debug)]
180pub struct BlockImage {
181    /// 图片来源。
182    pub src: ImageSource,
183    /// 显示宽度;`None` = 适配内容宽(不超出)。
184    pub width: Option<Length>,
185    /// 水平对齐。
186    pub align: Align,
187    /// 图注(排在图下方,居中小字);`None` = 无。
188    pub caption: Option<Vec<Inline>>,
189    /// 装饰层(角标/边框/水印/圆角/阴影);默认全无。
190    pub decor: ImageDecor,
191}
192
193/// 图片装饰层 —— 叠在图面上的附加呈现,**不改变布局尺寸**(阴影溢出照画)。
194#[derive(Clone, Debug, Default)]
195pub struct ImageDecor {
196    /// 角标:小标签贴在图的一角(如「动图」「GIF」)。
197    pub badge: Option<Badge>,
198    /// 边框:沿图片边缘描边(圆角时随圆角走)。
199    pub border: Option<ImageBorder>,
200    /// 水印:半透明文字叠在图面。
201    pub watermark: Option<Watermark>,
202    /// 圆角半径(逻辑像素,0 = 直角):裁切图面,边框/阴影随之。
203    pub radius: f32,
204    /// 投影;`None` = 无。
205    pub shadow: Option<Shadow>,
206}
207
208/// 图面上的锚点位置(角标 / 水印的停靠处)。
209#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
210pub enum Anchor {
211    /// 左上。
212    TopLeft,
213    /// 右上(角标默认)。
214    #[default]
215    TopRight,
216    /// 左下。
217    BottomLeft,
218    /// 右下(水印默认)。
219    BottomRight,
220    /// 正中。
221    Center,
222}
223
224/// 图片角标:圆角底板 + 短文字,贴在图的一角。
225#[derive(Clone, Debug)]
226pub struct Badge {
227    /// 标签文字(宜短,如「动图」)。
228    pub text: String,
229    /// 停靠角。
230    pub anchor: Anchor,
231    /// 底板色(默认黑 72%)。
232    pub bg: Color,
233    /// 文字色(默认白)。
234    pub fg: Color,
235    /// 相对基准字号的倍率(默认 0.75)。
236    pub size: f32,
237}
238
239impl Badge {
240    /// 默认形态的角标(右上角、黑底白字)。
241    pub fn new(text: impl Into<String>) -> Self {
242        Self {
243            text: text.into(),
244            anchor: Anchor::TopRight,
245            bg: Color::rgba(0, 0, 0, 184),
246            fg: Color::rgb(255, 255, 255),
247            size: 0.75,
248        }
249    }
250}
251
252/// 图片边框(沿图缘描边;有圆角时随圆角)。
253#[derive(Clone, Copy, Debug, PartialEq)]
254pub struct ImageBorder {
255    /// 线宽(逻辑像素)。
256    pub width: f32,
257    /// 颜色。
258    pub color: Color,
259}
260
261/// 图片水印:无底板的半透明文字。
262#[derive(Clone, Debug)]
263pub struct Watermark {
264    /// 水印文字。
265    pub text: String,
266    /// 停靠处。
267    pub anchor: Anchor,
268    /// 颜色(含 alpha;默认白 40%)。
269    pub color: Color,
270    /// 相对基准字号的倍率(默认 0.9)。
271    pub size: f32,
272}
273
274impl Watermark {
275    /// 默认形态的水印(右下角、白 40%)。
276    pub fn new(text: impl Into<String>) -> Self {
277        Self {
278            text: text.into(),
279            anchor: Anchor::BottomRight,
280            color: Color::rgba(255, 255, 255, 102),
281            size: 0.9,
282        }
283    }
284}
285
286/// 行内元素。
287#[derive(Clone, Debug)]
288pub enum Inline {
289    /// 一段带样式的文字。
290    Text {
291        /// 文字。
292        text: String,
293        /// 样式。
294        style: TextStyle,
295    },
296    /// 行内代码(等宽 + 浅底)。
297    Code(String),
298    /// 硬换行。
299    LineBreak,
300}
301
302/// 可叠加的文字样式。span 嵌套时逐字段合并。
303#[derive(Clone, Debug, PartialEq)]
304pub struct TextStyle {
305    /// 字重(CSS 习惯值:细 300 / 常规 400 / 粗 700,内置字体 100–900 都有真实例)。
306    /// `None` = 跟随语境:正文常规,标题 / 表头加粗。
307    pub weight: Option<u16>,
308    /// 斜体。
309    pub italic: bool,
310    /// 下划线。
311    pub underline: bool,
312    /// 删除线。
313    pub strike: bool,
314    /// 文字色;`None` = 用主题文字色。
315    pub color: Option<Color>,
316    /// 高亮底色;`None` = 无高亮。
317    pub highlight: Option<Highlight>,
318    /// 相对基准字号的倍率(默认 1.0)。
319    pub size: f32,
320    /// 字族角色。
321    pub font: FontRole,
322    /// 链接文字(标记文本 `[文字](URL)` 的产物):无显式 `color` 时按主题强调色渲染。
323    /// 图片不可点,URL 本身不展示。
324    pub link: bool,
325    /// 文字阴影;`None` = 无。
326    pub shadow: Option<Shadow>,
327}
328
329impl Default for TextStyle {
330    fn default() -> Self {
331        Self {
332            weight: None,
333            italic: false,
334            underline: false,
335            strike: false,
336            color: None,
337            highlight: None,
338            size: 1.0,
339            font: FontRole::Sans,
340            link: false,
341            shadow: None,
342        }
343    }
344}
345
346/// 阴影(文字与图片共用):偏移 + 软化半径 + 颜色,尺寸皆**逻辑像素**。
347/// 不参与布局(不撑大占位),溢出块界照画。
348#[derive(Clone, Copy, Debug, PartialEq)]
349pub struct Shadow {
350    /// 水平偏移(右正)。
351    pub dx: f32,
352    /// 垂直偏移(下正)。
353    pub dy: f32,
354    /// 软化半径(0 = 实边)。
355    pub blur: f32,
356    /// 颜色(含 alpha,通常用半透明)。
357    pub color: Color,
358}
359
360impl Default for Shadow {
361    fn default() -> Self {
362        // 默认一枚朴素下坠软影。
363        Self { dx: 0.0, dy: 2.0, blur: 6.0, color: Color::rgba(0, 0, 0, 64) }
364    }
365}
366
367/// 高亮底色来源。
368#[derive(Clone, Copy, Debug, PartialEq, Eq)]
369pub enum Highlight {
370    /// 跟随主题默认高亮色(随亮 / 暗变)。
371    Theme,
372    /// 指定具体色。
373    Custom(Color),
374}
375
376/// 字族角色。`Named` 直接按字族名匹配,匹配不到回退 Sans。
377#[derive(Clone, Debug, PartialEq)]
378pub enum FontRole {
379    /// 无衬线(默认正文)。
380    Sans,
381    /// 衬线。
382    Serif,
383    /// 等宽。
384    Mono,
385    /// 楷体。
386    Kai,
387    /// 指定字族名。
388    Named(String),
389}
390
391/// 水平对齐。
392#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
393pub enum Align {
394    /// 左对齐(默认)。
395    #[default]
396    Left,
397    /// 居中。
398    Center,
399    /// 右对齐。
400    Right,
401    /// 两端对齐。
402    Justify,
403}
404
405/// RGBA 颜色(每通道 8 位,非预乘)。
406#[derive(Clone, Copy, Debug, PartialEq, Eq)]
407pub struct Color {
408    /// 红。
409    pub r: u8,
410    /// 绿。
411    pub g: u8,
412    /// 蓝。
413    pub b: u8,
414    /// 不透明度(255 = 不透明)。
415    pub a: u8,
416}
417
418impl Color {
419    /// 不透明色。
420    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
421        Self { r, g, b, a: 255 }
422    }
423
424    /// 带 alpha 的色。
425    pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
426        Self { r, g, b, a }
427    }
428
429    /// 解析十六进制色:`#rgb` / `#rrggbb` / `#rrggbbaa`(井号可省,大小写不限)。
430    /// 非法返回 `None`。`#rgb` 每位扩成两位(`f` → `ff`)。
431    pub fn hex(s: &str) -> Option<Self> {
432        let h = s.strip_prefix('#').unwrap_or(s);
433        if !h.bytes().all(|b| b.is_ascii_hexdigit()) {
434            return None;
435        }
436        let n = |slice: &str| u8::from_str_radix(slice, 16).ok();
437        match h.len() {
438            3 => {
439                let b = h.as_bytes();
440                let dup = |c: u8| {
441                    let d = (c as char).to_digit(16)? as u8;
442                    Some(d << 4 | d)
443                };
444                Some(Self::rgb(dup(b[0])?, dup(b[1])?, dup(b[2])?))
445            }
446            6 => Some(Self::rgb(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?)),
447            8 => Some(Self::rgba(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?, n(&h[6..8])?)),
448            _ => None,
449        }
450    }
451}
452
453/// 长度:绝对像素,或相对内容宽的百分比。
454#[derive(Clone, Copy, Debug, PartialEq)]
455pub enum Length {
456    /// 绝对逻辑像素。
457    Px(f32),
458    /// 内容宽的百分比(0–100)。
459    Percent(f32),
460}
461
462/// 图片来源。引擎不联网:URL 由调用方下好,以 `Bytes` 传入。
463#[derive(Clone, Debug)]
464pub enum ImageSource {
465    /// 已加载的图片字节。
466    Bytes(Vec<u8>),
467    /// 磁盘路径,渲染时读取。
468    Path(PathBuf),
469    /// 具名引用(标记文本里的 `@名字`),渲染时从 [`RenderOptions::images`](crate::RenderOptions::images) 取字节。
470    Named(String),
471}