1use std::path::PathBuf;
17
18use crate::model::{
19 Align, Block, BlockImage, Cell, ColSpec, Color, Column, Columns, Document, FontRole, Highlight,
20 Inline, Length, List, ListItem, ListKind, Table, TableStyle, TextStyle,
21};
22
23#[derive(Default)]
25pub struct Doc {
26 blocks: Vec<Block>,
27}
28
29impl Doc {
30 pub fn new() -> Self {
32 Self { blocks: Vec::new() }
33 }
34
35 pub fn build(&self) -> Document {
37 Document { blocks: self.blocks.clone() }
38 }
39
40 pub fn heading<R>(&mut self, level: u8, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
42 let mut pb = ParaBuilder::new();
43 let _ = f(&mut pb);
44 self.blocks.push(Block::Heading {
45 level: level.clamp(1, 6),
46 inlines: pb.inlines,
47 align: pb.align,
48 });
49 self
50 }
51
52 pub fn paragraph<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
54 let mut pb = ParaBuilder::new();
55 let _ = f(&mut pb);
56 self.blocks.push(Block::Paragraph { inlines: pb.inlines, align: pb.align });
57 self
58 }
59
60 pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
62 self.paragraph(|p| {
63 p.text(s);
64 })
65 }
66
67 pub fn quote<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
69 let mut inner = Doc::new();
70 let _ = f(&mut inner);
71 self.blocks.push(Block::Quote(inner.blocks));
72 self
73 }
74
75 pub fn list<R>(&mut self, kind: ListKind, f: impl FnOnce(&mut ListBuilder) -> R) -> &mut Self {
77 let mut lb = ListBuilder { kind, start: 1, items: Vec::new() };
78 let _ = f(&mut lb);
79 self.blocks.push(Block::List(List { kind: lb.kind, start: lb.start, items: lb.items }));
80 self
81 }
82
83 pub fn code(&mut self, lang: impl Into<String>, text: impl Into<String>) -> &mut Self {
85 let lang = lang.into();
86 self.blocks.push(Block::Code {
87 lang: if lang.is_empty() { None } else { Some(lang) },
88 text: text.into(),
89 });
90 self
91 }
92
93 pub fn divider(&mut self) -> &mut Self {
95 self.blocks.push(Block::Divider);
96 self
97 }
98
99 pub fn columns<R>(&mut self, f: impl FnOnce(&mut ColumnsBuilder) -> R) -> &mut Self {
101 let mut cb = ColumnsBuilder { gap: None, cols: Vec::new() };
102 let _ = f(&mut cb);
103 self.blocks.push(Block::Columns(Columns { cols: cb.cols, gap: cb.gap }));
104 self
105 }
106
107 pub fn table<R>(&mut self, f: impl FnOnce(&mut TableBuilder) -> R) -> &mut Self {
109 let mut tb = TableBuilder {
110 header: None,
111 rows: Vec::new(),
112 cols: Vec::new(),
113 style: TableStyle::default(),
114 };
115 let _ = f(&mut tb);
116 self.blocks.push(Block::Table(Table {
117 header: tb.header,
118 rows: tb.rows,
119 cols: tb.cols,
120 style: tb.style,
121 }));
122 self
123 }
124
125 pub fn image_bytes<R>(
127 &mut self,
128 bytes: Vec<u8>,
129 f: impl FnOnce(&mut ImageBuilder) -> R,
130 ) -> &mut Self {
131 self.push_block_image(ImageSource::Bytes(bytes), f)
132 }
133
134 pub fn image_path<R>(
136 &mut self,
137 path: impl Into<PathBuf>,
138 f: impl FnOnce(&mut ImageBuilder) -> R,
139 ) -> &mut Self {
140 self.push_block_image(ImageSource::Path(path.into()), f)
141 }
142
143 fn push_block_image<R>(
144 &mut self,
145 src: ImageSource,
146 f: impl FnOnce(&mut ImageBuilder) -> R,
147 ) -> &mut Self {
148 let mut ib = ImageBuilder { width: None, align: Align::Left, caption: None };
149 let _ = f(&mut ib);
150 self.blocks.push(Block::Image(BlockImage {
151 src,
152 width: ib.width,
153 align: ib.align,
154 caption: ib.caption,
155 }));
156 self
157 }
158}
159
160use crate::model::ImageSource;
161
162pub struct ParaBuilder {
164 inlines: Vec<Inline>,
165 align: Align,
166}
167
168impl ParaBuilder {
169 fn new() -> Self {
170 Self { inlines: Vec::new(), align: Align::Left }
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>(
227 &mut self,
228 s: impl Into<String>,
229 f: impl FnOnce(&mut StyleBuilder) -> R,
230 ) -> &mut Self {
231 let mut sb = StyleBuilder { style: TextStyle::default() };
232 let _ = f(&mut sb);
233 self.push(s, sb.style)
234 }
235
236 pub fn line_break(&mut self) -> &mut Self {
238 self.inlines.push(Inline::LineBreak);
239 self
240 }
241
242 fn push(&mut self, s: impl Into<String>, style: TextStyle) -> &mut Self {
243 self.inlines.push(Inline::Text { text: s.into(), style });
244 self
245 }
246}
247
248pub struct StyleBuilder {
250 style: TextStyle,
251}
252
253impl StyleBuilder {
254 pub fn bold(&mut self) -> &mut Self {
256 self.style.weight = Some(700);
257 self
258 }
259 pub fn light(&mut self) -> &mut Self {
261 self.style.weight = Some(300);
262 self
263 }
264 pub fn weight(&mut self, w: u16) -> &mut Self {
266 if (1..=1000).contains(&w) {
267 self.style.weight = Some(w);
268 }
269 self
270 }
271 pub fn italic(&mut self) -> &mut Self {
273 self.style.italic = true;
274 self
275 }
276 pub fn underline(&mut self) -> &mut Self {
278 self.style.underline = true;
279 self
280 }
281 pub fn strike(&mut self) -> &mut Self {
283 self.style.strike = true;
284 self
285 }
286 pub fn color(&mut self, hex: &str) -> &mut Self {
288 if let Some(c) = Color::hex(hex) {
289 self.style.color = Some(c);
290 }
291 self
292 }
293 pub fn bg(&mut self, hex: &str) -> &mut Self {
295 if let Some(c) = Color::hex(hex) {
296 self.style.highlight = Some(Highlight::Custom(c));
297 }
298 self
299 }
300 pub fn size(&mut self, mult: f32) -> &mut Self {
303 if mult.is_finite() && mult > 0.0 {
304 self.style.size = mult;
305 }
306 self
307 }
308 pub fn font(&mut self, role: FontRole) -> &mut Self {
310 self.style.font = role;
311 self
312 }
313}
314
315pub struct TableBuilder {
317 header: Option<Vec<Cell>>,
318 rows: Vec<Vec<Cell>>,
319 cols: Vec<ColSpec>,
320 style: TableStyle,
321}
322
323impl TableBuilder {
324 pub fn head<I, S>(&mut self, cells: I) -> &mut Self
326 where
327 I: IntoIterator<Item = S>,
328 S: Into<String>,
329 {
330 self.header = Some(cells.into_iter().map(text_cell).collect());
331 self
332 }
333 pub fn row<I, S>(&mut self, cells: I) -> &mut Self
335 where
336 I: IntoIterator<Item = S>,
337 S: Into<String>,
338 {
339 self.rows.push(cells.into_iter().map(text_cell).collect());
340 self
341 }
342 pub fn align<I: IntoIterator<Item = Align>>(&mut self, aligns: I) -> &mut Self {
344 for (k, a) in aligns.into_iter().enumerate() {
345 self.ensure_col(k).align = a;
346 }
347 self
348 }
349 pub fn width(&mut self, col: usize, w: Length) -> &mut Self {
351 self.ensure_col(col).width = Some(w);
352 self
353 }
354 fn ensure_col(&mut self, k: usize) -> &mut ColSpec {
355 while self.cols.len() <= k {
356 self.cols.push(ColSpec::default());
357 }
358 &mut self.cols[k]
359 }
360
361 pub fn col_style<R>(&mut self, col: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
365 if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
366 style_cell(h, &f);
367 }
368 for row in &mut self.rows {
369 if let Some(c) = row.get_mut(col) {
370 style_cell(c, &f);
371 }
372 }
373 self
374 }
375 pub fn row_style<R>(&mut self, row: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
377 if let Some(r) = self.rows.get_mut(row) {
378 for c in r.iter_mut() {
379 style_cell(c, &f);
380 }
381 }
382 self
383 }
384 pub fn cell_style<R>(
386 &mut self,
387 row: usize,
388 col: usize,
389 f: impl Fn(&mut StyleBuilder) -> R,
390 ) -> &mut Self {
391 if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
392 style_cell(c, &f);
393 }
394 self
395 }
396 pub fn col_fill(&mut self, col: usize, hex: &str) -> &mut Self {
398 let bg = Color::hex(hex);
399 if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
400 h.bg = bg;
401 }
402 for row in &mut self.rows {
403 if let Some(c) = row.get_mut(col) {
404 c.bg = bg;
405 }
406 }
407 self
408 }
409 pub fn row_fill(&mut self, row: usize, hex: &str) -> &mut Self {
411 let bg = Color::hex(hex);
412 if let Some(r) = self.rows.get_mut(row) {
413 for c in r.iter_mut() {
414 c.bg = bg;
415 }
416 }
417 self
418 }
419 pub fn cell_fill(&mut self, row: usize, col: usize, hex: &str) -> &mut Self {
421 if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
422 c.bg = Color::hex(hex);
423 }
424 self
425 }
426
427 pub fn pad_x(&mut self, px: f32) -> &mut Self {
431 self.style.pad_x = Some(px.max(0.0));
432 self
433 }
434 pub fn pad_y(&mut self, px: f32) -> &mut Self {
436 self.style.pad_y = Some(px.max(0.0));
437 self
438 }
439 pub fn grid_outer(&mut self, on: bool) -> &mut Self {
441 self.style.grid.outer = on;
442 self
443 }
444 pub fn grid_vertical(&mut self, on: bool) -> &mut Self {
446 self.style.grid.vertical = on;
447 self
448 }
449 pub fn grid_horizontal(&mut self, on: bool) -> &mut Self {
451 self.style.grid.horizontal = on;
452 self
453 }
454 pub fn no_grid(&mut self) -> &mut Self {
456 self.style.grid.outer = false;
457 self.style.grid.vertical = false;
458 self.style.grid.horizontal = false;
459 self
460 }
461 pub fn header_fill(&mut self, on: bool) -> &mut Self {
463 self.style.header_fill = on;
464 self
465 }
466}
467
468fn text_cell(s: impl Into<String>) -> Cell {
470 Cell { inlines: vec![Inline::Text { text: s.into(), style: TextStyle::default() }], bg: None }
471}
472
473fn style_cell<R>(cell: &mut Cell, f: &impl Fn(&mut StyleBuilder) -> R) {
475 for inl in &mut cell.inlines {
476 if let Inline::Text { style, .. } = inl {
477 let mut sb = StyleBuilder { style: style.clone() };
478 let _ = f(&mut sb);
479 *style = sb.style;
480 }
481 }
482}
483
484pub struct ColumnsBuilder {
486 gap: Option<f32>,
487 cols: Vec<Column>,
488}
489
490impl ColumnsBuilder {
491 pub fn gap(&mut self, g: f32) -> &mut Self {
493 self.gap = Some(g);
494 self
495 }
496 pub fn col<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
498 self.col_weighted(1.0, f)
499 }
500 pub fn col_weighted<R>(&mut self, weight: f32, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
502 let mut inner = Doc::new();
503 let _ = f(&mut inner);
504 self.cols.push(Column { blocks: inner.blocks, weight });
505 self
506 }
507}
508
509pub struct ListBuilder {
511 kind: ListKind,
512 start: u32,
513 items: Vec<ListItem>,
514}
515
516impl ListBuilder {
517 pub fn start(&mut self, n: u32) -> &mut Self {
519 self.start = n;
520 self
521 }
522 pub fn item<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
524 let mut inner = Doc::new();
525 let _ = f(&mut inner);
526 self.items.push(ListItem { blocks: inner.blocks, check: None });
527 self
528 }
529 pub fn task<R>(&mut self, done: bool, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
531 let mut inner = Doc::new();
532 let _ = f(&mut inner);
533 self.items.push(ListItem { blocks: inner.blocks, check: Some(done) });
534 self
535 }
536}
537
538pub struct ImageBuilder {
540 width: Option<Length>,
541 align: Align,
542 caption: Option<Vec<Inline>>,
543}
544
545impl ImageBuilder {
546 pub fn width_px(&mut self, px: f32) -> &mut Self {
548 self.width = Some(Length::Px(px));
549 self
550 }
551 pub fn width_percent(&mut self, pct: f32) -> &mut Self {
553 self.width = Some(Length::Percent(pct));
554 self
555 }
556 pub fn align(&mut self, a: Align) -> &mut Self {
558 self.align = a;
559 self
560 }
561 pub fn caption(&mut self, s: impl Into<String>) -> &mut Self {
563 self.caption = Some(vec![Inline::Text { text: s.into(), style: TextStyle::default() }]);
564 self
565 }
566 pub fn caption_with<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
568 let mut pb = ParaBuilder::new();
569 let _ = f(&mut pb);
570 self.caption = Some(pb.inlines);
571 self
572 }
573}
574