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