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