1use std::path::PathBuf;
17
18use crate::model::{
19 Align, Anchor, Badge, Block, BlockImage, Cell, ColSpec, Color, Column, Columns, Document, FontRole, Highlight,
20 ImageBorder, ImageDecor, Inline, Length, List, ListItem, ListKind, Panel, PanelDecor, Progress, Shadow, Table,
21 TableStyle, TextStyle, Watermark,
22};
23
24#[derive(Default)]
26pub struct Doc {
27 blocks: Vec<Block>,
28}
29
30impl Doc {
31 pub fn new() -> Self {
33 Self { blocks: Vec::new() }
34 }
35
36 pub fn build(&self) -> Document {
38 Document { blocks: self.blocks.clone() }
39 }
40
41 pub fn heading<R>(&mut self, level: u8, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
43 let mut pb = ParaBuilder::new();
44 let _ = f(&mut pb);
45 self.blocks.push(Block::Heading { level: level.clamp(1, 6), inlines: pb.inlines, align: pb.align });
46 self
47 }
48
49 pub fn paragraph<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
51 let mut pb = ParaBuilder::new();
52 let _ = f(&mut pb);
53 self.blocks.push(Block::Paragraph { inlines: pb.inlines, align: pb.align });
54 self
55 }
56
57 pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
59 self.paragraph(|p| {
60 p.text(s);
61 })
62 }
63
64 pub fn quote<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
66 let mut inner = Doc::new();
67 let _ = f(&mut inner);
68 self.blocks.push(Block::Quote(inner.blocks));
69 self
70 }
71
72 pub fn list<R>(&mut self, kind: ListKind, f: impl FnOnce(&mut ListBuilder) -> R) -> &mut Self {
74 let mut lb = ListBuilder { kind, start: 1, items: Vec::new() };
75 let _ = f(&mut lb);
76 self.blocks.push(Block::List(List { kind: lb.kind, start: lb.start, items: lb.items }));
77 self
78 }
79
80 pub fn code(&mut self, lang: impl Into<String>, text: impl Into<String>) -> &mut Self {
82 let lang = lang.into();
83 self.blocks.push(Block::Code { lang: if lang.is_empty() { None } else { Some(lang) }, text: text.into() });
84 self
85 }
86
87 pub fn divider(&mut self) -> &mut Self {
89 self.blocks.push(Block::Divider);
90 self
91 }
92
93 pub fn panel<R>(&mut self, f: impl FnOnce(&mut PanelBuilder) -> R) -> &mut Self {
97 let mut pb = PanelBuilder::new();
98 let _ = f(&mut pb);
99 self.blocks.push(Block::Panel(pb.into_panel()));
100 self
101 }
102
103 pub fn columns<R>(&mut self, f: impl FnOnce(&mut ColumnsBuilder) -> R) -> &mut Self {
105 let mut cb = ColumnsBuilder { gap: None, cols: Vec::new() };
106 let _ = f(&mut cb);
107 self.blocks.push(Block::Columns(Columns { cols: cb.cols, gap: cb.gap }));
108 self
109 }
110
111 pub fn table<R>(&mut self, f: impl FnOnce(&mut TableBuilder) -> R) -> &mut Self {
113 let mut tb = TableBuilder { header: None, rows: Vec::new(), cols: Vec::new(), style: TableStyle::default() };
114 let _ = f(&mut tb);
115 self.blocks.push(Block::Table(Table { header: tb.header, rows: tb.rows, cols: tb.cols, style: tb.style }));
116 self
117 }
118
119 pub fn progress<R>(&mut self, value: f32, f: impl FnOnce(&mut ProgressBuilder) -> R) -> &mut Self {
123 let mut pb = ProgressBuilder {
124 p: Progress { value, height: 10.0, fill: None, track: None, radius: None, width: None, align: Align::Left },
125 };
126 let _ = f(&mut pb);
127 self.blocks.push(Block::Progress(pb.p));
128 self
129 }
130
131 pub fn image_bytes<R>(&mut self, bytes: Vec<u8>, f: impl FnOnce(&mut ImageBuilder) -> R) -> &mut Self {
133 self.push_block_image(ImageSource::Bytes(bytes), f)
134 }
135
136 pub fn image_path<R>(&mut self, path: impl Into<PathBuf>, f: impl FnOnce(&mut ImageBuilder) -> R) -> &mut Self {
138 self.push_block_image(ImageSource::Path(path.into()), f)
139 }
140
141 fn push_block_image<R>(&mut self, src: ImageSource, f: impl FnOnce(&mut ImageBuilder) -> R) -> &mut Self {
142 let mut ib = ImageBuilder { width: None, align: Align::Left, caption: None, decor: ImageDecor::default() };
143 let _ = f(&mut ib);
144 self.blocks.push(Block::Image(BlockImage {
145 src,
146 width: ib.width,
147 align: ib.align,
148 caption: ib.caption,
149 decor: ib.decor,
150 }));
151 self
152 }
153}
154
155use crate::model::ImageSource;
156
157pub struct ParaBuilder {
159 inlines: Vec<Inline>,
160 align: Align,
161}
162
163impl ParaBuilder {
164 pub(crate) fn new() -> Self {
165 Self { inlines: Vec::new(), align: Align::Left }
166 }
167
168 pub(crate) fn into_inlines(self) -> Vec<Inline> {
170 self.inlines
171 }
172
173 pub fn align(&mut self, a: Align) -> &mut Self {
175 self.align = a;
176 self
177 }
178
179 pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
181 self.push(s, TextStyle::default())
182 }
183
184 pub fn bold(&mut self, s: impl Into<String>) -> &mut Self {
186 self.push(s, TextStyle { weight: Some(700), ..Default::default() })
187 }
188
189 pub fn light(&mut self, s: impl Into<String>) -> &mut Self {
191 self.push(s, TextStyle { weight: Some(300), ..Default::default() })
192 }
193
194 pub fn italic(&mut self, s: impl Into<String>) -> &mut Self {
196 self.push(s, TextStyle { italic: true, ..Default::default() })
197 }
198
199 pub fn underline(&mut self, s: impl Into<String>) -> &mut Self {
201 self.push(s, TextStyle { underline: true, ..Default::default() })
202 }
203
204 pub fn strike(&mut self, s: impl Into<String>) -> &mut Self {
206 self.push(s, TextStyle { strike: true, ..Default::default() })
207 }
208
209 pub fn highlight(&mut self, s: impl Into<String>) -> &mut Self {
211 self.push(s, TextStyle { highlight: Some(Highlight::Theme), ..Default::default() })
212 }
213
214 pub fn color(&mut self, hex: &str, s: impl Into<String>) -> &mut Self {
216 self.push(s, TextStyle { color: Color::hex(hex), ..Default::default() })
217 }
218
219 pub fn code(&mut self, s: impl Into<String>) -> &mut Self {
221 self.inlines.push(Inline::Code(s.into()));
222 self
223 }
224
225 pub fn styled<R>(&mut self, s: impl Into<String>, f: impl FnOnce(&mut StyleBuilder) -> R) -> &mut Self {
227 let mut sb = StyleBuilder { style: TextStyle::default() };
228 let _ = f(&mut sb);
229 self.push(s, sb.style)
230 }
231
232 pub fn line_break(&mut self) -> &mut Self {
234 self.inlines.push(Inline::LineBreak);
235 self
236 }
237
238 fn push(&mut self, s: impl Into<String>, style: TextStyle) -> &mut Self {
239 self.inlines.push(Inline::Text { text: s.into(), style });
240 self
241 }
242}
243
244pub struct StyleBuilder {
246 style: TextStyle,
247}
248
249impl StyleBuilder {
250 pub fn bold(&mut self) -> &mut Self {
252 self.style.weight = Some(700);
253 self
254 }
255 pub fn light(&mut self) -> &mut Self {
257 self.style.weight = Some(300);
258 self
259 }
260 pub fn weight(&mut self, w: u16) -> &mut Self {
262 if (1..=1000).contains(&w) {
263 self.style.weight = Some(w);
264 }
265 self
266 }
267 pub fn italic(&mut self) -> &mut Self {
269 self.style.italic = true;
270 self
271 }
272 pub fn underline(&mut self) -> &mut Self {
274 self.style.underline = true;
275 self
276 }
277 pub fn strike(&mut self) -> &mut Self {
279 self.style.strike = true;
280 self
281 }
282 pub fn color(&mut self, hex: &str) -> &mut Self {
284 if let Some(c) = Color::hex(hex) {
285 self.style.color = Some(c);
286 }
287 self
288 }
289 pub fn bg(&mut self, hex: &str) -> &mut Self {
291 if let Some(c) = Color::hex(hex) {
292 self.style.highlight = Some(Highlight::Custom(c));
293 }
294 self
295 }
296 pub fn size(&mut self, mult: f32) -> &mut Self {
299 if mult.is_finite() && mult > 0.0 {
300 self.style.size = mult;
301 }
302 self
303 }
304 pub fn font(&mut self, role: FontRole) -> &mut Self {
306 self.style.font = role;
307 self
308 }
309 pub fn link(&mut self) -> &mut Self {
311 self.style.link = true;
312 self
313 }
314 pub fn ring(&mut self) -> &mut Self {
318 self.style.ring.get_or_insert_default();
319 self
320 }
321 pub fn ring_color(&mut self, hex: &str) -> &mut Self {
323 let r = self.style.ring.get_or_insert_default();
324 r.color = Color::hex(hex).or(r.color);
325 self
326 }
327 pub fn ring_radius(&mut self, r: f32) -> &mut Self {
329 self.ring_radii(r, r)
330 }
331 pub fn ring_radii(&mut self, rx: f32, ry: f32) -> &mut Self {
333 let r = self.style.ring.get_or_insert_default();
334 if rx.is_finite() && rx > 0.0 {
335 r.rx = Some(rx);
336 }
337 if ry.is_finite() && ry > 0.0 {
338 r.ry = Some(ry);
339 }
340 self
341 }
342 pub fn ring_stroke(&mut self, w: f32) -> &mut Self {
344 let r = self.style.ring.get_or_insert_default();
345 if w.is_finite() && w > 0.0 {
346 r.width = Some(w);
347 }
348 self
349 }
350 pub fn ring_each(&mut self) -> &mut Self {
353 self.style.ring.get_or_insert_default().each = true;
354 self
355 }
356 pub fn dot(&mut self) -> &mut Self {
358 self.style.dot.get_or_insert_default();
359 self
360 }
361 pub fn dot_color(&mut self, hex: &str) -> &mut Self {
363 let d = self.style.dot.get_or_insert_default();
364 d.color = Color::hex(hex).or(d.color);
365 self
366 }
367 pub fn dot_radius(&mut self, r: f32) -> &mut Self {
369 let d = self.style.dot.get_or_insert_default();
370 if r.is_finite() && r > 0.0 {
371 d.radius = Some(r);
372 }
373 self
374 }
375 pub fn dot_each(&mut self) -> &mut Self {
377 self.style.dot.get_or_insert_default().each = true;
378 self
379 }
380 pub fn aside_right(&mut self) -> &mut Self {
383 self.style.aside = Some(crate::model::AsideSide::Right);
384 self
385 }
386 pub fn aside_left(&mut self) -> &mut Self {
388 self.style.aside = Some(crate::model::AsideSide::Left);
389 self
390 }
391 pub fn shadow(&mut self) -> &mut Self {
393 self.style.shadow = Some(Shadow::default());
394 self
395 }
396 pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
398 if let Some(color) = Color::hex(hex) {
399 self.style.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
400 }
401 self
402 }
403}
404
405pub struct TableBuilder {
407 header: Option<Vec<Cell>>,
408 rows: Vec<Vec<Cell>>,
409 cols: Vec<ColSpec>,
410 style: TableStyle,
411}
412
413impl TableBuilder {
414 pub fn head<I, S>(&mut self, cells: I) -> &mut Self
416 where
417 I: IntoIterator<Item = S>,
418 S: Into<String>,
419 {
420 self.header = Some(cells.into_iter().map(text_cell).collect());
421 self
422 }
423 pub fn row<I, S>(&mut self, cells: I) -> &mut Self
425 where
426 I: IntoIterator<Item = S>,
427 S: Into<String>,
428 {
429 self.rows.push(cells.into_iter().map(text_cell).collect());
430 self
431 }
432 pub fn head_rich<R>(&mut self, f: impl FnOnce(&mut RowBuilder) -> R) -> &mut Self {
434 let mut rb = RowBuilder { cells: Vec::new() };
435 let _ = f(&mut rb);
436 self.header = Some(rb.cells);
437 self
438 }
439 pub fn row_rich<R>(&mut self, f: impl FnOnce(&mut RowBuilder) -> R) -> &mut Self {
441 let mut rb = RowBuilder { cells: Vec::new() };
442 let _ = f(&mut rb);
443 self.rows.push(rb.cells);
444 self
445 }
446 pub fn align<I: IntoIterator<Item = Align>>(&mut self, aligns: I) -> &mut Self {
448 for (k, a) in aligns.into_iter().enumerate() {
449 self.ensure_col(k).align = a;
450 }
451 self
452 }
453 pub fn width(&mut self, col: usize, w: Length) -> &mut Self {
455 self.ensure_col(col).width = Some(w);
456 self
457 }
458 fn ensure_col(&mut self, k: usize) -> &mut ColSpec {
459 while self.cols.len() <= k {
460 self.cols.push(ColSpec::default());
461 }
462 &mut self.cols[k]
463 }
464
465 pub fn col_style<R>(&mut self, col: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
469 if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
470 style_cell(h, &f);
471 }
472 for row in &mut self.rows {
473 if let Some(c) = row.get_mut(col) {
474 style_cell(c, &f);
475 }
476 }
477 self
478 }
479 pub fn row_style<R>(&mut self, row: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
481 if let Some(r) = self.rows.get_mut(row) {
482 for c in r.iter_mut() {
483 style_cell(c, &f);
484 }
485 }
486 self
487 }
488 pub fn cell_style<R>(&mut self, row: usize, col: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
490 if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
491 style_cell(c, &f);
492 }
493 self
494 }
495 pub fn col_fill(&mut self, col: usize, hex: &str) -> &mut Self {
497 let Some(bg) = Color::hex(hex) else { return self };
498 if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
499 h.bg = Some(bg);
500 }
501 for row in &mut self.rows {
502 if let Some(c) = row.get_mut(col) {
503 c.bg = Some(bg);
504 }
505 }
506 self
507 }
508 pub fn row_fill(&mut self, row: usize, hex: &str) -> &mut Self {
510 let Some(bg) = Color::hex(hex) else { return self };
511 if let Some(r) = self.rows.get_mut(row) {
512 for c in r.iter_mut() {
513 c.bg = Some(bg);
514 }
515 }
516 self
517 }
518 pub fn cell_fill(&mut self, row: usize, col: usize, hex: &str) -> &mut Self {
520 let Some(bg) = Color::hex(hex) else { return self };
521 if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
522 c.bg = Some(bg);
523 }
524 self
525 }
526
527 pub fn pad_x(&mut self, px: f32) -> &mut Self {
531 self.style.pad_x = Some(px.max(0.0));
532 self
533 }
534 pub fn pad_y(&mut self, px: f32) -> &mut Self {
536 self.style.pad_y = Some(px.max(0.0));
537 self
538 }
539 pub fn expand(&mut self) -> &mut Self {
541 self.style.expand = true;
542 self
543 }
544 pub fn table_align(&mut self, a: Align) -> &mut Self {
546 self.style.align = a;
547 self
548 }
549 pub fn grid_outer(&mut self, on: bool) -> &mut Self {
551 self.style.grid.outer = on;
552 self
553 }
554 pub fn grid_vertical(&mut self, on: bool) -> &mut Self {
556 self.style.grid.vertical = on;
557 self
558 }
559 pub fn grid_horizontal(&mut self, on: bool) -> &mut Self {
561 self.style.grid.horizontal = on;
562 self
563 }
564 pub fn no_grid(&mut self) -> &mut Self {
566 self.style.grid.outer = false;
567 self.style.grid.vertical = false;
568 self.style.grid.horizontal = false;
569 self
570 }
571 pub fn header_fill(&mut self, on: bool) -> &mut Self {
573 self.style.header_fill = on;
574 self
575 }
576}
577
578pub struct RowBuilder {
580 cells: Vec<Cell>,
581}
582
583impl RowBuilder {
584 pub fn cell<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
586 let mut pb = ParaBuilder::new();
587 let _ = f(&mut pb);
588 self.cells.push(Cell { inlines: pb.into_inlines(), bg: None });
589 self
590 }
591 pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
593 self.cells.push(text_cell(s));
594 self
595 }
596}
597
598fn text_cell(s: impl Into<String>) -> Cell {
600 Cell { inlines: vec![Inline::Text { text: s.into(), style: TextStyle::default() }], bg: None }
601}
602
603fn style_cell<R>(cell: &mut Cell, f: &impl Fn(&mut StyleBuilder) -> R) {
605 for inl in &mut cell.inlines {
606 if let Inline::Text { style, .. } = inl {
607 let mut sb = StyleBuilder { style: style.clone() };
608 let _ = f(&mut sb);
609 *style = sb.style;
610 }
611 }
612}
613
614pub struct ColumnsBuilder {
616 gap: Option<f32>,
617 cols: Vec<Column>,
618}
619
620impl ColumnsBuilder {
621 pub fn gap(&mut self, g: f32) -> &mut Self {
623 if g.is_finite() && g >= 0.0 {
624 self.gap = Some(g);
625 }
626 self
627 }
628 pub fn col<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
630 self.col_weighted(1.0, f)
631 }
632 pub fn col_weighted<R>(&mut self, weight: f32, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
634 let weight = if weight.is_finite() && weight > 0.0 { weight } else { 1.0 };
635 let mut inner = Doc::new();
636 let _ = f(&mut inner);
637 self.cols.push(Column { blocks: inner.blocks, weight });
638 self
639 }
640 pub fn panel<R>(&mut self, f: impl FnOnce(&mut PanelBuilder) -> R) -> &mut Self {
642 self.panel_weighted(1.0, f)
643 }
644 pub fn panel_weighted<R>(&mut self, weight: f32, f: impl FnOnce(&mut PanelBuilder) -> R) -> &mut Self {
646 let weight = if weight.is_finite() && weight > 0.0 { weight } else { 1.0 };
647 let mut pb = PanelBuilder::new();
648 let _ = f(&mut pb);
649 self.cols.push(Column { blocks: vec![Block::Panel(pb.into_panel())], weight });
650 self
651 }
652}
653
654pub struct PanelBuilder {
658 doc: Doc,
659 decor: PanelDecor,
660}
661
662impl PanelBuilder {
663 fn new() -> Self {
664 Self { doc: Doc::new(), decor: PanelDecor::default() }
665 }
666
667 fn into_panel(self) -> Panel {
668 Panel { blocks: self.doc.blocks, decor: self.decor }
669 }
670
671 pub fn bg(&mut self, hex: &str) -> &mut Self {
673 if let Some(c) = Color::hex(hex) {
674 self.decor.bg = Some(c);
675 }
676 self
677 }
678 pub fn border(&mut self, width: f32, hex: &str) -> &mut Self {
680 if width > 0.0 && width.is_finite() {
681 if let Some(color) = Color::hex(hex) {
682 self.decor.border = Some(ImageBorder { width, color });
683 }
684 }
685 self
686 }
687 pub fn rounded(&mut self, radius: f32) -> &mut Self {
689 if radius.is_finite() && radius >= 0.0 {
690 self.decor.radius = Some(radius);
691 }
692 self
693 }
694 pub fn pad(&mut self, px: f32) -> &mut Self {
696 if px.is_finite() && px >= 0.0 {
697 self.decor.pad = Some(px);
698 }
699 self
700 }
701 pub fn shadow(&mut self) -> &mut Self {
703 self.decor.shadow = Some(Shadow::default());
704 self
705 }
706 pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
708 if let Some(color) = Color::hex(hex) {
709 self.decor.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
710 }
711 self
712 }
713}
714
715impl std::ops::Deref for PanelBuilder {
716 type Target = Doc;
717 fn deref(&self) -> &Doc {
718 &self.doc
719 }
720}
721
722impl std::ops::DerefMut for PanelBuilder {
723 fn deref_mut(&mut self) -> &mut Doc {
724 &mut self.doc
725 }
726}
727
728pub struct ProgressBuilder {
730 p: Progress,
731}
732
733impl ProgressBuilder {
734 pub fn height(&mut self, h: f32) -> &mut Self {
736 self.p.height = h;
737 self
738 }
739 pub fn fill(&mut self, hex: &str) -> &mut Self {
741 self.p.fill = Color::hex(hex).or(self.p.fill);
742 self
743 }
744 pub fn track(&mut self, hex: &str) -> &mut Self {
746 self.p.track = Color::hex(hex).or(self.p.track);
747 self
748 }
749 pub fn radius(&mut self, r: f32) -> &mut Self {
751 self.p.radius = Some(r);
752 self
753 }
754 pub fn width_px(&mut self, px: f32) -> &mut Self {
756 self.p.width = Some(Length::Px(px));
757 self
758 }
759 pub fn width_percent(&mut self, pct: f32) -> &mut Self {
761 self.p.width = Some(Length::Percent(pct));
762 self
763 }
764 pub fn align(&mut self, a: Align) -> &mut Self {
766 self.p.align = a;
767 self
768 }
769}
770
771pub struct ListBuilder {
773 kind: ListKind,
774 start: u32,
775 items: Vec<ListItem>,
776}
777
778impl ListBuilder {
779 pub fn start(&mut self, n: u32) -> &mut Self {
781 self.start = n;
782 self
783 }
784 pub fn item<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
786 let mut inner = Doc::new();
787 let _ = f(&mut inner);
788 self.items.push(ListItem { blocks: inner.blocks, check: None });
789 self
790 }
791 pub fn task<R>(&mut self, done: bool, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
793 let mut inner = Doc::new();
794 let _ = f(&mut inner);
795 self.items.push(ListItem { blocks: inner.blocks, check: Some(done) });
796 self
797 }
798}
799
800pub struct ImageBuilder {
802 width: Option<Length>,
803 align: Align,
804 caption: Option<Vec<Inline>>,
805 decor: ImageDecor,
806}
807
808impl ImageBuilder {
809 pub fn width_px(&mut self, px: f32) -> &mut Self {
811 self.width = Some(Length::Px(px));
812 self
813 }
814 pub fn width_percent(&mut self, pct: f32) -> &mut Self {
816 self.width = Some(Length::Percent(pct));
817 self
818 }
819 pub fn align(&mut self, a: Align) -> &mut Self {
821 self.align = a;
822 self
823 }
824 pub fn caption(&mut self, s: impl Into<String>) -> &mut Self {
826 self.caption = Some(vec![Inline::Text { text: s.into(), style: TextStyle::default() }]);
827 self
828 }
829 pub fn caption_with<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
831 let mut pb = ParaBuilder::new();
832 let _ = f(&mut pb);
833 self.caption = Some(pb.inlines);
834 self
835 }
836
837 pub fn badge<R>(&mut self, text: impl Into<String>, f: impl FnOnce(&mut BadgeBuilder) -> R) -> &mut Self {
841 let mut bb = BadgeBuilder { badge: Badge::new(text) };
842 let _ = f(&mut bb);
843 self.decor.badge = Some(bb.badge);
844 self
845 }
846 pub fn border(&mut self, width: f32, hex: &str) -> &mut Self {
848 if width > 0.0 && width.is_finite() {
849 if let Some(color) = Color::hex(hex) {
850 self.decor.border = Some(ImageBorder { width, color });
851 }
852 }
853 self
854 }
855 pub fn watermark<R>(&mut self, text: impl Into<String>, f: impl FnOnce(&mut WatermarkBuilder) -> R) -> &mut Self {
857 let mut wb = WatermarkBuilder { wm: Watermark::new(text) };
858 let _ = f(&mut wb);
859 self.decor.watermark = Some(wb.wm);
860 self
861 }
862 pub fn rounded(&mut self, radius: f32) -> &mut Self {
864 if radius.is_finite() && radius > 0.0 {
865 self.decor.radius = radius;
866 }
867 self
868 }
869 pub fn shadow(&mut self) -> &mut Self {
871 self.decor.shadow = Some(Shadow::default());
872 self
873 }
874 pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
876 if let Some(color) = Color::hex(hex) {
877 self.decor.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
878 }
879 self
880 }
881}
882
883pub struct BadgeBuilder {
885 badge: Badge,
886}
887
888impl BadgeBuilder {
889 pub fn anchor(&mut self, a: Anchor) -> &mut Self {
891 self.badge.anchor = a;
892 self
893 }
894 pub fn bg(&mut self, hex: &str) -> &mut Self {
896 if let Some(c) = Color::hex(hex) {
897 self.badge.bg = c;
898 }
899 self
900 }
901 pub fn fg(&mut self, hex: &str) -> &mut Self {
903 if let Some(c) = Color::hex(hex) {
904 self.badge.fg = c;
905 }
906 self
907 }
908 pub fn size(&mut self, mult: f32) -> &mut Self {
910 if mult.is_finite() && mult > 0.0 {
911 self.badge.size = mult;
912 }
913 self
914 }
915}
916
917pub struct WatermarkBuilder {
919 wm: Watermark,
920}
921
922impl WatermarkBuilder {
923 pub fn anchor(&mut self, a: Anchor) -> &mut Self {
925 self.wm.anchor = a;
926 self
927 }
928 pub fn color(&mut self, hex: &str) -> &mut Self {
930 if let Some(c) = Color::hex(hex) {
931 self.wm.color = c;
932 }
933 self
934 }
935 pub fn size(&mut self, mult: f32) -> &mut Self {
937 if mult.is_finite() && mult > 0.0 {
938 self.wm.size = mult;
939 }
940 self
941 }
942}