Skip to main content

nagisa_render/
build.rs

1//! Rust 构建器 API —— 用链式调用拼出一个 [`Document`](crate::Document),作为 markup 之外的
2//! 另一前端(从结构化数据生成文档时更顺手)。两前端产物相同。
3//!
4//! 风格:块级构建器([`Doc`] / [`ListBuilder`])用 `&mut self -> &mut Self` 链式;子内容
5//! 用闭包配置(`|p| ...` 拿到 `&mut` 子构建器,表达式或语句写法都行,返回值忽略)。
6//!
7//! ```ignore
8//! let doc = Doc::new()
9//!     .heading(1, |h| h.align(Align::Center).text("月度报告"))
10//!     .paragraph(|p| { p.bold("本月").text("亮点:").highlight("达标"); })
11//!     .list(ListKind::Unordered, |l| { l.item(|i| i.text("任务一")); })
12//!     .divider()
13//!     .build();
14//! ```
15
16use std::path::PathBuf;
17
18use crate::model::{
19    Align, Block, BlockImage, Cell, ColSpec, Color, Column, Columns, Document, FontRole, Highlight,
20    Inline, Length, List, ListItem, ListKind, Table, TableStyle, TextStyle,
21};
22
23/// 文档 / 块序列构建器。也用作引用、列表项的内层块容器。
24#[derive(Default)]
25pub struct Doc {
26    blocks: Vec<Block>,
27}
28
29impl Doc {
30    /// 新建一个空文档构建器。
31    pub fn new() -> Self {
32        Self { blocks: Vec::new() }
33    }
34
35    /// 收尾成 [`Document`]。
36    pub fn build(&self) -> Document {
37        Document { blocks: self.blocks.clone() }
38    }
39
40    /// 标题(`level` 取 1..=6,越界夹到范围)。
41    pub fn heading<R>(&mut self, level: u8, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
42        let mut pb = ParaBuilder::new();
43        let _ = f(&mut pb);
44        self.blocks.push(Block::Heading {
45            level: level.clamp(1, 6),
46            inlines: pb.inlines,
47            align: pb.align,
48        });
49        self
50    }
51
52    /// 段落。
53    pub fn paragraph<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
54        let mut pb = ParaBuilder::new();
55        let _ = f(&mut pb);
56        self.blocks.push(Block::Paragraph { inlines: pb.inlines, align: pb.align });
57        self
58    }
59
60    /// 便捷:一行纯文字段落。
61    pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
62        self.paragraph(|p| {
63            p.text(s);
64        })
65    }
66
67    /// 引用块(内层是块容器,可嵌套)。
68    pub fn quote<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
69        let mut inner = Doc::new();
70        let _ = f(&mut inner);
71        self.blocks.push(Block::Quote(inner.blocks));
72        self
73    }
74
75    /// 列表(有序 / 无序)。
76    pub fn list<R>(&mut self, kind: ListKind, f: impl FnOnce(&mut ListBuilder) -> R) -> &mut Self {
77        let mut lb = ListBuilder { kind, start: 1, items: Vec::new() };
78        let _ = f(&mut lb);
79        self.blocks.push(Block::List(List { kind: lb.kind, start: lb.start, items: lb.items }));
80        self
81    }
82
83    /// 代码块。`lang` 空串 = 无语言标签。
84    pub fn code(&mut self, lang: impl Into<String>, text: impl Into<String>) -> &mut Self {
85        let lang = lang.into();
86        self.blocks.push(Block::Code {
87            lang: if lang.is_empty() { None } else { Some(lang) },
88            text: text.into(),
89        });
90        self
91    }
92
93    /// 分割线。
94    pub fn divider(&mut self) -> &mut Self {
95        self.blocks.push(Block::Divider);
96        self
97    }
98
99    /// 显式并排栏:闭包里用 `.col(..)` / `.col_weighted(w, ..)` 加栏。
100    pub fn columns<R>(&mut self, f: impl FnOnce(&mut ColumnsBuilder) -> R) -> &mut Self {
101        let mut cb = ColumnsBuilder { gap: None, cols: Vec::new() };
102        let _ = f(&mut cb);
103        self.blocks.push(Block::Columns(Columns { cols: cb.cols, gap: cb.gap }));
104        self
105    }
106
107    /// 表格:闭包里用 `.head([..])` / `.row([..])` / `.align([..])` / `.width(列, 长)`。
108    pub fn table<R>(&mut self, f: impl FnOnce(&mut TableBuilder) -> R) -> &mut Self {
109        let mut tb = TableBuilder {
110            header: None,
111            rows: Vec::new(),
112            cols: Vec::new(),
113            style: TableStyle::default(),
114        };
115        let _ = f(&mut tb);
116        self.blocks.push(Block::Table(Table {
117            header: tb.header,
118            rows: tb.rows,
119            cols: tb.cols,
120            style: tb.style,
121        }));
122        self
123    }
124
125    /// 块级图(字节来源)。
126    pub fn image_bytes<R>(
127        &mut self,
128        bytes: Vec<u8>,
129        f: impl FnOnce(&mut ImageBuilder) -> R,
130    ) -> &mut Self {
131        self.push_block_image(ImageSource::Bytes(bytes), f)
132    }
133
134    /// 块级图(磁盘路径)。
135    pub fn image_path<R>(
136        &mut self,
137        path: impl Into<PathBuf>,
138        f: impl FnOnce(&mut ImageBuilder) -> R,
139    ) -> &mut Self {
140        self.push_block_image(ImageSource::Path(path.into()), f)
141    }
142
143    fn push_block_image<R>(
144        &mut self,
145        src: ImageSource,
146        f: impl FnOnce(&mut ImageBuilder) -> R,
147    ) -> &mut Self {
148        let mut ib = ImageBuilder { width: None, align: Align::Left, caption: None };
149        let _ = f(&mut ib);
150        self.blocks.push(Block::Image(BlockImage {
151            src,
152            width: ib.width,
153            align: ib.align,
154            caption: ib.caption,
155        }));
156        self
157    }
158}
159
160use crate::model::ImageSource;
161
162/// 段落 / 标题的行内内容构建器(也用于图注)。
163pub struct ParaBuilder {
164    inlines: Vec<Inline>,
165    align: Align,
166}
167
168impl ParaBuilder {
169    fn new() -> Self {
170        Self { inlines: Vec::new(), align: Align::Left }
171    }
172
173    /// 设对齐。
174    pub fn align(&mut self, a: Align) -> &mut Self {
175        self.align = a;
176        self
177    }
178
179    /// 普通文字。
180    pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
181        self.push(s, TextStyle::default())
182    }
183
184    /// 粗体文字。
185    pub fn bold(&mut self, s: impl Into<String>) -> &mut Self {
186        self.push(s, TextStyle { weight: Some(700), ..Default::default() })
187    }
188
189    /// 细体文字(字重 300)。
190    pub fn light(&mut self, s: impl Into<String>) -> &mut Self {
191        self.push(s, TextStyle { weight: Some(300), ..Default::default() })
192    }
193
194    /// 斜体文字。
195    pub fn italic(&mut self, s: impl Into<String>) -> &mut Self {
196        self.push(s, TextStyle { italic: true, ..Default::default() })
197    }
198
199    /// 下划线文字。
200    pub fn underline(&mut self, s: impl Into<String>) -> &mut Self {
201        self.push(s, TextStyle { underline: true, ..Default::default() })
202    }
203
204    /// 删除线文字。
205    pub fn strike(&mut self, s: impl Into<String>) -> &mut Self {
206        self.push(s, TextStyle { strike: true, ..Default::default() })
207    }
208
209    /// 高亮文字(主题默认高亮色)。
210    pub fn highlight(&mut self, s: impl Into<String>) -> &mut Self {
211        self.push(s, TextStyle { highlight: Some(Highlight::Theme), ..Default::default() })
212    }
213
214    /// 指定色文字(十六进制;非法则用默认色)。
215    pub fn color(&mut self, hex: &str, s: impl Into<String>) -> &mut Self {
216        self.push(s, TextStyle { color: Color::hex(hex), ..Default::default() })
217    }
218
219    /// 行内代码。
220    pub fn code(&mut self, s: impl Into<String>) -> &mut Self {
221        self.inlines.push(Inline::Code(s.into()));
222        self
223    }
224
225    /// 任意样式文字:闭包里配置 [`StyleBuilder`]。
226    pub fn styled<R>(
227        &mut self,
228        s: impl Into<String>,
229        f: impl FnOnce(&mut StyleBuilder) -> R,
230    ) -> &mut Self {
231        let mut sb = StyleBuilder { style: TextStyle::default() };
232        let _ = f(&mut sb);
233        self.push(s, sb.style)
234    }
235
236    /// 硬换行。
237    pub fn line_break(&mut self) -> &mut Self {
238        self.inlines.push(Inline::LineBreak);
239        self
240    }
241
242    fn push(&mut self, s: impl Into<String>, style: TextStyle) -> &mut Self {
243        self.inlines.push(Inline::Text { text: s.into(), style });
244        self
245    }
246}
247
248/// 文字样式构建器(给 [`ParaBuilder::styled`])。
249pub struct StyleBuilder {
250    style: TextStyle,
251}
252
253impl StyleBuilder {
254    /// 加粗(字重 700)。
255    pub fn bold(&mut self) -> &mut Self {
256        self.style.weight = Some(700);
257        self
258    }
259    /// 细体(字重 300)。
260    pub fn light(&mut self) -> &mut Self {
261        self.style.weight = Some(300);
262        self
263    }
264    /// 任意字重(CSS 习惯值 1–1000,常用 100–900;越界忽略)。
265    pub fn weight(&mut self, w: u16) -> &mut Self {
266        if (1..=1000).contains(&w) {
267            self.style.weight = Some(w);
268        }
269        self
270    }
271    /// 斜体。
272    pub fn italic(&mut self) -> &mut Self {
273        self.style.italic = true;
274        self
275    }
276    /// 下划线。
277    pub fn underline(&mut self) -> &mut Self {
278        self.style.underline = true;
279        self
280    }
281    /// 删除线。
282    pub fn strike(&mut self) -> &mut Self {
283        self.style.strike = true;
284        self
285    }
286    /// 文字色(十六进制;非法忽略)。
287    pub fn color(&mut self, hex: &str) -> &mut Self {
288        if let Some(c) = Color::hex(hex) {
289            self.style.color = Some(c);
290        }
291        self
292    }
293    /// 高亮底色(十六进制;非法忽略)。
294    pub fn bg(&mut self, hex: &str) -> &mut Self {
295        if let Some(c) = Color::hex(hex) {
296            self.style.highlight = Some(Highlight::Custom(c));
297        }
298        self
299    }
300    /// 字号倍率(相对基准)。非有限或 ≤ 0 忽略(保持默认),与标记前端一致——
301    /// 避免 0 字号把 cosmic-text 整形拖进死循环。
302    pub fn size(&mut self, mult: f32) -> &mut Self {
303        if mult.is_finite() && mult > 0.0 {
304            self.style.size = mult;
305        }
306        self
307    }
308    /// 字族角色。
309    pub fn font(&mut self, role: FontRole) -> &mut Self {
310        self.style.font = role;
311        self
312    }
313}
314
315/// 表格构建器(纯文字单元格)。
316pub struct TableBuilder {
317    header: Option<Vec<Cell>>,
318    rows: Vec<Vec<Cell>>,
319    cols: Vec<ColSpec>,
320    style: TableStyle,
321}
322
323impl TableBuilder {
324    /// 设表头。
325    pub fn head<I, S>(&mut self, cells: I) -> &mut Self
326    where
327        I: IntoIterator<Item = S>,
328        S: Into<String>,
329    {
330        self.header = Some(cells.into_iter().map(text_cell).collect());
331        self
332    }
333    /// 加一数据行。
334    pub fn row<I, S>(&mut self, cells: I) -> &mut Self
335    where
336        I: IntoIterator<Item = S>,
337        S: Into<String>,
338    {
339        self.rows.push(cells.into_iter().map(text_cell).collect());
340        self
341    }
342    /// 设各列对齐(从第 0 列起)。
343    pub fn align<I: IntoIterator<Item = Align>>(&mut self, aligns: I) -> &mut Self {
344        for (k, a) in aligns.into_iter().enumerate() {
345            self.ensure_col(k).align = a;
346        }
347        self
348    }
349    /// 给某列(0 起)限宽。
350    pub fn width(&mut self, col: usize, w: Length) -> &mut Self {
351        self.ensure_col(col).width = Some(w);
352        self
353    }
354    fn ensure_col(&mut self, k: usize) -> &mut ColSpec {
355        while self.cols.len() <= k {
356            self.cols.push(ColSpec::default());
357        }
358        &mut self.cols[k]
359    }
360
361    // ── 按列 / 行 / 格设文字样式 + 背景(在已加的单元格上叠加;先加行,再设样式) ──
362
363    /// 整列(含表头)的文字样式。
364    pub fn col_style<R>(&mut self, col: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
365        if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
366            style_cell(h, &f);
367        }
368        for row in &mut self.rows {
369            if let Some(c) = row.get_mut(col) {
370                style_cell(c, &f);
371            }
372        }
373        self
374    }
375    /// 整行(数据行,0 起)的文字样式。
376    pub fn row_style<R>(&mut self, row: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
377        if let Some(r) = self.rows.get_mut(row) {
378            for c in r.iter_mut() {
379                style_cell(c, &f);
380            }
381        }
382        self
383    }
384    /// 单格(数据行 / 列,0 起)的文字样式。
385    pub fn cell_style<R>(
386        &mut self,
387        row: usize,
388        col: usize,
389        f: impl Fn(&mut StyleBuilder) -> R,
390    ) -> &mut Self {
391        if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
392            style_cell(c, &f);
393        }
394        self
395    }
396    /// 整列(含表头)背景填色。
397    pub fn col_fill(&mut self, col: usize, hex: &str) -> &mut Self {
398        let bg = Color::hex(hex);
399        if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
400            h.bg = bg;
401        }
402        for row in &mut self.rows {
403            if let Some(c) = row.get_mut(col) {
404                c.bg = bg;
405            }
406        }
407        self
408    }
409    /// 整行(数据行)背景填色。
410    pub fn row_fill(&mut self, row: usize, hex: &str) -> &mut Self {
411        let bg = Color::hex(hex);
412        if let Some(r) = self.rows.get_mut(row) {
413            for c in r.iter_mut() {
414                c.bg = bg;
415            }
416        }
417        self
418    }
419    /// 单格(数据行 / 列)背景填色。
420    pub fn cell_fill(&mut self, row: usize, col: usize, hex: &str) -> &mut Self {
421        if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
422            c.bg = Color::hex(hex);
423        }
424        self
425    }
426
427    // ── 紧凑度 + 网格线 ──
428
429    /// 列内边距(单元格左右,逻辑像素);越小列越紧凑。
430    pub fn pad_x(&mut self, px: f32) -> &mut Self {
431        self.style.pad_x = Some(px.max(0.0));
432        self
433    }
434    /// 行内边距(单元格上下,逻辑像素);越小行越紧凑、行距越小。
435    pub fn pad_y(&mut self, px: f32) -> &mut Self {
436        self.style.pad_y = Some(px.max(0.0));
437        self
438    }
439    /// 外框线开关。
440    pub fn grid_outer(&mut self, on: bool) -> &mut Self {
441        self.style.grid.outer = on;
442        self
443    }
444    /// 列竖线开关。
445    pub fn grid_vertical(&mut self, on: bool) -> &mut Self {
446        self.style.grid.vertical = on;
447        self
448    }
449    /// 行横线开关。
450    pub fn grid_horizontal(&mut self, on: bool) -> &mut Self {
451        self.style.grid.horizontal = on;
452        self
453    }
454    /// 去掉所有网格线(外框 / 竖线 / 横线)。
455    pub fn no_grid(&mut self) -> &mut Self {
456        self.style.grid.outer = false;
457        self.style.grid.vertical = false;
458        self.style.grid.horizontal = false;
459        self
460    }
461    /// 表头浅底开关。
462    pub fn header_fill(&mut self, on: bool) -> &mut Self {
463        self.style.header_fill = on;
464        self
465    }
466}
467
468/// 纯文字单元格。
469fn text_cell(s: impl Into<String>) -> Cell {
470    Cell { inlines: vec![Inline::Text { text: s.into(), style: TextStyle::default() }], bg: None }
471}
472
473/// 给一个单元格的所有文字段叠加样式(从各段现有样式起、合并闭包改动的字段)。
474fn style_cell<R>(cell: &mut Cell, f: &impl Fn(&mut StyleBuilder) -> R) {
475    for inl in &mut cell.inlines {
476        if let Inline::Text { style, .. } = inl {
477            let mut sb = StyleBuilder { style: style.clone() };
478            let _ = f(&mut sb);
479            *style = sb.style;
480        }
481    }
482}
483
484/// 并排栏构建器。
485pub struct ColumnsBuilder {
486    gap: Option<f32>,
487    cols: Vec<Column>,
488}
489
490impl ColumnsBuilder {
491    /// 栏间距(逻辑像素)。
492    pub fn gap(&mut self, g: f32) -> &mut Self {
493        self.gap = Some(g);
494        self
495    }
496    /// 一栏(权重 1.0)。
497    pub fn col<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
498        self.col_weighted(1.0, f)
499    }
500    /// 一栏(指定宽度权重)。
501    pub fn col_weighted<R>(&mut self, weight: f32, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
502        let mut inner = Doc::new();
503        let _ = f(&mut inner);
504        self.cols.push(Column { blocks: inner.blocks, weight });
505        self
506    }
507}
508
509/// 列表构建器。
510pub struct ListBuilder {
511    kind: ListKind,
512    start: u32,
513    items: Vec<ListItem>,
514}
515
516impl ListBuilder {
517    /// 有序列表起始序号。
518    pub fn start(&mut self, n: u32) -> &mut Self {
519        self.start = n;
520        self
521    }
522    /// 一个列表项(内容是块容器,可放多段 / 嵌套子列表)。
523    pub fn item<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
524        let mut inner = Doc::new();
525        let _ = f(&mut inner);
526        self.items.push(ListItem { blocks: inner.blocks, check: None });
527        self
528    }
529    /// 一个任务项(`done` = 已完成):标记渲染成 `✓` / `□`,对应标记文本的 `- [x]` / `- [ ]`。
530    pub fn task<R>(&mut self, done: bool, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
531        let mut inner = Doc::new();
532        let _ = f(&mut inner);
533        self.items.push(ListItem { blocks: inner.blocks, check: Some(done) });
534        self
535    }
536}
537
538/// 块级图片构建器。
539pub struct ImageBuilder {
540    width: Option<Length>,
541    align: Align,
542    caption: Option<Vec<Inline>>,
543}
544
545impl ImageBuilder {
546    /// 绝对宽度(逻辑像素)。
547    pub fn width_px(&mut self, px: f32) -> &mut Self {
548        self.width = Some(Length::Px(px));
549        self
550    }
551    /// 相对内容宽的百分比宽度。
552    pub fn width_percent(&mut self, pct: f32) -> &mut Self {
553        self.width = Some(Length::Percent(pct));
554        self
555    }
556    /// 对齐。
557    pub fn align(&mut self, a: Align) -> &mut Self {
558        self.align = a;
559        self
560    }
561    /// 纯文字图注。
562    pub fn caption(&mut self, s: impl Into<String>) -> &mut Self {
563        self.caption = Some(vec![Inline::Text { text: s.into(), style: TextStyle::default() }]);
564        self
565    }
566    /// 富文字图注(闭包配置行内)。
567    pub fn caption_with<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
568        let mut pb = ParaBuilder::new();
569        let _ = f(&mut pb);
570        self.caption = Some(pb.inlines);
571        self
572    }
573}
574