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 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 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 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 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 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 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
189pub 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 pub(crate) fn into_inlines(self) -> Vec<Inline> {
202 self.inlines
203 }
204
205 pub fn align(&mut self, a: Align) -> &mut Self {
207 self.align = a;
208 self
209 }
210
211 pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
213 self.push(s, TextStyle::default())
214 }
215
216 pub fn bold(&mut self, s: impl Into<String>) -> &mut Self {
218 self.push(s, TextStyle { weight: Some(700), ..Default::default() })
219 }
220
221 pub fn light(&mut self, s: impl Into<String>) -> &mut Self {
223 self.push(s, TextStyle { weight: Some(300), ..Default::default() })
224 }
225
226 pub fn italic(&mut self, s: impl Into<String>) -> &mut Self {
228 self.push(s, TextStyle { italic: true, ..Default::default() })
229 }
230
231 pub fn underline(&mut self, s: impl Into<String>) -> &mut Self {
233 self.push(s, TextStyle { underline: true, ..Default::default() })
234 }
235
236 pub fn strike(&mut self, s: impl Into<String>) -> &mut Self {
238 self.push(s, TextStyle { strike: true, ..Default::default() })
239 }
240
241 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 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 pub fn code(&mut self, s: impl Into<String>) -> &mut Self {
253 self.inlines.push(Inline::Code(s.into()));
254 self
255 }
256
257 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 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
280pub struct StyleBuilder {
282 style: TextStyle,
283}
284
285impl StyleBuilder {
286 pub fn bold(&mut self) -> &mut Self {
288 self.style.weight = Some(700);
289 self
290 }
291 pub fn light(&mut self) -> &mut Self {
293 self.style.weight = Some(300);
294 self
295 }
296 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 pub fn italic(&mut self) -> &mut Self {
305 self.style.italic = true;
306 self
307 }
308 pub fn underline(&mut self) -> &mut Self {
310 self.style.underline = true;
311 self
312 }
313 pub fn strike(&mut self) -> &mut Self {
315 self.style.strike = true;
316 self
317 }
318 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 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 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 pub fn font(&mut self, role: FontRole) -> &mut Self {
342 self.style.font = role;
343 self
344 }
345 pub fn ring(&mut self) -> &mut Self {
349 self.style.ring.get_or_insert_default();
350 self
351 }
352 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 pub fn ring_radius(&mut self, r: f32) -> &mut Self {
360 self.ring_radii(r, r)
361 }
362 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 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 pub fn ring_each(&mut self) -> &mut Self {
384 self.style.ring.get_or_insert_default().each = true;
385 self
386 }
387 pub fn dot(&mut self) -> &mut Self {
389 self.style.dot.get_or_insert_default();
390 self
391 }
392 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 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 pub fn dot_each(&mut self) -> &mut Self {
408 self.style.dot.get_or_insert_default().each = true;
409 self
410 }
411 pub fn aside_right(&mut self) -> &mut Self {
415 self.style.aside = Some(crate::model::AsideSide::Right);
416 self
417 }
418 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 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
436pub struct TableBuilder {
438 header: Option<Vec<Cell>>,
439 rows: Vec<Vec<Cell>>,
440 cols: Vec<ColSpec>,
441 style: TableStyle,
442}
443
444impl TableBuilder {
445 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 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 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 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 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 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 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 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 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 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 pub fn pad_x(&mut self, px: f32) -> &mut Self {
552 self.style.pad_x = Some(px.max(0.0));
553 self
554 }
555 pub fn pad_y(&mut self, px: f32) -> &mut Self {
557 self.style.pad_y = Some(px.max(0.0));
558 self
559 }
560 pub fn expand(&mut self) -> &mut Self {
562 self.style.expand = true;
563 self
564 }
565 pub fn grid_outer(&mut self, on: bool) -> &mut Self {
567 self.style.grid.outer = on;
568 self
569 }
570 pub fn grid_vertical(&mut self, on: bool) -> &mut Self {
572 self.style.grid.vertical = on;
573 self
574 }
575 pub fn grid_horizontal(&mut self, on: bool) -> &mut Self {
577 self.style.grid.horizontal = on;
578 self
579 }
580 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 pub fn header_fill(&mut self, on: bool) -> &mut Self {
589 self.style.header_fill = on;
590 self
591 }
592}
593
594fn text_cell(s: impl Into<String>) -> Cell {
596 Cell { inlines: vec![Inline::Text { text: s.into(), style: TextStyle::default() }], bg: None }
597}
598
599fn 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
610pub struct ColumnsBuilder {
612 gap: Option<f32>,
613 cols: Vec<Column>,
614}
615
616impl ColumnsBuilder {
617 pub fn gap(&mut self, g: f32) -> &mut Self {
619 self.gap = Some(g);
620 self
621 }
622 pub fn col<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
624 self.col_weighted(1.0, f)
625 }
626 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
635pub struct ProgressBuilder {
637 p: Progress,
638}
639
640impl ProgressBuilder {
641 pub fn height(&mut self, h: f32) -> &mut Self {
643 self.p.height = h;
644 self
645 }
646 pub fn fill(&mut self, hex: &str) -> &mut Self {
648 self.p.fill = Color::hex(hex).or(self.p.fill);
649 self
650 }
651 pub fn track(&mut self, hex: &str) -> &mut Self {
653 self.p.track = Color::hex(hex).or(self.p.track);
654 self
655 }
656 pub fn radius(&mut self, r: f32) -> &mut Self {
658 self.p.radius = Some(r);
659 self
660 }
661 pub fn width_px(&mut self, px: f32) -> &mut Self {
663 self.p.width = Some(Length::Px(px));
664 self
665 }
666 pub fn width_percent(&mut self, pct: f32) -> &mut Self {
668 self.p.width = Some(Length::Percent(pct));
669 self
670 }
671 pub fn align(&mut self, a: Align) -> &mut Self {
673 self.p.align = a;
674 self
675 }
676}
677
678pub struct ListBuilder {
680 kind: ListKind,
681 start: u32,
682 items: Vec<ListItem>,
683}
684
685impl ListBuilder {
686 pub fn start(&mut self, n: u32) -> &mut Self {
688 self.start = n;
689 self
690 }
691 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 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
707pub struct ImageBuilder {
709 width: Option<Length>,
710 align: Align,
711 caption: Option<Vec<Inline>>,
712 decor: ImageDecor,
713}
714
715impl ImageBuilder {
716 pub fn width_px(&mut self, px: f32) -> &mut Self {
718 self.width = Some(Length::Px(px));
719 self
720 }
721 pub fn width_percent(&mut self, pct: f32) -> &mut Self {
723 self.width = Some(Length::Percent(pct));
724 self
725 }
726 pub fn align(&mut self, a: Align) -> &mut Self {
728 self.align = a;
729 self
730 }
731 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 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 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 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 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 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 pub fn shadow(&mut self) -> &mut Self {
786 self.decor.shadow = Some(Shadow::default());
787 self
788 }
789 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
798pub struct BadgeBuilder {
800 badge: Badge,
801}
802
803impl BadgeBuilder {
804 pub fn anchor(&mut self, a: Anchor) -> &mut Self {
806 self.badge.anchor = a;
807 self
808 }
809 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 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 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
832pub struct WatermarkBuilder {
834 wm: Watermark,
835}
836
837impl WatermarkBuilder {
838 pub fn anchor(&mut self, a: Anchor) -> &mut Self {
840 self.wm.anchor = a;
841 self
842 }
843 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 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