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