1use 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#[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 {
46 level: level.clamp(1, 6),
47 inlines: pb.inlines,
48 align: pb.align,
49 });
50 self
51 }
52
53 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 pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
63 self.paragraph(|p| {
64 p.text(s);
65 })
66 }
67
68 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 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 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 pub fn divider(&mut self) -> &mut Self {
96 self.blocks.push(Block::Divider);
97 self
98 }
99
100 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 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 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 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 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 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
199pub 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 pub(crate) fn into_inlines(self) -> Vec<Inline> {
212 self.inlines
213 }
214
215 pub fn align(&mut self, a: Align) -> &mut Self {
217 self.align = a;
218 self
219 }
220
221 pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
223 self.push(s, TextStyle::default())
224 }
225
226 pub fn bold(&mut self, s: impl Into<String>) -> &mut Self {
228 self.push(s, TextStyle { weight: Some(700), ..Default::default() })
229 }
230
231 pub fn light(&mut self, s: impl Into<String>) -> &mut Self {
233 self.push(s, TextStyle { weight: Some(300), ..Default::default() })
234 }
235
236 pub fn italic(&mut self, s: impl Into<String>) -> &mut Self {
238 self.push(s, TextStyle { italic: true, ..Default::default() })
239 }
240
241 pub fn underline(&mut self, s: impl Into<String>) -> &mut Self {
243 self.push(s, TextStyle { underline: true, ..Default::default() })
244 }
245
246 pub fn strike(&mut self, s: impl Into<String>) -> &mut Self {
248 self.push(s, TextStyle { strike: true, ..Default::default() })
249 }
250
251 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 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 pub fn code(&mut self, s: impl Into<String>) -> &mut Self {
263 self.inlines.push(Inline::Code(s.into()));
264 self
265 }
266
267 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 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
290pub struct StyleBuilder {
292 style: TextStyle,
293}
294
295impl StyleBuilder {
296 pub fn bold(&mut self) -> &mut Self {
298 self.style.weight = Some(700);
299 self
300 }
301 pub fn light(&mut self) -> &mut Self {
303 self.style.weight = Some(300);
304 self
305 }
306 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 pub fn italic(&mut self) -> &mut Self {
315 self.style.italic = true;
316 self
317 }
318 pub fn underline(&mut self) -> &mut Self {
320 self.style.underline = true;
321 self
322 }
323 pub fn strike(&mut self) -> &mut Self {
325 self.style.strike = true;
326 self
327 }
328 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 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 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 pub fn font(&mut self, role: FontRole) -> &mut Self {
352 self.style.font = role;
353 self
354 }
355 pub fn link(&mut self) -> &mut Self {
357 self.style.link = true;
358 self
359 }
360 pub fn ring(&mut self) -> &mut Self {
364 self.style.ring.get_or_insert_default();
365 self
366 }
367 pub fn ring_color(&mut self, hex: &str) -> &mut Self {
369 let r = self.style.ring.get_or_insert_default();
370 r.color = Color::hex(hex).or(r.color);
371 self
372 }
373 pub fn ring_radius(&mut self, r: f32) -> &mut Self {
375 self.ring_radii(r, r)
376 }
377 pub fn ring_radii(&mut self, rx: f32, ry: f32) -> &mut Self {
379 let r = self.style.ring.get_or_insert_default();
380 if rx.is_finite() && rx > 0.0 {
381 r.rx = Some(rx);
382 }
383 if ry.is_finite() && ry > 0.0 {
384 r.ry = Some(ry);
385 }
386 self
387 }
388 pub fn ring_stroke(&mut self, w: f32) -> &mut Self {
390 let r = self.style.ring.get_or_insert_default();
391 if w.is_finite() && w > 0.0 {
392 r.width = Some(w);
393 }
394 self
395 }
396 pub fn ring_each(&mut self) -> &mut Self {
399 self.style.ring.get_or_insert_default().each = true;
400 self
401 }
402 pub fn dot(&mut self) -> &mut Self {
404 self.style.dot.get_or_insert_default();
405 self
406 }
407 pub fn dot_color(&mut self, hex: &str) -> &mut Self {
409 let d = self.style.dot.get_or_insert_default();
410 d.color = Color::hex(hex).or(d.color);
411 self
412 }
413 pub fn dot_radius(&mut self, r: f32) -> &mut Self {
415 let d = self.style.dot.get_or_insert_default();
416 if r.is_finite() && r > 0.0 {
417 d.radius = Some(r);
418 }
419 self
420 }
421 pub fn dot_each(&mut self) -> &mut Self {
423 self.style.dot.get_or_insert_default().each = true;
424 self
425 }
426 pub fn aside_right(&mut self) -> &mut Self {
429 self.style.aside = Some(crate::model::AsideSide::Right);
430 self
431 }
432 pub fn aside_left(&mut self) -> &mut Self {
434 self.style.aside = Some(crate::model::AsideSide::Left);
435 self
436 }
437 pub fn shadow(&mut self) -> &mut Self {
439 self.style.shadow = Some(Shadow::default());
440 self
441 }
442 pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
444 if let Some(color) = Color::hex(hex) {
445 self.style.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
446 }
447 self
448 }
449}
450
451pub struct TableBuilder {
453 header: Option<Vec<Cell>>,
454 rows: Vec<Vec<Cell>>,
455 cols: Vec<ColSpec>,
456 style: TableStyle,
457}
458
459impl TableBuilder {
460 pub fn head<I, S>(&mut self, cells: I) -> &mut Self
462 where
463 I: IntoIterator<Item = S>,
464 S: Into<String>,
465 {
466 self.header = Some(cells.into_iter().map(text_cell).collect());
467 self
468 }
469 pub fn row<I, S>(&mut self, cells: I) -> &mut Self
471 where
472 I: IntoIterator<Item = S>,
473 S: Into<String>,
474 {
475 self.rows.push(cells.into_iter().map(text_cell).collect());
476 self
477 }
478 pub fn head_rich<R>(&mut self, f: impl FnOnce(&mut RowBuilder) -> R) -> &mut Self {
480 let mut rb = RowBuilder { cells: Vec::new() };
481 let _ = f(&mut rb);
482 self.header = Some(rb.cells);
483 self
484 }
485 pub fn row_rich<R>(&mut self, f: impl FnOnce(&mut RowBuilder) -> R) -> &mut Self {
487 let mut rb = RowBuilder { cells: Vec::new() };
488 let _ = f(&mut rb);
489 self.rows.push(rb.cells);
490 self
491 }
492 pub fn align<I: IntoIterator<Item = Align>>(&mut self, aligns: I) -> &mut Self {
494 for (k, a) in aligns.into_iter().enumerate() {
495 self.ensure_col(k).align = a;
496 }
497 self
498 }
499 pub fn width(&mut self, col: usize, w: Length) -> &mut Self {
501 self.ensure_col(col).width = Some(w);
502 self
503 }
504 fn ensure_col(&mut self, k: usize) -> &mut ColSpec {
505 while self.cols.len() <= k {
506 self.cols.push(ColSpec::default());
507 }
508 &mut self.cols[k]
509 }
510
511 pub fn col_style<R>(&mut self, col: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
515 if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
516 style_cell(h, &f);
517 }
518 for row in &mut self.rows {
519 if let Some(c) = row.get_mut(col) {
520 style_cell(c, &f);
521 }
522 }
523 self
524 }
525 pub fn row_style<R>(&mut self, row: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
527 if let Some(r) = self.rows.get_mut(row) {
528 for c in r.iter_mut() {
529 style_cell(c, &f);
530 }
531 }
532 self
533 }
534 pub fn cell_style<R>(
536 &mut self,
537 row: usize,
538 col: usize,
539 f: impl Fn(&mut StyleBuilder) -> R,
540 ) -> &mut Self {
541 if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
542 style_cell(c, &f);
543 }
544 self
545 }
546 pub fn col_fill(&mut self, col: usize, hex: &str) -> &mut Self {
548 let Some(bg) = Color::hex(hex) else { return self };
549 if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
550 h.bg = Some(bg);
551 }
552 for row in &mut self.rows {
553 if let Some(c) = row.get_mut(col) {
554 c.bg = Some(bg);
555 }
556 }
557 self
558 }
559 pub fn row_fill(&mut self, row: usize, hex: &str) -> &mut Self {
561 let Some(bg) = Color::hex(hex) else { return self };
562 if let Some(r) = self.rows.get_mut(row) {
563 for c in r.iter_mut() {
564 c.bg = Some(bg);
565 }
566 }
567 self
568 }
569 pub fn cell_fill(&mut self, row: usize, col: usize, hex: &str) -> &mut Self {
571 let Some(bg) = Color::hex(hex) else { return self };
572 if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
573 c.bg = Some(bg);
574 }
575 self
576 }
577
578 pub fn pad_x(&mut self, px: f32) -> &mut Self {
582 self.style.pad_x = Some(px.max(0.0));
583 self
584 }
585 pub fn pad_y(&mut self, px: f32) -> &mut Self {
587 self.style.pad_y = Some(px.max(0.0));
588 self
589 }
590 pub fn expand(&mut self) -> &mut Self {
592 self.style.expand = true;
593 self
594 }
595 pub fn table_align(&mut self, a: Align) -> &mut Self {
597 self.style.align = a;
598 self
599 }
600 pub fn grid_outer(&mut self, on: bool) -> &mut Self {
602 self.style.grid.outer = on;
603 self
604 }
605 pub fn grid_vertical(&mut self, on: bool) -> &mut Self {
607 self.style.grid.vertical = on;
608 self
609 }
610 pub fn grid_horizontal(&mut self, on: bool) -> &mut Self {
612 self.style.grid.horizontal = on;
613 self
614 }
615 pub fn no_grid(&mut self) -> &mut Self {
617 self.style.grid.outer = false;
618 self.style.grid.vertical = false;
619 self.style.grid.horizontal = false;
620 self
621 }
622 pub fn header_fill(&mut self, on: bool) -> &mut Self {
624 self.style.header_fill = on;
625 self
626 }
627}
628
629pub struct RowBuilder {
631 cells: Vec<Cell>,
632}
633
634impl RowBuilder {
635 pub fn cell<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
637 let mut pb = ParaBuilder::new();
638 let _ = f(&mut pb);
639 self.cells.push(Cell { inlines: pb.into_inlines(), bg: None });
640 self
641 }
642 pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
644 self.cells.push(text_cell(s));
645 self
646 }
647}
648
649fn text_cell(s: impl Into<String>) -> Cell {
651 Cell { inlines: vec![Inline::Text { text: s.into(), style: TextStyle::default() }], bg: None }
652}
653
654fn style_cell<R>(cell: &mut Cell, f: &impl Fn(&mut StyleBuilder) -> R) {
656 for inl in &mut cell.inlines {
657 if let Inline::Text { style, .. } = inl {
658 let mut sb = StyleBuilder { style: style.clone() };
659 let _ = f(&mut sb);
660 *style = sb.style;
661 }
662 }
663}
664
665pub struct ColumnsBuilder {
667 gap: Option<f32>,
668 cols: Vec<Column>,
669}
670
671impl ColumnsBuilder {
672 pub fn gap(&mut self, g: f32) -> &mut Self {
674 if g.is_finite() && g >= 0.0 {
675 self.gap = Some(g);
676 }
677 self
678 }
679 pub fn col<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
681 self.col_weighted(1.0, f)
682 }
683 pub fn col_weighted<R>(&mut self, weight: f32, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
685 let weight = if weight.is_finite() && weight > 0.0 { weight } else { 1.0 };
686 let mut inner = Doc::new();
687 let _ = f(&mut inner);
688 self.cols.push(Column { blocks: inner.blocks, weight });
689 self
690 }
691 pub fn panel<R>(&mut self, f: impl FnOnce(&mut PanelBuilder) -> R) -> &mut Self {
693 self.panel_weighted(1.0, f)
694 }
695 pub fn panel_weighted<R>(
697 &mut self,
698 weight: f32,
699 f: impl FnOnce(&mut PanelBuilder) -> R,
700 ) -> &mut Self {
701 let weight = if weight.is_finite() && weight > 0.0 { weight } else { 1.0 };
702 let mut pb = PanelBuilder::new();
703 let _ = f(&mut pb);
704 self.cols.push(Column { blocks: vec![Block::Panel(pb.into_panel())], weight });
705 self
706 }
707}
708
709pub struct PanelBuilder {
713 doc: Doc,
714 decor: PanelDecor,
715}
716
717impl PanelBuilder {
718 fn new() -> Self {
719 Self { doc: Doc::new(), decor: PanelDecor::default() }
720 }
721
722 fn into_panel(self) -> Panel {
723 Panel { blocks: self.doc.blocks, decor: self.decor }
724 }
725
726 pub fn bg(&mut self, hex: &str) -> &mut Self {
728 if let Some(c) = Color::hex(hex) {
729 self.decor.bg = Some(c);
730 }
731 self
732 }
733 pub fn border(&mut self, width: f32, hex: &str) -> &mut Self {
735 if width > 0.0 && width.is_finite() {
736 if let Some(color) = Color::hex(hex) {
737 self.decor.border = Some(ImageBorder { width, color });
738 }
739 }
740 self
741 }
742 pub fn rounded(&mut self, radius: f32) -> &mut Self {
744 if radius.is_finite() && radius >= 0.0 {
745 self.decor.radius = Some(radius);
746 }
747 self
748 }
749 pub fn pad(&mut self, px: f32) -> &mut Self {
751 if px.is_finite() && px >= 0.0 {
752 self.decor.pad = Some(px);
753 }
754 self
755 }
756 pub fn shadow(&mut self) -> &mut Self {
758 self.decor.shadow = Some(Shadow::default());
759 self
760 }
761 pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
763 if let Some(color) = Color::hex(hex) {
764 self.decor.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
765 }
766 self
767 }
768}
769
770impl std::ops::Deref for PanelBuilder {
771 type Target = Doc;
772 fn deref(&self) -> &Doc {
773 &self.doc
774 }
775}
776
777impl std::ops::DerefMut for PanelBuilder {
778 fn deref_mut(&mut self) -> &mut Doc {
779 &mut self.doc
780 }
781}
782
783pub struct ProgressBuilder {
785 p: Progress,
786}
787
788impl ProgressBuilder {
789 pub fn height(&mut self, h: f32) -> &mut Self {
791 self.p.height = h;
792 self
793 }
794 pub fn fill(&mut self, hex: &str) -> &mut Self {
796 self.p.fill = Color::hex(hex).or(self.p.fill);
797 self
798 }
799 pub fn track(&mut self, hex: &str) -> &mut Self {
801 self.p.track = Color::hex(hex).or(self.p.track);
802 self
803 }
804 pub fn radius(&mut self, r: f32) -> &mut Self {
806 self.p.radius = Some(r);
807 self
808 }
809 pub fn width_px(&mut self, px: f32) -> &mut Self {
811 self.p.width = Some(Length::Px(px));
812 self
813 }
814 pub fn width_percent(&mut self, pct: f32) -> &mut Self {
816 self.p.width = Some(Length::Percent(pct));
817 self
818 }
819 pub fn align(&mut self, a: Align) -> &mut Self {
821 self.p.align = a;
822 self
823 }
824}
825
826pub struct ListBuilder {
828 kind: ListKind,
829 start: u32,
830 items: Vec<ListItem>,
831}
832
833impl ListBuilder {
834 pub fn start(&mut self, n: u32) -> &mut Self {
836 self.start = n;
837 self
838 }
839 pub fn item<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
841 let mut inner = Doc::new();
842 let _ = f(&mut inner);
843 self.items.push(ListItem { blocks: inner.blocks, check: None });
844 self
845 }
846 pub fn task<R>(&mut self, done: bool, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
848 let mut inner = Doc::new();
849 let _ = f(&mut inner);
850 self.items.push(ListItem { blocks: inner.blocks, check: Some(done) });
851 self
852 }
853}
854
855pub struct ImageBuilder {
857 width: Option<Length>,
858 align: Align,
859 caption: Option<Vec<Inline>>,
860 decor: ImageDecor,
861}
862
863impl ImageBuilder {
864 pub fn width_px(&mut self, px: f32) -> &mut Self {
866 self.width = Some(Length::Px(px));
867 self
868 }
869 pub fn width_percent(&mut self, pct: f32) -> &mut Self {
871 self.width = Some(Length::Percent(pct));
872 self
873 }
874 pub fn align(&mut self, a: Align) -> &mut Self {
876 self.align = a;
877 self
878 }
879 pub fn caption(&mut self, s: impl Into<String>) -> &mut Self {
881 self.caption = Some(vec![Inline::Text { text: s.into(), style: TextStyle::default() }]);
882 self
883 }
884 pub fn caption_with<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
886 let mut pb = ParaBuilder::new();
887 let _ = f(&mut pb);
888 self.caption = Some(pb.inlines);
889 self
890 }
891
892 pub fn badge<R>(
896 &mut self,
897 text: impl Into<String>,
898 f: impl FnOnce(&mut BadgeBuilder) -> R,
899 ) -> &mut Self {
900 let mut bb = BadgeBuilder { badge: Badge::new(text) };
901 let _ = f(&mut bb);
902 self.decor.badge = Some(bb.badge);
903 self
904 }
905 pub fn border(&mut self, width: f32, hex: &str) -> &mut Self {
907 if width > 0.0 && width.is_finite() {
908 if let Some(color) = Color::hex(hex) {
909 self.decor.border = Some(ImageBorder { width, color });
910 }
911 }
912 self
913 }
914 pub fn watermark<R>(
916 &mut self,
917 text: impl Into<String>,
918 f: impl FnOnce(&mut WatermarkBuilder) -> R,
919 ) -> &mut Self {
920 let mut wb = WatermarkBuilder { wm: Watermark::new(text) };
921 let _ = f(&mut wb);
922 self.decor.watermark = Some(wb.wm);
923 self
924 }
925 pub fn rounded(&mut self, radius: f32) -> &mut Self {
927 if radius.is_finite() && radius > 0.0 {
928 self.decor.radius = radius;
929 }
930 self
931 }
932 pub fn shadow(&mut self) -> &mut Self {
934 self.decor.shadow = Some(Shadow::default());
935 self
936 }
937 pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
939 if let Some(color) = Color::hex(hex) {
940 self.decor.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
941 }
942 self
943 }
944}
945
946pub struct BadgeBuilder {
948 badge: Badge,
949}
950
951impl BadgeBuilder {
952 pub fn anchor(&mut self, a: Anchor) -> &mut Self {
954 self.badge.anchor = a;
955 self
956 }
957 pub fn bg(&mut self, hex: &str) -> &mut Self {
959 if let Some(c) = Color::hex(hex) {
960 self.badge.bg = c;
961 }
962 self
963 }
964 pub fn fg(&mut self, hex: &str) -> &mut Self {
966 if let Some(c) = Color::hex(hex) {
967 self.badge.fg = c;
968 }
969 self
970 }
971 pub fn size(&mut self, mult: f32) -> &mut Self {
973 if mult.is_finite() && mult > 0.0 {
974 self.badge.size = mult;
975 }
976 self
977 }
978}
979
980pub struct WatermarkBuilder {
982 wm: Watermark,
983}
984
985impl WatermarkBuilder {
986 pub fn anchor(&mut self, a: Anchor) -> &mut Self {
988 self.wm.anchor = a;
989 self
990 }
991 pub fn color(&mut self, hex: &str) -> &mut Self {
993 if let Some(c) = Color::hex(hex) {
994 self.wm.color = c;
995 }
996 self
997 }
998 pub fn size(&mut self, mult: f32) -> &mut Self {
1000 if mult.is_finite() && mult > 0.0 {
1001 self.wm.size = mult;
1002 }
1003 self
1004 }
1005}
1006