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, Anchor, Badge, Block, BlockImage, Cell, ColSpec, Color, Column, Columns, Document, FontRole, Highlight,
20    ImageBorder, ImageDecor, Inline, Length, List, ListItem, ListKind, Panel, PanelDecor, Progress, Shadow, Table,
21    TableStyle, TextStyle, Watermark,
22};
23
24/// 文档 / 块序列构建器。也用作引用、列表项的内层块容器。
25#[derive(Default)]
26pub struct Doc {
27    blocks: Vec<Block>,
28}
29
30impl Doc {
31    /// 新建一个空文档构建器。
32    pub fn new() -> Self {
33        Self { blocks: Vec::new() }
34    }
35
36    /// 收尾成 [`Document`]。
37    pub fn build(&self) -> Document {
38        Document { blocks: self.blocks.clone() }
39    }
40
41    /// 标题(`level` 取 1..=6,越界夹到范围)。
42    pub fn heading<R>(&mut self, level: u8, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
43        let mut pb = ParaBuilder::new();
44        let _ = f(&mut pb);
45        self.blocks.push(Block::Heading { level: level.clamp(1, 6), inlines: pb.inlines, align: pb.align });
46        self
47    }
48
49    /// 段落。
50    pub fn paragraph<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
51        let mut pb = ParaBuilder::new();
52        let _ = f(&mut pb);
53        self.blocks.push(Block::Paragraph { inlines: pb.inlines, align: pb.align });
54        self
55    }
56
57    /// 便捷:一行纯文字段落。
58    pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
59        self.paragraph(|p| {
60            p.text(s);
61        })
62    }
63
64    /// 引用块(内层是块容器,可嵌套)。
65    pub fn quote<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
66        let mut inner = Doc::new();
67        let _ = f(&mut inner);
68        self.blocks.push(Block::Quote(inner.blocks));
69        self
70    }
71
72    /// 列表(有序 / 无序)。
73    pub fn list<R>(&mut self, kind: ListKind, f: impl FnOnce(&mut ListBuilder) -> R) -> &mut Self {
74        let mut lb = ListBuilder { kind, start: 1, items: Vec::new() };
75        let _ = f(&mut lb);
76        self.blocks.push(Block::List(List { kind: lb.kind, start: lb.start, items: lb.items }));
77        self
78    }
79
80    /// 代码块。`lang` 空串 = 无语言标签。
81    pub fn code(&mut self, lang: impl Into<String>, text: impl Into<String>) -> &mut Self {
82        let lang = lang.into();
83        self.blocks.push(Block::Code { lang: if lang.is_empty() { None } else { Some(lang) }, text: text.into() });
84        self
85    }
86
87    /// 分割线。
88    pub fn divider(&mut self) -> &mut Self {
89        self.blocks.push(Block::Divider);
90        self
91    }
92
93    /// 面板(卡片):闭包先配装饰(`.bg(..)` / `.border(..)` / `.rounded(..)` / `.pad(..)` /
94    /// `.shadow()`),内容方法经 `Deref` 直接用(`p.text(..)` / `p.heading(..)` …);全缺省
95    /// 即主题默认卡片样(浅底 + 细边 + 圆角)。
96    pub fn panel<R>(&mut self, f: impl FnOnce(&mut PanelBuilder) -> R) -> &mut Self {
97        let mut pb = PanelBuilder::new();
98        let _ = f(&mut pb);
99        self.blocks.push(Block::Panel(pb.into_panel()));
100        self
101    }
102
103    /// 显式并排栏:闭包里用 `.col(..)` / `.col_weighted(w, ..)` 加栏。
104    pub fn columns<R>(&mut self, f: impl FnOnce(&mut ColumnsBuilder) -> R) -> &mut Self {
105        let mut cb = ColumnsBuilder { gap: None, cols: Vec::new() };
106        let _ = f(&mut cb);
107        self.blocks.push(Block::Columns(Columns { cols: cb.cols, gap: cb.gap }));
108        self
109    }
110
111    /// 表格:闭包里用 `.head([..])` / `.row([..])` / `.align([..])` / `.width(列, 长)`。
112    pub fn table<R>(&mut self, f: impl FnOnce(&mut TableBuilder) -> R) -> &mut Self {
113        let mut tb = TableBuilder { header: None, rows: Vec::new(), cols: Vec::new(), style: TableStyle::default() };
114        let _ = f(&mut tb);
115        self.blocks.push(Block::Table(Table { header: tb.header, rows: tb.rows, cols: tb.cols, style: tb.style }));
116        self
117    }
118
119    /// 进度条:`value` 取 0–1(越界渲染时夹取),样式经闭包调
120    /// (`.height(..)` / `.fill(..)` / `.track(..)` / `.radius(..)` / `.width_px(..)` /
121    /// `.width_percent(..)` / `.align(..)`),全缺省即「铺满内容宽的胶囊条,主题强调色」。
122    pub fn progress<R>(&mut self, value: f32, f: impl FnOnce(&mut ProgressBuilder) -> R) -> &mut Self {
123        let mut pb = ProgressBuilder {
124            p: Progress { value, height: 10.0, fill: None, track: None, radius: None, width: None, align: Align::Left },
125        };
126        let _ = f(&mut pb);
127        self.blocks.push(Block::Progress(pb.p));
128        self
129    }
130
131    /// 块级图(字节来源)。
132    pub fn image_bytes<R>(&mut self, bytes: Vec<u8>, f: impl FnOnce(&mut ImageBuilder) -> R) -> &mut Self {
133        self.push_block_image(ImageSource::Bytes(bytes), f)
134    }
135
136    /// 块级图(磁盘路径)。
137    pub fn image_path<R>(&mut self, path: impl Into<PathBuf>, f: impl FnOnce(&mut ImageBuilder) -> R) -> &mut Self {
138        self.push_block_image(ImageSource::Path(path.into()), f)
139    }
140
141    fn push_block_image<R>(&mut self, src: ImageSource, f: impl FnOnce(&mut ImageBuilder) -> R) -> &mut Self {
142        let mut ib = ImageBuilder { width: None, align: Align::Left, caption: None, decor: ImageDecor::default() };
143        let _ = f(&mut ib);
144        self.blocks.push(Block::Image(BlockImage {
145            src,
146            width: ib.width,
147            align: ib.align,
148            caption: ib.caption,
149            decor: ib.decor,
150        }));
151        self
152    }
153}
154
155use crate::model::ImageSource;
156
157/// 段落 / 标题的行内内容构建器(也用于图注)。
158pub struct ParaBuilder {
159    inlines: Vec<Inline>,
160    align: Align,
161}
162
163impl ParaBuilder {
164    pub(crate) fn new() -> Self {
165        Self { inlines: Vec::new(), align: Align::Left }
166    }
167
168    /// 取走已累积的行内序列(页眉/页脚的富文本构造用)。
169    pub(crate) fn into_inlines(self) -> Vec<Inline> {
170        self.inlines
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>(&mut self, s: impl Into<String>, f: impl FnOnce(&mut StyleBuilder) -> R) -> &mut Self {
227        let mut sb = StyleBuilder { style: TextStyle::default() };
228        let _ = f(&mut sb);
229        self.push(s, sb.style)
230    }
231
232    /// 硬换行。
233    pub fn line_break(&mut self) -> &mut Self {
234        self.inlines.push(Inline::LineBreak);
235        self
236    }
237
238    fn push(&mut self, s: impl Into<String>, style: TextStyle) -> &mut Self {
239        self.inlines.push(Inline::Text { text: s.into(), style });
240        self
241    }
242}
243
244/// 文字样式构建器(给 [`ParaBuilder::styled`])。
245pub struct StyleBuilder {
246    style: TextStyle,
247}
248
249impl StyleBuilder {
250    /// 加粗(字重 700)。
251    pub fn bold(&mut self) -> &mut Self {
252        self.style.weight = Some(700);
253        self
254    }
255    /// 细体(字重 300)。
256    pub fn light(&mut self) -> &mut Self {
257        self.style.weight = Some(300);
258        self
259    }
260    /// 任意字重(CSS 习惯值 1–1000,常用 100–900;越界忽略)。
261    pub fn weight(&mut self, w: u16) -> &mut Self {
262        if (1..=1000).contains(&w) {
263            self.style.weight = Some(w);
264        }
265        self
266    }
267    /// 斜体。
268    pub fn italic(&mut self) -> &mut Self {
269        self.style.italic = true;
270        self
271    }
272    /// 下划线。
273    pub fn underline(&mut self) -> &mut Self {
274        self.style.underline = true;
275        self
276    }
277    /// 删除线。
278    pub fn strike(&mut self) -> &mut Self {
279        self.style.strike = true;
280        self
281    }
282    /// 文字色(十六进制;非法忽略)。
283    pub fn color(&mut self, hex: &str) -> &mut Self {
284        if let Some(c) = Color::hex(hex) {
285            self.style.color = Some(c);
286        }
287        self
288    }
289    /// 高亮底色(十六进制;非法忽略)。
290    pub fn bg(&mut self, hex: &str) -> &mut Self {
291        if let Some(c) = Color::hex(hex) {
292            self.style.highlight = Some(Highlight::Custom(c));
293        }
294        self
295    }
296    /// 字号倍率(相对基准)。非有限或 ≤ 0 忽略(保持默认),与标记前端一致——
297    /// 0 字号在整形里没有意义,挡在入口。
298    pub fn size(&mut self, mult: f32) -> &mut Self {
299        if mult.is_finite() && mult > 0.0 {
300            self.style.size = mult;
301        }
302        self
303    }
304    /// 字族角色。
305    pub fn font(&mut self, role: FontRole) -> &mut Self {
306        self.style.font = role;
307        self
308    }
309    /// 链接样式:无显式色时按主题强调色渲(与标记文本 `[文字](URL)` 同款;引擎不存 URL)。
310    pub fn link(&mut self) -> &mut Self {
311        self.style.link = true;
312        self
313    }
314    /// 圈注:以这段文字为中心画一圈椭圆描边(缺省按文字宽窄自适应、颜色跟随墨色;
315    /// 不占布局尺寸,圈溢出到行距)。尺寸经 [`ring_radius`](Self::ring_radius) /
316    /// [`ring_radii`](Self::ring_radii) 给定后与文字宽窄无关。
317    pub fn ring(&mut self) -> &mut Self {
318        self.style.ring.get_or_insert_default();
319        self
320    }
321    /// 圈注描边色(十六进制;非法忽略,跟随墨色)。
322    pub fn ring_color(&mut self, hex: &str) -> &mut Self {
323        let r = self.style.ring.get_or_insert_default();
324        r.color = Color::hex(hex).or(r.color);
325        self
326    }
327    /// 圈注定径:**正圆**,半径 `r`(逻辑像素)——多字与单字圈出同样大的圈。
328    pub fn ring_radius(&mut self, r: f32) -> &mut Self {
329        self.ring_radii(r, r)
330    }
331    /// 圈注定径:**椭圆**,横/纵半径(逻辑像素)。非有限或 ≤ 0 的分量忽略(保持自适应)。
332    pub fn ring_radii(&mut self, rx: f32, ry: f32) -> &mut Self {
333        let r = self.style.ring.get_or_insert_default();
334        if rx.is_finite() && rx > 0.0 {
335            r.rx = Some(rx);
336        }
337        if ry.is_finite() && ry > 0.0 {
338            r.ry = Some(ry);
339        }
340        self
341    }
342    /// 圈注线宽(逻辑像素;非有限或 ≤ 0 忽略,保持 0.07 倍字号缺省)。
343    pub fn ring_stroke(&mut self, w: f32) -> &mut Self {
344        let r = self.style.ring.get_or_insert_default();
345        if w.is_finite() && w > 0.0 {
346            r.width = Some(w);
347        }
348        self
349    }
350    /// 逐字圈:整段一字一圈(空白跳过),未定径时按字取**正圆**。缺省是整段一个圈
351    /// (范围圈,自适应为扁椭圆)。
352    pub fn ring_each(&mut self) -> &mut Self {
353        self.style.ring.get_or_insert_default().each = true;
354        self
355    }
356    /// 着重点:这段文字正下方一枚实心小点(颜色跟随墨色;画进行距,不占高度)。
357    pub fn dot(&mut self) -> &mut Self {
358        self.style.dot.get_or_insert_default();
359        self
360    }
361    /// 着重点颜色(十六进制;非法忽略,跟随墨色)。
362    pub fn dot_color(&mut self, hex: &str) -> &mut Self {
363        let d = self.style.dot.get_or_insert_default();
364        d.color = Color::hex(hex).or(d.color);
365        self
366    }
367    /// 着重点半径(逻辑像素;非有限或 ≤ 0 忽略,保持 0.09 倍字号缺省)。
368    pub fn dot_radius(&mut self, r: f32) -> &mut Self {
369        let d = self.style.dot.get_or_insert_default();
370        if r.is_finite() && r > 0.0 {
371            d.radius = Some(r);
372        }
373        self
374    }
375    /// 逐字点:一字一点(中文着重号的正字法;空白跳过)。缺省是整段中线下一点。
376    pub fn dot_each(&mut self) -> &mut Self {
377        self.style.dot.get_or_insert_default().each = true;
378        self
379    }
380    /// 边注挂右:这段挂到本行内容的右外侧,参与绘制不参与布局——居中 / 对齐按其余
381    /// 内容算,边注不挤不偏(「当前」「✓」这类行尾标记用)。
382    pub fn aside_right(&mut self) -> &mut Self {
383        self.style.aside = Some(crate::model::AsideSide::Right);
384        self
385    }
386    /// 边注挂左:同 [`aside_right`](Self::aside_right),停靠行首左外侧。
387    pub fn aside_left(&mut self) -> &mut Self {
388        self.style.aside = Some(crate::model::AsideSide::Left);
389        self
390    }
391    /// 文字阴影(默认形态:下坠 2 逻辑像素、软化 6、黑 25%)。
392    pub fn shadow(&mut self) -> &mut Self {
393        self.style.shadow = Some(Shadow::default());
394        self
395    }
396    /// 文字阴影(自定形态:偏移/软化为逻辑像素,色为十六进制,非法色忽略整条)。
397    pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
398        if let Some(color) = Color::hex(hex) {
399            self.style.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
400        }
401        self
402    }
403}
404
405/// 表格构建器(纯文字单元格)。
406pub struct TableBuilder {
407    header: Option<Vec<Cell>>,
408    rows: Vec<Vec<Cell>>,
409    cols: Vec<ColSpec>,
410    style: TableStyle,
411}
412
413impl TableBuilder {
414    /// 设表头。
415    pub fn head<I, S>(&mut self, cells: I) -> &mut Self
416    where
417        I: IntoIterator<Item = S>,
418        S: Into<String>,
419    {
420        self.header = Some(cells.into_iter().map(text_cell).collect());
421        self
422    }
423    /// 加一数据行。
424    pub fn row<I, S>(&mut self, cells: I) -> &mut Self
425    where
426        I: IntoIterator<Item = S>,
427        S: Into<String>,
428    {
429        self.rows.push(cells.into_iter().map(text_cell).collect());
430        self
431    }
432    /// 设表头(富文本:每格一个闭包拼行内)。
433    pub fn head_rich<R>(&mut self, f: impl FnOnce(&mut RowBuilder) -> R) -> &mut Self {
434        let mut rb = RowBuilder { cells: Vec::new() };
435        let _ = f(&mut rb);
436        self.header = Some(rb.cells);
437        self
438    }
439    /// 加一数据行(富文本:每格一个闭包拼行内)。
440    pub fn row_rich<R>(&mut self, f: impl FnOnce(&mut RowBuilder) -> R) -> &mut Self {
441        let mut rb = RowBuilder { cells: Vec::new() };
442        let _ = f(&mut rb);
443        self.rows.push(rb.cells);
444        self
445    }
446    /// 设各列对齐(从第 0 列起)。
447    pub fn align<I: IntoIterator<Item = Align>>(&mut self, aligns: I) -> &mut Self {
448        for (k, a) in aligns.into_iter().enumerate() {
449            self.ensure_col(k).align = a;
450        }
451        self
452    }
453    /// 给某列(0 起)限宽。
454    pub fn width(&mut self, col: usize, w: Length) -> &mut Self {
455        self.ensure_col(col).width = Some(w);
456        self
457    }
458    fn ensure_col(&mut self, k: usize) -> &mut ColSpec {
459        while self.cols.len() <= k {
460            self.cols.push(ColSpec::default());
461        }
462        &mut self.cols[k]
463    }
464
465    // ── 按列 / 行 / 格设文字样式 + 背景(在已加的单元格上叠加;先加行,再设样式) ──
466
467    /// 整列(含表头)的文字样式。
468    pub fn col_style<R>(&mut self, col: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
469        if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
470            style_cell(h, &f);
471        }
472        for row in &mut self.rows {
473            if let Some(c) = row.get_mut(col) {
474                style_cell(c, &f);
475            }
476        }
477        self
478    }
479    /// 整行(数据行,0 起)的文字样式。
480    pub fn row_style<R>(&mut self, row: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
481        if let Some(r) = self.rows.get_mut(row) {
482            for c in r.iter_mut() {
483                style_cell(c, &f);
484            }
485        }
486        self
487    }
488    /// 单格(数据行 / 列,0 起)的文字样式。
489    pub fn cell_style<R>(&mut self, row: usize, col: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
490        if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
491            style_cell(c, &f);
492        }
493        self
494    }
495    /// 整列(含表头)背景填色(非法色忽略,不动已设值)。
496    pub fn col_fill(&mut self, col: usize, hex: &str) -> &mut Self {
497        let Some(bg) = Color::hex(hex) else { return self };
498        if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
499            h.bg = Some(bg);
500        }
501        for row in &mut self.rows {
502            if let Some(c) = row.get_mut(col) {
503                c.bg = Some(bg);
504            }
505        }
506        self
507    }
508    /// 整行(数据行)背景填色(非法色忽略,不动已设值)。
509    pub fn row_fill(&mut self, row: usize, hex: &str) -> &mut Self {
510        let Some(bg) = Color::hex(hex) else { return self };
511        if let Some(r) = self.rows.get_mut(row) {
512            for c in r.iter_mut() {
513                c.bg = Some(bg);
514            }
515        }
516        self
517    }
518    /// 单格(数据行 / 列)背景填色(非法色忽略,不动已设值)。
519    pub fn cell_fill(&mut self, row: usize, col: usize, hex: &str) -> &mut Self {
520        let Some(bg) = Color::hex(hex) else { return self };
521        if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
522            c.bg = Some(bg);
523        }
524        self
525    }
526
527    // ── 紧凑度 + 网格线 ──
528
529    /// 列内边距(单元格左右,逻辑像素);越小列越紧凑。
530    pub fn pad_x(&mut self, px: f32) -> &mut Self {
531        self.style.pad_x = Some(px.max(0.0));
532        self
533    }
534    /// 行内边距(单元格上下,逻辑像素);越小行越紧凑、行距越小。
535    pub fn pad_y(&mut self, px: f32) -> &mut Self {
536        self.style.pad_y = Some(px.max(0.0));
537        self
538    }
539    /// 拉伸铺满可用宽(富余宽度按比例分给自适应列;全固定列则整体等比放大)。
540    pub fn expand(&mut self) -> &mut Self {
541        self.style.expand = true;
542        self
543    }
544    /// 整表水平对齐(窄表生效;别与 [`expand`](Self::expand) 混用——铺满了没得对齐)。
545    pub fn table_align(&mut self, a: Align) -> &mut Self {
546        self.style.align = a;
547        self
548    }
549    /// 外框线开关。
550    pub fn grid_outer(&mut self, on: bool) -> &mut Self {
551        self.style.grid.outer = on;
552        self
553    }
554    /// 列竖线开关。
555    pub fn grid_vertical(&mut self, on: bool) -> &mut Self {
556        self.style.grid.vertical = on;
557        self
558    }
559    /// 行横线开关。
560    pub fn grid_horizontal(&mut self, on: bool) -> &mut Self {
561        self.style.grid.horizontal = on;
562        self
563    }
564    /// 去掉所有网格线(外框 / 竖线 / 横线)。
565    pub fn no_grid(&mut self) -> &mut Self {
566        self.style.grid.outer = false;
567        self.style.grid.vertical = false;
568        self.style.grid.horizontal = false;
569        self
570    }
571    /// 表头浅底开关。
572    pub fn header_fill(&mut self, on: bool) -> &mut Self {
573        self.style.header_fill = on;
574        self
575    }
576}
577
578/// 富文本行构建器([`TableBuilder::head_rich`] / [`TableBuilder::row_rich`] 的闭包参数)。
579pub struct RowBuilder {
580    cells: Vec<Cell>,
581}
582
583impl RowBuilder {
584    /// 加一格(闭包拼行内,粗细 / 色 / 行内码皆可)。
585    pub fn cell<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
586        let mut pb = ParaBuilder::new();
587        let _ = f(&mut pb);
588        self.cells.push(Cell { inlines: pb.into_inlines(), bg: None });
589        self
590    }
591    /// 加一格纯文字(与 [`TableBuilder::row`] 同款便捷)。
592    pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
593        self.cells.push(text_cell(s));
594        self
595    }
596}
597
598/// 纯文字单元格。
599fn text_cell(s: impl Into<String>) -> Cell {
600    Cell { inlines: vec![Inline::Text { text: s.into(), style: TextStyle::default() }], bg: None }
601}
602
603/// 给一个单元格的所有文字段叠加样式(从各段现有样式起、合并闭包改动的字段)。
604fn style_cell<R>(cell: &mut Cell, f: &impl Fn(&mut StyleBuilder) -> R) {
605    for inl in &mut cell.inlines {
606        if let Inline::Text { style, .. } = inl {
607            let mut sb = StyleBuilder { style: style.clone() };
608            let _ = f(&mut sb);
609            *style = sb.style;
610        }
611    }
612}
613
614/// 并排栏构建器。
615pub struct ColumnsBuilder {
616    gap: Option<f32>,
617    cols: Vec<Column>,
618}
619
620impl ColumnsBuilder {
621    /// 栏间距(逻辑像素;非有限或 < 0 忽略)。
622    pub fn gap(&mut self, g: f32) -> &mut Self {
623        if g.is_finite() && g >= 0.0 {
624            self.gap = Some(g);
625        }
626        self
627    }
628    /// 一栏(权重 1.0)。
629    pub fn col<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
630        self.col_weighted(1.0, f)
631    }
632    /// 一栏(指定宽度权重;非有限或 ≤ 0 回退 1.0,与标记前端口径一致)。
633    pub fn col_weighted<R>(&mut self, weight: f32, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
634        let weight = if weight.is_finite() && weight > 0.0 { weight } else { 1.0 };
635        let mut inner = Doc::new();
636        let _ = f(&mut inner);
637        self.cols.push(Column { blocks: inner.blocks, weight });
638        self
639    }
640    /// 一栏卡片(权重 1.0):整栏就是一个面板,装饰盒自动拉齐到本行最高栏。
641    pub fn panel<R>(&mut self, f: impl FnOnce(&mut PanelBuilder) -> R) -> &mut Self {
642        self.panel_weighted(1.0, f)
643    }
644    /// 一栏卡片(指定宽度权重;非法权重回退 1.0)。
645    pub fn panel_weighted<R>(&mut self, weight: f32, f: impl FnOnce(&mut PanelBuilder) -> R) -> &mut Self {
646        let weight = if weight.is_finite() && weight > 0.0 { weight } else { 1.0 };
647        let mut pb = PanelBuilder::new();
648        let _ = f(&mut pb);
649        self.cols.push(Column { blocks: vec![Block::Panel(pb.into_panel())], weight });
650        self
651    }
652}
653
654/// 面板构建器([`Doc::panel`] / [`ColumnsBuilder::panel`] 的闭包参数):装饰方法在本体,
655/// 内容方法经 `Deref` 落到内层 [`Doc`](先配装饰再加内容,内容方法返回 `&mut Doc`,
656/// 链上接不回装饰方法)。
657pub struct PanelBuilder {
658    doc: Doc,
659    decor: PanelDecor,
660}
661
662impl PanelBuilder {
663    fn new() -> Self {
664        Self { doc: Doc::new(), decor: PanelDecor::default() }
665    }
666
667    fn into_panel(self) -> Panel {
668        Panel { blocks: self.doc.blocks, decor: self.decor }
669    }
670
671    /// 底色(十六进制,可含 alpha;非法忽略)。
672    pub fn bg(&mut self, hex: &str) -> &mut Self {
673        if let Some(c) = Color::hex(hex) {
674            self.decor.bg = Some(c);
675        }
676        self
677    }
678    /// 边框:线宽(逻辑像素)+ 十六进制色(非法色忽略整条)。
679    pub fn border(&mut self, width: f32, hex: &str) -> &mut Self {
680        if width > 0.0 && width.is_finite() {
681            if let Some(color) = Color::hex(hex) {
682                self.decor.border = Some(ImageBorder { width, color });
683            }
684        }
685        self
686    }
687    /// 圆角半径(逻辑像素;0 = 直角)。
688    pub fn rounded(&mut self, radius: f32) -> &mut Self {
689        if radius.is_finite() && radius >= 0.0 {
690            self.decor.radius = Some(radius);
691        }
692        self
693    }
694    /// 内边距(逻辑像素;非有限或 < 0 忽略)。
695    pub fn pad(&mut self, px: f32) -> &mut Self {
696        if px.is_finite() && px >= 0.0 {
697            self.decor.pad = Some(px);
698        }
699        self
700    }
701    /// 投影(默认形态:下坠 2 逻辑像素、软化 6、黑 25%)。
702    pub fn shadow(&mut self) -> &mut Self {
703        self.decor.shadow = Some(Shadow::default());
704        self
705    }
706    /// 投影(自定形态:偏移/软化为逻辑像素,色为十六进制,非法色忽略整条)。
707    pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
708        if let Some(color) = Color::hex(hex) {
709            self.decor.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
710        }
711        self
712    }
713}
714
715impl std::ops::Deref for PanelBuilder {
716    type Target = Doc;
717    fn deref(&self) -> &Doc {
718        &self.doc
719    }
720}
721
722impl std::ops::DerefMut for PanelBuilder {
723    fn deref_mut(&mut self) -> &mut Doc {
724        &mut self.doc
725    }
726}
727
728/// 进度条构建器([`Doc::progress`] 的闭包参数)。
729pub struct ProgressBuilder {
730    p: Progress,
731}
732
733impl ProgressBuilder {
734    /// 条高(逻辑像素,默认 10)。
735    pub fn height(&mut self, h: f32) -> &mut Self {
736        self.p.height = h;
737        self
738    }
739    /// 填充色(默认主题强调色)。
740    pub fn fill(&mut self, hex: &str) -> &mut Self {
741        self.p.fill = Color::hex(hex).or(self.p.fill);
742        self
743    }
744    /// 底槽色(默认主题边框色)。
745    pub fn track(&mut self, hex: &str) -> &mut Self {
746        self.p.track = Color::hex(hex).or(self.p.track);
747        self
748    }
749    /// 圆角半径(逻辑像素,默认半高即胶囊形;0 = 直角)。
750    pub fn radius(&mut self, r: f32) -> &mut Self {
751        self.p.radius = Some(r);
752        self
753    }
754    /// 条宽(绝对逻辑像素;默认铺满内容宽)。
755    pub fn width_px(&mut self, px: f32) -> &mut Self {
756        self.p.width = Some(Length::Px(px));
757        self
758    }
759    /// 条宽(内容宽的百分比)。
760    pub fn width_percent(&mut self, pct: f32) -> &mut Self {
761        self.p.width = Some(Length::Percent(pct));
762        self
763    }
764    /// 水平对齐(窄于内容宽时生效)。
765    pub fn align(&mut self, a: Align) -> &mut Self {
766        self.p.align = a;
767        self
768    }
769}
770
771/// 列表构建器。
772pub struct ListBuilder {
773    kind: ListKind,
774    start: u32,
775    items: Vec<ListItem>,
776}
777
778impl ListBuilder {
779    /// 有序列表起始序号。
780    pub fn start(&mut self, n: u32) -> &mut Self {
781        self.start = n;
782        self
783    }
784    /// 一个列表项(内容是块容器,可放多段 / 嵌套子列表)。
785    pub fn item<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
786        let mut inner = Doc::new();
787        let _ = f(&mut inner);
788        self.items.push(ListItem { blocks: inner.blocks, check: None });
789        self
790    }
791    /// 一个任务项(`done` = 已完成):标记渲染成 `✓` / `□`,对应标记文本的 `- [x]` / `- [ ]`。
792    pub fn task<R>(&mut self, done: bool, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
793        let mut inner = Doc::new();
794        let _ = f(&mut inner);
795        self.items.push(ListItem { blocks: inner.blocks, check: Some(done) });
796        self
797    }
798}
799
800/// 块级图片构建器。
801pub struct ImageBuilder {
802    width: Option<Length>,
803    align: Align,
804    caption: Option<Vec<Inline>>,
805    decor: ImageDecor,
806}
807
808impl ImageBuilder {
809    /// 绝对宽度(逻辑像素)。
810    pub fn width_px(&mut self, px: f32) -> &mut Self {
811        self.width = Some(Length::Px(px));
812        self
813    }
814    /// 相对内容宽的百分比宽度。
815    pub fn width_percent(&mut self, pct: f32) -> &mut Self {
816        self.width = Some(Length::Percent(pct));
817        self
818    }
819    /// 对齐。
820    pub fn align(&mut self, a: Align) -> &mut Self {
821        self.align = a;
822        self
823    }
824    /// 纯文字图注。
825    pub fn caption(&mut self, s: impl Into<String>) -> &mut Self {
826        self.caption = Some(vec![Inline::Text { text: s.into(), style: TextStyle::default() }]);
827        self
828    }
829    /// 富文字图注(闭包配置行内)。
830    pub fn caption_with<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
831        let mut pb = ParaBuilder::new();
832        let _ = f(&mut pb);
833        self.caption = Some(pb.inlines);
834        self
835    }
836
837    // ── 装饰层:角标 / 边框 / 水印 / 圆角 / 阴影(画在图面上,不改布局尺寸) ──
838
839    /// 角标(默认右上角、黑底白字),闭包微调:`im.badge("动图", |b| b.anchor(..).bg(..))`。
840    pub fn badge<R>(&mut self, text: impl Into<String>, f: impl FnOnce(&mut BadgeBuilder) -> R) -> &mut Self {
841        let mut bb = BadgeBuilder { badge: Badge::new(text) };
842        let _ = f(&mut bb);
843        self.decor.badge = Some(bb.badge);
844        self
845    }
846    /// 边框:线宽(逻辑像素)+ 十六进制色(非法色忽略整条);有圆角时随圆角描边。
847    pub fn border(&mut self, width: f32, hex: &str) -> &mut Self {
848        if width > 0.0 && width.is_finite() {
849            if let Some(color) = Color::hex(hex) {
850                self.decor.border = Some(ImageBorder { width, color });
851            }
852        }
853        self
854    }
855    /// 水印(默认右下角、白 40%),闭包微调:`im.watermark("abot", |w| w.anchor(..))`。
856    pub fn watermark<R>(&mut self, text: impl Into<String>, f: impl FnOnce(&mut WatermarkBuilder) -> R) -> &mut Self {
857        let mut wb = WatermarkBuilder { wm: Watermark::new(text) };
858        let _ = f(&mut wb);
859        self.decor.watermark = Some(wb.wm);
860        self
861    }
862    /// 圆角半径(逻辑像素):裁切图面四角。
863    pub fn rounded(&mut self, radius: f32) -> &mut Self {
864        if radius.is_finite() && radius > 0.0 {
865            self.decor.radius = radius;
866        }
867        self
868    }
869    /// 投影(默认形态:下坠 2 逻辑像素、软化 6、黑 25%)。
870    pub fn shadow(&mut self) -> &mut Self {
871        self.decor.shadow = Some(Shadow::default());
872        self
873    }
874    /// 投影(自定形态:偏移/软化为逻辑像素,色为十六进制,非法色忽略整条)。
875    pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
876        if let Some(color) = Color::hex(hex) {
877            self.decor.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
878        }
879        self
880    }
881}
882
883/// 角标微调构建器。
884pub struct BadgeBuilder {
885    badge: Badge,
886}
887
888impl BadgeBuilder {
889    /// 停靠角。
890    pub fn anchor(&mut self, a: Anchor) -> &mut Self {
891        self.badge.anchor = a;
892        self
893    }
894    /// 底板色(十六进制,可含 alpha;非法忽略)。
895    pub fn bg(&mut self, hex: &str) -> &mut Self {
896        if let Some(c) = Color::hex(hex) {
897            self.badge.bg = c;
898        }
899        self
900    }
901    /// 文字色(十六进制;非法忽略)。
902    pub fn fg(&mut self, hex: &str) -> &mut Self {
903        if let Some(c) = Color::hex(hex) {
904            self.badge.fg = c;
905        }
906        self
907    }
908    /// 字号倍率(相对基准;非法忽略)。
909    pub fn size(&mut self, mult: f32) -> &mut Self {
910        if mult.is_finite() && mult > 0.0 {
911            self.badge.size = mult;
912        }
913        self
914    }
915}
916
917/// 水印微调构建器。
918pub struct WatermarkBuilder {
919    wm: Watermark,
920}
921
922impl WatermarkBuilder {
923    /// 停靠处(四角或正中)。
924    pub fn anchor(&mut self, a: Anchor) -> &mut Self {
925        self.wm.anchor = a;
926        self
927    }
928    /// 颜色(十六进制,可含 alpha;非法忽略)。
929    pub fn color(&mut self, hex: &str) -> &mut Self {
930        if let Some(c) = Color::hex(hex) {
931            self.wm.color = c;
932        }
933        self
934    }
935    /// 字号倍率(相对基准;非法忽略)。
936    pub fn size(&mut self, mult: f32) -> &mut Self {
937        if mult.is_finite() && mult > 0.0 {
938            self.wm.size = mult;
939        }
940        self
941    }
942}