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, Shadow,
21 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 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 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
169pub 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 pub(crate) fn into_inlines(self) -> Vec<Inline> {
182 self.inlines
183 }
184
185 pub fn align(&mut self, a: Align) -> &mut Self {
187 self.align = a;
188 self
189 }
190
191 pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
193 self.push(s, TextStyle::default())
194 }
195
196 pub fn bold(&mut self, s: impl Into<String>) -> &mut Self {
198 self.push(s, TextStyle { weight: Some(700), ..Default::default() })
199 }
200
201 pub fn light(&mut self, s: impl Into<String>) -> &mut Self {
203 self.push(s, TextStyle { weight: Some(300), ..Default::default() })
204 }
205
206 pub fn italic(&mut self, s: impl Into<String>) -> &mut Self {
208 self.push(s, TextStyle { italic: true, ..Default::default() })
209 }
210
211 pub fn underline(&mut self, s: impl Into<String>) -> &mut Self {
213 self.push(s, TextStyle { underline: true, ..Default::default() })
214 }
215
216 pub fn strike(&mut self, s: impl Into<String>) -> &mut Self {
218 self.push(s, TextStyle { strike: true, ..Default::default() })
219 }
220
221 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 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 pub fn code(&mut self, s: impl Into<String>) -> &mut Self {
233 self.inlines.push(Inline::Code(s.into()));
234 self
235 }
236
237 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 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
260pub struct StyleBuilder {
262 style: TextStyle,
263}
264
265impl StyleBuilder {
266 pub fn bold(&mut self) -> &mut Self {
268 self.style.weight = Some(700);
269 self
270 }
271 pub fn light(&mut self) -> &mut Self {
273 self.style.weight = Some(300);
274 self
275 }
276 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 pub fn italic(&mut self) -> &mut Self {
285 self.style.italic = true;
286 self
287 }
288 pub fn underline(&mut self) -> &mut Self {
290 self.style.underline = true;
291 self
292 }
293 pub fn strike(&mut self) -> &mut Self {
295 self.style.strike = true;
296 self
297 }
298 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 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 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 pub fn font(&mut self, role: FontRole) -> &mut Self {
322 self.style.font = role;
323 self
324 }
325 pub fn shadow(&mut self) -> &mut Self {
327 self.style.shadow = Some(Shadow::default());
328 self
329 }
330 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
339pub struct TableBuilder {
341 header: Option<Vec<Cell>>,
342 rows: Vec<Vec<Cell>>,
343 cols: Vec<ColSpec>,
344 style: TableStyle,
345}
346
347impl TableBuilder {
348 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 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 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 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 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 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 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 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 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 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 pub fn pad_x(&mut self, px: f32) -> &mut Self {
455 self.style.pad_x = Some(px.max(0.0));
456 self
457 }
458 pub fn pad_y(&mut self, px: f32) -> &mut Self {
460 self.style.pad_y = Some(px.max(0.0));
461 self
462 }
463 pub fn expand(&mut self) -> &mut Self {
465 self.style.expand = true;
466 self
467 }
468 pub fn grid_outer(&mut self, on: bool) -> &mut Self {
470 self.style.grid.outer = on;
471 self
472 }
473 pub fn grid_vertical(&mut self, on: bool) -> &mut Self {
475 self.style.grid.vertical = on;
476 self
477 }
478 pub fn grid_horizontal(&mut self, on: bool) -> &mut Self {
480 self.style.grid.horizontal = on;
481 self
482 }
483 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 pub fn header_fill(&mut self, on: bool) -> &mut Self {
492 self.style.header_fill = on;
493 self
494 }
495}
496
497fn text_cell(s: impl Into<String>) -> Cell {
499 Cell { inlines: vec![Inline::Text { text: s.into(), style: TextStyle::default() }], bg: None }
500}
501
502fn 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
513pub struct ColumnsBuilder {
515 gap: Option<f32>,
516 cols: Vec<Column>,
517}
518
519impl ColumnsBuilder {
520 pub fn gap(&mut self, g: f32) -> &mut Self {
522 self.gap = Some(g);
523 self
524 }
525 pub fn col<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
527 self.col_weighted(1.0, f)
528 }
529 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
538pub struct ListBuilder {
540 kind: ListKind,
541 start: u32,
542 items: Vec<ListItem>,
543}
544
545impl ListBuilder {
546 pub fn start(&mut self, n: u32) -> &mut Self {
548 self.start = n;
549 self
550 }
551 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 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
567pub struct ImageBuilder {
569 width: Option<Length>,
570 align: Align,
571 caption: Option<Vec<Inline>>,
572 decor: ImageDecor,
573}
574
575impl ImageBuilder {
576 pub fn width_px(&mut self, px: f32) -> &mut Self {
578 self.width = Some(Length::Px(px));
579 self
580 }
581 pub fn width_percent(&mut self, pct: f32) -> &mut Self {
583 self.width = Some(Length::Percent(pct));
584 self
585 }
586 pub fn align(&mut self, a: Align) -> &mut Self {
588 self.align = a;
589 self
590 }
591 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 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 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 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 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 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 pub fn shadow(&mut self) -> &mut Self {
646 self.decor.shadow = Some(Shadow::default());
647 self
648 }
649 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
658pub struct BadgeBuilder {
660 badge: Badge,
661}
662
663impl BadgeBuilder {
664 pub fn anchor(&mut self, a: Anchor) -> &mut Self {
666 self.badge.anchor = a;
667 self
668 }
669 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 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 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
692pub struct WatermarkBuilder {
694 wm: Watermark,
695}
696
697impl WatermarkBuilder {
698 pub fn anchor(&mut self, a: Anchor) -> &mut Self {
700 self.wm.anchor = a;
701 self
702 }
703 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 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