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    /// 圈注:以这段文字为中心画一圈椭圆描边(缺省按文字宽窄自适应、颜色跟随墨色;
356    /// 不占布局尺寸,圈溢出到行距)。尺寸经 [`ring_radius`](Self::ring_radius) /
357    /// [`ring_radii`](Self::ring_radii) 给定后与文字宽窄无关。
358    pub fn ring(&mut self) -> &mut Self {
359        self.style.ring.get_or_insert_default();
360        self
361    }
362    /// 圈注描边色(十六进制;非法忽略,跟随墨色)。
363    pub fn ring_color(&mut self, hex: &str) -> &mut Self {
364        let r = self.style.ring.get_or_insert_default();
365        r.color = Color::hex(hex).or(r.color);
366        self
367    }
368    /// 圈注定径:**正圆**,半径 `r`(逻辑像素)——多字与单字圈出同样大的圈。
369    pub fn ring_radius(&mut self, r: f32) -> &mut Self {
370        self.ring_radii(r, r)
371    }
372    /// 圈注定径:**椭圆**,横/纵半径(逻辑像素)。非有限或 ≤ 0 的分量忽略(保持自适应)。
373    pub fn ring_radii(&mut self, rx: f32, ry: f32) -> &mut Self {
374        let r = self.style.ring.get_or_insert_default();
375        if rx.is_finite() && rx > 0.0 {
376            r.rx = Some(rx);
377        }
378        if ry.is_finite() && ry > 0.0 {
379            r.ry = Some(ry);
380        }
381        self
382    }
383    /// 圈注线宽(逻辑像素;非有限或 ≤ 0 忽略,保持 0.07 倍字号缺省)。
384    pub fn ring_stroke(&mut self, w: f32) -> &mut Self {
385        let r = self.style.ring.get_or_insert_default();
386        if w.is_finite() && w > 0.0 {
387            r.width = Some(w);
388        }
389        self
390    }
391    /// 逐字圈:整段一字一圈(空白跳过),未定径时按字取**正圆**。缺省是整段一个圈
392    /// (范围圈,自适应为扁椭圆)。
393    pub fn ring_each(&mut self) -> &mut Self {
394        self.style.ring.get_or_insert_default().each = true;
395        self
396    }
397    /// 着重点:这段文字正下方一枚实心小点(颜色跟随墨色;画进行距,不占高度)。
398    pub fn dot(&mut self) -> &mut Self {
399        self.style.dot.get_or_insert_default();
400        self
401    }
402    /// 着重点颜色(十六进制;非法忽略,跟随墨色)。
403    pub fn dot_color(&mut self, hex: &str) -> &mut Self {
404        let d = self.style.dot.get_or_insert_default();
405        d.color = Color::hex(hex).or(d.color);
406        self
407    }
408    /// 着重点半径(逻辑像素;非有限或 ≤ 0 忽略,保持 0.09 倍字号缺省)。
409    pub fn dot_radius(&mut self, r: f32) -> &mut Self {
410        let d = self.style.dot.get_or_insert_default();
411        if r.is_finite() && r > 0.0 {
412            d.radius = Some(r);
413        }
414        self
415    }
416    /// 逐字点:一字一点(中文着重号的正字法;空白跳过)。缺省是整段中线下一点。
417    pub fn dot_each(&mut self) -> &mut Self {
418        self.style.dot.get_or_insert_default().each = true;
419        self
420    }
421    /// 文字阴影(默认形态:下坠 2 逻辑像素、软化 6、黑 25%)。
422    /// 边注挂右:这段挂到本行内容的右外侧,参与绘制不参与布局——居中 / 对齐按其余
423    /// 内容算,边注不挤不偏(「当前」「✓」这类行尾标记用)。
424    pub fn aside_right(&mut self) -> &mut Self {
425        self.style.aside = Some(crate::model::AsideSide::Right);
426        self
427    }
428    /// 边注挂左:同 [`aside_right`](Self::aside_right),停靠行首左外侧。
429    pub fn aside_left(&mut self) -> &mut Self {
430        self.style.aside = Some(crate::model::AsideSide::Left);
431        self
432    }
433    pub fn shadow(&mut self) -> &mut Self {
434        self.style.shadow = Some(Shadow::default());
435        self
436    }
437    /// 文字阴影(自定形态:偏移/软化为逻辑像素,色为十六进制,非法色忽略整条)。
438    pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
439        if let Some(color) = Color::hex(hex) {
440            self.style.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
441        }
442        self
443    }
444}
445
446/// 表格构建器(纯文字单元格)。
447pub struct TableBuilder {
448    header: Option<Vec<Cell>>,
449    rows: Vec<Vec<Cell>>,
450    cols: Vec<ColSpec>,
451    style: TableStyle,
452}
453
454impl TableBuilder {
455    /// 设表头。
456    pub fn head<I, S>(&mut self, cells: I) -> &mut Self
457    where
458        I: IntoIterator<Item = S>,
459        S: Into<String>,
460    {
461        self.header = Some(cells.into_iter().map(text_cell).collect());
462        self
463    }
464    /// 加一数据行。
465    pub fn row<I, S>(&mut self, cells: I) -> &mut Self
466    where
467        I: IntoIterator<Item = S>,
468        S: Into<String>,
469    {
470        self.rows.push(cells.into_iter().map(text_cell).collect());
471        self
472    }
473    /// 设各列对齐(从第 0 列起)。
474    pub fn align<I: IntoIterator<Item = Align>>(&mut self, aligns: I) -> &mut Self {
475        for (k, a) in aligns.into_iter().enumerate() {
476            self.ensure_col(k).align = a;
477        }
478        self
479    }
480    /// 给某列(0 起)限宽。
481    pub fn width(&mut self, col: usize, w: Length) -> &mut Self {
482        self.ensure_col(col).width = Some(w);
483        self
484    }
485    fn ensure_col(&mut self, k: usize) -> &mut ColSpec {
486        while self.cols.len() <= k {
487            self.cols.push(ColSpec::default());
488        }
489        &mut self.cols[k]
490    }
491
492    // ── 按列 / 行 / 格设文字样式 + 背景(在已加的单元格上叠加;先加行,再设样式) ──
493
494    /// 整列(含表头)的文字样式。
495    pub fn col_style<R>(&mut self, col: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
496        if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
497            style_cell(h, &f);
498        }
499        for row in &mut self.rows {
500            if let Some(c) = row.get_mut(col) {
501                style_cell(c, &f);
502            }
503        }
504        self
505    }
506    /// 整行(数据行,0 起)的文字样式。
507    pub fn row_style<R>(&mut self, row: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
508        if let Some(r) = self.rows.get_mut(row) {
509            for c in r.iter_mut() {
510                style_cell(c, &f);
511            }
512        }
513        self
514    }
515    /// 单格(数据行 / 列,0 起)的文字样式。
516    pub fn cell_style<R>(
517        &mut self,
518        row: usize,
519        col: usize,
520        f: impl Fn(&mut StyleBuilder) -> R,
521    ) -> &mut Self {
522        if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
523            style_cell(c, &f);
524        }
525        self
526    }
527    /// 整列(含表头)背景填色。
528    pub fn col_fill(&mut self, col: usize, hex: &str) -> &mut Self {
529        let bg = Color::hex(hex);
530        if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
531            h.bg = bg;
532        }
533        for row in &mut self.rows {
534            if let Some(c) = row.get_mut(col) {
535                c.bg = bg;
536            }
537        }
538        self
539    }
540    /// 整行(数据行)背景填色。
541    pub fn row_fill(&mut self, row: usize, hex: &str) -> &mut Self {
542        let bg = Color::hex(hex);
543        if let Some(r) = self.rows.get_mut(row) {
544            for c in r.iter_mut() {
545                c.bg = bg;
546            }
547        }
548        self
549    }
550    /// 单格(数据行 / 列)背景填色。
551    pub fn cell_fill(&mut self, row: usize, col: usize, hex: &str) -> &mut Self {
552        if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
553            c.bg = Color::hex(hex);
554        }
555        self
556    }
557
558    // ── 紧凑度 + 网格线 ──
559
560    /// 列内边距(单元格左右,逻辑像素);越小列越紧凑。
561    pub fn pad_x(&mut self, px: f32) -> &mut Self {
562        self.style.pad_x = Some(px.max(0.0));
563        self
564    }
565    /// 行内边距(单元格上下,逻辑像素);越小行越紧凑、行距越小。
566    pub fn pad_y(&mut self, px: f32) -> &mut Self {
567        self.style.pad_y = Some(px.max(0.0));
568        self
569    }
570    /// 拉伸铺满可用宽(富余宽度按比例分给自适应列;全固定列则整体等比放大)。
571    pub fn expand(&mut self) -> &mut Self {
572        self.style.expand = true;
573        self
574    }
575    /// 外框线开关。
576    pub fn grid_outer(&mut self, on: bool) -> &mut Self {
577        self.style.grid.outer = on;
578        self
579    }
580    /// 列竖线开关。
581    pub fn grid_vertical(&mut self, on: bool) -> &mut Self {
582        self.style.grid.vertical = on;
583        self
584    }
585    /// 行横线开关。
586    pub fn grid_horizontal(&mut self, on: bool) -> &mut Self {
587        self.style.grid.horizontal = on;
588        self
589    }
590    /// 去掉所有网格线(外框 / 竖线 / 横线)。
591    pub fn no_grid(&mut self) -> &mut Self {
592        self.style.grid.outer = false;
593        self.style.grid.vertical = false;
594        self.style.grid.horizontal = false;
595        self
596    }
597    /// 表头浅底开关。
598    pub fn header_fill(&mut self, on: bool) -> &mut Self {
599        self.style.header_fill = on;
600        self
601    }
602}
603
604/// 纯文字单元格。
605fn text_cell(s: impl Into<String>) -> Cell {
606    Cell { inlines: vec![Inline::Text { text: s.into(), style: TextStyle::default() }], bg: None }
607}
608
609/// 给一个单元格的所有文字段叠加样式(从各段现有样式起、合并闭包改动的字段)。
610fn style_cell<R>(cell: &mut Cell, f: &impl Fn(&mut StyleBuilder) -> R) {
611    for inl in &mut cell.inlines {
612        if let Inline::Text { style, .. } = inl {
613            let mut sb = StyleBuilder { style: style.clone() };
614            let _ = f(&mut sb);
615            *style = sb.style;
616        }
617    }
618}
619
620/// 并排栏构建器。
621pub struct ColumnsBuilder {
622    gap: Option<f32>,
623    cols: Vec<Column>,
624}
625
626impl ColumnsBuilder {
627    /// 栏间距(逻辑像素)。
628    pub fn gap(&mut self, g: f32) -> &mut Self {
629        self.gap = Some(g);
630        self
631    }
632    /// 一栏(权重 1.0)。
633    pub fn col<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
634        self.col_weighted(1.0, f)
635    }
636    /// 一栏(指定宽度权重)。
637    pub fn col_weighted<R>(&mut self, weight: f32, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
638        let mut inner = Doc::new();
639        let _ = f(&mut inner);
640        self.cols.push(Column { blocks: inner.blocks, weight });
641        self
642    }
643    /// 一栏卡片(权重 1.0):整栏就是一个面板,装饰盒自动拉齐到本行最高栏。
644    pub fn panel<R>(&mut self, f: impl FnOnce(&mut PanelBuilder) -> R) -> &mut Self {
645        self.panel_weighted(1.0, f)
646    }
647    /// 一栏卡片(指定宽度权重)。
648    pub fn panel_weighted<R>(
649        &mut self,
650        weight: f32,
651        f: impl FnOnce(&mut PanelBuilder) -> R,
652    ) -> &mut Self {
653        let mut pb = PanelBuilder::new();
654        let _ = f(&mut pb);
655        self.cols.push(Column { blocks: vec![Block::Panel(pb.into_panel())], weight });
656        self
657    }
658}
659
660/// 面板构建器([`Doc::panel`] / [`ColumnsBuilder::panel`] 的闭包参数):装饰方法在本体,
661/// 内容方法经 `Deref` 落到内层 [`Doc`](先配装饰再加内容,内容方法返回 `&mut Doc`,
662/// 链上接不回装饰方法)。
663pub struct PanelBuilder {
664    doc: Doc,
665    decor: PanelDecor,
666}
667
668impl PanelBuilder {
669    fn new() -> Self {
670        Self { doc: Doc::new(), decor: PanelDecor::default() }
671    }
672
673    fn into_panel(self) -> Panel {
674        Panel { blocks: self.doc.blocks, decor: self.decor }
675    }
676
677    /// 底色(十六进制,可含 alpha;非法忽略)。
678    pub fn bg(&mut self, hex: &str) -> &mut Self {
679        if let Some(c) = Color::hex(hex) {
680            self.decor.bg = Some(c);
681        }
682        self
683    }
684    /// 边框:线宽(逻辑像素)+ 十六进制色(非法色忽略整条)。
685    pub fn border(&mut self, width: f32, hex: &str) -> &mut Self {
686        if width > 0.0 && width.is_finite() {
687            if let Some(color) = Color::hex(hex) {
688                self.decor.border = Some(ImageBorder { width, color });
689            }
690        }
691        self
692    }
693    /// 圆角半径(逻辑像素;0 = 直角)。
694    pub fn rounded(&mut self, radius: f32) -> &mut Self {
695        if radius.is_finite() && radius >= 0.0 {
696            self.decor.radius = Some(radius);
697        }
698        self
699    }
700    /// 内边距(逻辑像素;非有限或 < 0 忽略)。
701    pub fn pad(&mut self, px: f32) -> &mut Self {
702        if px.is_finite() && px >= 0.0 {
703            self.decor.pad = Some(px);
704        }
705        self
706    }
707    /// 投影(默认形态:下坠 2 逻辑像素、软化 6、黑 25%)。
708    pub fn shadow(&mut self) -> &mut Self {
709        self.decor.shadow = Some(Shadow::default());
710        self
711    }
712    /// 投影(自定形态:偏移/软化为逻辑像素,色为十六进制,非法色忽略整条)。
713    pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
714        if let Some(color) = Color::hex(hex) {
715            self.decor.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
716        }
717        self
718    }
719}
720
721impl std::ops::Deref for PanelBuilder {
722    type Target = Doc;
723    fn deref(&self) -> &Doc {
724        &self.doc
725    }
726}
727
728impl std::ops::DerefMut for PanelBuilder {
729    fn deref_mut(&mut self) -> &mut Doc {
730        &mut self.doc
731    }
732}
733
734/// 进度条构建器([`Doc::progress`] 的闭包参数)。
735pub struct ProgressBuilder {
736    p: Progress,
737}
738
739impl ProgressBuilder {
740    /// 条高(逻辑像素,默认 10)。
741    pub fn height(&mut self, h: f32) -> &mut Self {
742        self.p.height = h;
743        self
744    }
745    /// 填充色(默认主题强调色)。
746    pub fn fill(&mut self, hex: &str) -> &mut Self {
747        self.p.fill = Color::hex(hex).or(self.p.fill);
748        self
749    }
750    /// 底槽色(默认主题边框色)。
751    pub fn track(&mut self, hex: &str) -> &mut Self {
752        self.p.track = Color::hex(hex).or(self.p.track);
753        self
754    }
755    /// 圆角半径(逻辑像素,默认半高即胶囊形;0 = 直角)。
756    pub fn radius(&mut self, r: f32) -> &mut Self {
757        self.p.radius = Some(r);
758        self
759    }
760    /// 条宽(绝对逻辑像素;默认铺满内容宽)。
761    pub fn width_px(&mut self, px: f32) -> &mut Self {
762        self.p.width = Some(Length::Px(px));
763        self
764    }
765    /// 条宽(内容宽的百分比)。
766    pub fn width_percent(&mut self, pct: f32) -> &mut Self {
767        self.p.width = Some(Length::Percent(pct));
768        self
769    }
770    /// 水平对齐(窄于内容宽时生效)。
771    pub fn align(&mut self, a: Align) -> &mut Self {
772        self.p.align = a;
773        self
774    }
775}
776
777/// 列表构建器。
778pub struct ListBuilder {
779    kind: ListKind,
780    start: u32,
781    items: Vec<ListItem>,
782}
783
784impl ListBuilder {
785    /// 有序列表起始序号。
786    pub fn start(&mut self, n: u32) -> &mut Self {
787        self.start = n;
788        self
789    }
790    /// 一个列表项(内容是块容器,可放多段 / 嵌套子列表)。
791    pub fn item<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
792        let mut inner = Doc::new();
793        let _ = f(&mut inner);
794        self.items.push(ListItem { blocks: inner.blocks, check: None });
795        self
796    }
797    /// 一个任务项(`done` = 已完成):标记渲染成 `✓` / `□`,对应标记文本的 `- [x]` / `- [ ]`。
798    pub fn task<R>(&mut self, done: bool, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
799        let mut inner = Doc::new();
800        let _ = f(&mut inner);
801        self.items.push(ListItem { blocks: inner.blocks, check: Some(done) });
802        self
803    }
804}
805
806/// 块级图片构建器。
807pub struct ImageBuilder {
808    width: Option<Length>,
809    align: Align,
810    caption: Option<Vec<Inline>>,
811    decor: ImageDecor,
812}
813
814impl ImageBuilder {
815    /// 绝对宽度(逻辑像素)。
816    pub fn width_px(&mut self, px: f32) -> &mut Self {
817        self.width = Some(Length::Px(px));
818        self
819    }
820    /// 相对内容宽的百分比宽度。
821    pub fn width_percent(&mut self, pct: f32) -> &mut Self {
822        self.width = Some(Length::Percent(pct));
823        self
824    }
825    /// 对齐。
826    pub fn align(&mut self, a: Align) -> &mut Self {
827        self.align = a;
828        self
829    }
830    /// 纯文字图注。
831    pub fn caption(&mut self, s: impl Into<String>) -> &mut Self {
832        self.caption = Some(vec![Inline::Text { text: s.into(), style: TextStyle::default() }]);
833        self
834    }
835    /// 富文字图注(闭包配置行内)。
836    pub fn caption_with<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
837        let mut pb = ParaBuilder::new();
838        let _ = f(&mut pb);
839        self.caption = Some(pb.inlines);
840        self
841    }
842
843    // ── 装饰层:角标 / 边框 / 水印 / 圆角 / 阴影(画在图面上,不改布局尺寸) ──
844
845    /// 角标(默认右上角、黑底白字),闭包微调:`im.badge("动图", |b| b.anchor(..).bg(..))`。
846    pub fn badge<R>(
847        &mut self,
848        text: impl Into<String>,
849        f: impl FnOnce(&mut BadgeBuilder) -> R,
850    ) -> &mut Self {
851        let mut bb = BadgeBuilder { badge: Badge::new(text) };
852        let _ = f(&mut bb);
853        self.decor.badge = Some(bb.badge);
854        self
855    }
856    /// 边框:线宽(逻辑像素)+ 十六进制色(非法色忽略整条);有圆角时随圆角描边。
857    pub fn border(&mut self, width: f32, hex: &str) -> &mut Self {
858        if width > 0.0 && width.is_finite() {
859            if let Some(color) = Color::hex(hex) {
860                self.decor.border = Some(ImageBorder { width, color });
861            }
862        }
863        self
864    }
865    /// 水印(默认右下角、白 40%),闭包微调:`im.watermark("abot", |w| w.anchor(..))`。
866    pub fn watermark<R>(
867        &mut self,
868        text: impl Into<String>,
869        f: impl FnOnce(&mut WatermarkBuilder) -> R,
870    ) -> &mut Self {
871        let mut wb = WatermarkBuilder { wm: Watermark::new(text) };
872        let _ = f(&mut wb);
873        self.decor.watermark = Some(wb.wm);
874        self
875    }
876    /// 圆角半径(逻辑像素):裁切图面四角。
877    pub fn rounded(&mut self, radius: f32) -> &mut Self {
878        if radius.is_finite() && radius > 0.0 {
879            self.decor.radius = radius;
880        }
881        self
882    }
883    /// 投影(默认形态:下坠 2 逻辑像素、软化 6、黑 25%)。
884    pub fn shadow(&mut self) -> &mut Self {
885        self.decor.shadow = Some(Shadow::default());
886        self
887    }
888    /// 投影(自定形态:偏移/软化为逻辑像素,色为十六进制,非法色忽略整条)。
889    pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
890        if let Some(color) = Color::hex(hex) {
891            self.decor.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
892        }
893        self
894    }
895}
896
897/// 角标微调构建器。
898pub struct BadgeBuilder {
899    badge: Badge,
900}
901
902impl BadgeBuilder {
903    /// 停靠角。
904    pub fn anchor(&mut self, a: Anchor) -> &mut Self {
905        self.badge.anchor = a;
906        self
907    }
908    /// 底板色(十六进制,可含 alpha;非法忽略)。
909    pub fn bg(&mut self, hex: &str) -> &mut Self {
910        if let Some(c) = Color::hex(hex) {
911            self.badge.bg = c;
912        }
913        self
914    }
915    /// 文字色(十六进制;非法忽略)。
916    pub fn fg(&mut self, hex: &str) -> &mut Self {
917        if let Some(c) = Color::hex(hex) {
918            self.badge.fg = c;
919        }
920        self
921    }
922    /// 字号倍率(相对基准;非法忽略)。
923    pub fn size(&mut self, mult: f32) -> &mut Self {
924        if mult.is_finite() && mult > 0.0 {
925            self.badge.size = mult;
926        }
927        self
928    }
929}
930
931/// 水印微调构建器。
932pub struct WatermarkBuilder {
933    wm: Watermark,
934}
935
936impl WatermarkBuilder {
937    /// 停靠处(四角或正中)。
938    pub fn anchor(&mut self, a: Anchor) -> &mut Self {
939        self.wm.anchor = a;
940        self
941    }
942    /// 颜色(十六进制,可含 alpha;非法忽略)。
943    pub fn color(&mut self, hex: &str) -> &mut Self {
944        if let Some(c) = Color::hex(hex) {
945            self.wm.color = c;
946        }
947        self
948    }
949    /// 字号倍率(相对基准;非法忽略)。
950    pub fn size(&mut self, mult: f32) -> &mut Self {
951        if mult.is_finite() && mult > 0.0 {
952            self.wm.size = mult;
953        }
954        self
955    }
956}
957