use std::path::PathBuf;
use crate::model::{
Align, Anchor, Badge, Block, BlockImage, Cell, ColSpec, Color, Column, Columns, Document,
FontRole, Highlight, ImageBorder, ImageDecor, Inline, Length, List, ListItem, ListKind,
Panel, PanelDecor, Progress, Shadow, Table, TableStyle, TextStyle, Watermark,
};
#[derive(Default)]
pub struct Doc {
blocks: Vec<Block>,
}
impl Doc {
pub fn new() -> Self {
Self { blocks: Vec::new() }
}
pub fn build(&self) -> Document {
Document { blocks: self.blocks.clone() }
}
pub fn heading<R>(&mut self, level: u8, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
let mut pb = ParaBuilder::new();
let _ = f(&mut pb);
self.blocks.push(Block::Heading {
level: level.clamp(1, 6),
inlines: pb.inlines,
align: pb.align,
});
self
}
pub fn paragraph<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
let mut pb = ParaBuilder::new();
let _ = f(&mut pb);
self.blocks.push(Block::Paragraph { inlines: pb.inlines, align: pb.align });
self
}
pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
self.paragraph(|p| {
p.text(s);
})
}
pub fn quote<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
let mut inner = Doc::new();
let _ = f(&mut inner);
self.blocks.push(Block::Quote(inner.blocks));
self
}
pub fn list<R>(&mut self, kind: ListKind, f: impl FnOnce(&mut ListBuilder) -> R) -> &mut Self {
let mut lb = ListBuilder { kind, start: 1, items: Vec::new() };
let _ = f(&mut lb);
self.blocks.push(Block::List(List { kind: lb.kind, start: lb.start, items: lb.items }));
self
}
pub fn code(&mut self, lang: impl Into<String>, text: impl Into<String>) -> &mut Self {
let lang = lang.into();
self.blocks.push(Block::Code {
lang: if lang.is_empty() { None } else { Some(lang) },
text: text.into(),
});
self
}
pub fn divider(&mut self) -> &mut Self {
self.blocks.push(Block::Divider);
self
}
pub fn panel<R>(&mut self, f: impl FnOnce(&mut PanelBuilder) -> R) -> &mut Self {
let mut pb = PanelBuilder::new();
let _ = f(&mut pb);
self.blocks.push(Block::Panel(pb.into_panel()));
self
}
pub fn columns<R>(&mut self, f: impl FnOnce(&mut ColumnsBuilder) -> R) -> &mut Self {
let mut cb = ColumnsBuilder { gap: None, cols: Vec::new() };
let _ = f(&mut cb);
self.blocks.push(Block::Columns(Columns { cols: cb.cols, gap: cb.gap }));
self
}
pub fn table<R>(&mut self, f: impl FnOnce(&mut TableBuilder) -> R) -> &mut Self {
let mut tb = TableBuilder {
header: None,
rows: Vec::new(),
cols: Vec::new(),
style: TableStyle::default(),
};
let _ = f(&mut tb);
self.blocks.push(Block::Table(Table {
header: tb.header,
rows: tb.rows,
cols: tb.cols,
style: tb.style,
}));
self
}
pub fn progress<R>(&mut self, value: f32, f: impl FnOnce(&mut ProgressBuilder) -> R) -> &mut Self {
let mut pb = ProgressBuilder {
p: Progress {
value,
height: 10.0,
fill: None,
track: None,
radius: None,
width: None,
align: Align::Left,
},
};
let _ = f(&mut pb);
self.blocks.push(Block::Progress(pb.p));
self
}
pub fn image_bytes<R>(
&mut self,
bytes: Vec<u8>,
f: impl FnOnce(&mut ImageBuilder) -> R,
) -> &mut Self {
self.push_block_image(ImageSource::Bytes(bytes), f)
}
pub fn image_path<R>(
&mut self,
path: impl Into<PathBuf>,
f: impl FnOnce(&mut ImageBuilder) -> R,
) -> &mut Self {
self.push_block_image(ImageSource::Path(path.into()), f)
}
fn push_block_image<R>(
&mut self,
src: ImageSource,
f: impl FnOnce(&mut ImageBuilder) -> R,
) -> &mut Self {
let mut ib = ImageBuilder {
width: None,
align: Align::Left,
caption: None,
decor: ImageDecor::default(),
};
let _ = f(&mut ib);
self.blocks.push(Block::Image(BlockImage {
src,
width: ib.width,
align: ib.align,
caption: ib.caption,
decor: ib.decor,
}));
self
}
}
use crate::model::ImageSource;
pub struct ParaBuilder {
inlines: Vec<Inline>,
align: Align,
}
impl ParaBuilder {
pub(crate) fn new() -> Self {
Self { inlines: Vec::new(), align: Align::Left }
}
pub(crate) fn into_inlines(self) -> Vec<Inline> {
self.inlines
}
pub fn align(&mut self, a: Align) -> &mut Self {
self.align = a;
self
}
pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
self.push(s, TextStyle::default())
}
pub fn bold(&mut self, s: impl Into<String>) -> &mut Self {
self.push(s, TextStyle { weight: Some(700), ..Default::default() })
}
pub fn light(&mut self, s: impl Into<String>) -> &mut Self {
self.push(s, TextStyle { weight: Some(300), ..Default::default() })
}
pub fn italic(&mut self, s: impl Into<String>) -> &mut Self {
self.push(s, TextStyle { italic: true, ..Default::default() })
}
pub fn underline(&mut self, s: impl Into<String>) -> &mut Self {
self.push(s, TextStyle { underline: true, ..Default::default() })
}
pub fn strike(&mut self, s: impl Into<String>) -> &mut Self {
self.push(s, TextStyle { strike: true, ..Default::default() })
}
pub fn highlight(&mut self, s: impl Into<String>) -> &mut Self {
self.push(s, TextStyle { highlight: Some(Highlight::Theme), ..Default::default() })
}
pub fn color(&mut self, hex: &str, s: impl Into<String>) -> &mut Self {
self.push(s, TextStyle { color: Color::hex(hex), ..Default::default() })
}
pub fn code(&mut self, s: impl Into<String>) -> &mut Self {
self.inlines.push(Inline::Code(s.into()));
self
}
pub fn styled<R>(
&mut self,
s: impl Into<String>,
f: impl FnOnce(&mut StyleBuilder) -> R,
) -> &mut Self {
let mut sb = StyleBuilder { style: TextStyle::default() };
let _ = f(&mut sb);
self.push(s, sb.style)
}
pub fn line_break(&mut self) -> &mut Self {
self.inlines.push(Inline::LineBreak);
self
}
fn push(&mut self, s: impl Into<String>, style: TextStyle) -> &mut Self {
self.inlines.push(Inline::Text { text: s.into(), style });
self
}
}
pub struct StyleBuilder {
style: TextStyle,
}
impl StyleBuilder {
pub fn bold(&mut self) -> &mut Self {
self.style.weight = Some(700);
self
}
pub fn light(&mut self) -> &mut Self {
self.style.weight = Some(300);
self
}
pub fn weight(&mut self, w: u16) -> &mut Self {
if (1..=1000).contains(&w) {
self.style.weight = Some(w);
}
self
}
pub fn italic(&mut self) -> &mut Self {
self.style.italic = true;
self
}
pub fn underline(&mut self) -> &mut Self {
self.style.underline = true;
self
}
pub fn strike(&mut self) -> &mut Self {
self.style.strike = true;
self
}
pub fn color(&mut self, hex: &str) -> &mut Self {
if let Some(c) = Color::hex(hex) {
self.style.color = Some(c);
}
self
}
pub fn bg(&mut self, hex: &str) -> &mut Self {
if let Some(c) = Color::hex(hex) {
self.style.highlight = Some(Highlight::Custom(c));
}
self
}
pub fn size(&mut self, mult: f32) -> &mut Self {
if mult.is_finite() && mult > 0.0 {
self.style.size = mult;
}
self
}
pub fn font(&mut self, role: FontRole) -> &mut Self {
self.style.font = role;
self
}
pub fn link(&mut self) -> &mut Self {
self.style.link = true;
self
}
pub fn ring(&mut self) -> &mut Self {
self.style.ring.get_or_insert_default();
self
}
pub fn ring_color(&mut self, hex: &str) -> &mut Self {
let r = self.style.ring.get_or_insert_default();
r.color = Color::hex(hex).or(r.color);
self
}
pub fn ring_radius(&mut self, r: f32) -> &mut Self {
self.ring_radii(r, r)
}
pub fn ring_radii(&mut self, rx: f32, ry: f32) -> &mut Self {
let r = self.style.ring.get_or_insert_default();
if rx.is_finite() && rx > 0.0 {
r.rx = Some(rx);
}
if ry.is_finite() && ry > 0.0 {
r.ry = Some(ry);
}
self
}
pub fn ring_stroke(&mut self, w: f32) -> &mut Self {
let r = self.style.ring.get_or_insert_default();
if w.is_finite() && w > 0.0 {
r.width = Some(w);
}
self
}
pub fn ring_each(&mut self) -> &mut Self {
self.style.ring.get_or_insert_default().each = true;
self
}
pub fn dot(&mut self) -> &mut Self {
self.style.dot.get_or_insert_default();
self
}
pub fn dot_color(&mut self, hex: &str) -> &mut Self {
let d = self.style.dot.get_or_insert_default();
d.color = Color::hex(hex).or(d.color);
self
}
pub fn dot_radius(&mut self, r: f32) -> &mut Self {
let d = self.style.dot.get_or_insert_default();
if r.is_finite() && r > 0.0 {
d.radius = Some(r);
}
self
}
pub fn dot_each(&mut self) -> &mut Self {
self.style.dot.get_or_insert_default().each = true;
self
}
pub fn aside_right(&mut self) -> &mut Self {
self.style.aside = Some(crate::model::AsideSide::Right);
self
}
pub fn aside_left(&mut self) -> &mut Self {
self.style.aside = Some(crate::model::AsideSide::Left);
self
}
pub fn shadow(&mut self) -> &mut Self {
self.style.shadow = Some(Shadow::default());
self
}
pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
if let Some(color) = Color::hex(hex) {
self.style.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
}
self
}
}
pub struct TableBuilder {
header: Option<Vec<Cell>>,
rows: Vec<Vec<Cell>>,
cols: Vec<ColSpec>,
style: TableStyle,
}
impl TableBuilder {
pub fn head<I, S>(&mut self, cells: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.header = Some(cells.into_iter().map(text_cell).collect());
self
}
pub fn row<I, S>(&mut self, cells: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.rows.push(cells.into_iter().map(text_cell).collect());
self
}
pub fn head_rich<R>(&mut self, f: impl FnOnce(&mut RowBuilder) -> R) -> &mut Self {
let mut rb = RowBuilder { cells: Vec::new() };
let _ = f(&mut rb);
self.header = Some(rb.cells);
self
}
pub fn row_rich<R>(&mut self, f: impl FnOnce(&mut RowBuilder) -> R) -> &mut Self {
let mut rb = RowBuilder { cells: Vec::new() };
let _ = f(&mut rb);
self.rows.push(rb.cells);
self
}
pub fn align<I: IntoIterator<Item = Align>>(&mut self, aligns: I) -> &mut Self {
for (k, a) in aligns.into_iter().enumerate() {
self.ensure_col(k).align = a;
}
self
}
pub fn width(&mut self, col: usize, w: Length) -> &mut Self {
self.ensure_col(col).width = Some(w);
self
}
fn ensure_col(&mut self, k: usize) -> &mut ColSpec {
while self.cols.len() <= k {
self.cols.push(ColSpec::default());
}
&mut self.cols[k]
}
pub fn col_style<R>(&mut self, col: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
style_cell(h, &f);
}
for row in &mut self.rows {
if let Some(c) = row.get_mut(col) {
style_cell(c, &f);
}
}
self
}
pub fn row_style<R>(&mut self, row: usize, f: impl Fn(&mut StyleBuilder) -> R) -> &mut Self {
if let Some(r) = self.rows.get_mut(row) {
for c in r.iter_mut() {
style_cell(c, &f);
}
}
self
}
pub fn cell_style<R>(
&mut self,
row: usize,
col: usize,
f: impl Fn(&mut StyleBuilder) -> R,
) -> &mut Self {
if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
style_cell(c, &f);
}
self
}
pub fn col_fill(&mut self, col: usize, hex: &str) -> &mut Self {
let Some(bg) = Color::hex(hex) else { return self };
if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
h.bg = Some(bg);
}
for row in &mut self.rows {
if let Some(c) = row.get_mut(col) {
c.bg = Some(bg);
}
}
self
}
pub fn row_fill(&mut self, row: usize, hex: &str) -> &mut Self {
let Some(bg) = Color::hex(hex) else { return self };
if let Some(r) = self.rows.get_mut(row) {
for c in r.iter_mut() {
c.bg = Some(bg);
}
}
self
}
pub fn cell_fill(&mut self, row: usize, col: usize, hex: &str) -> &mut Self {
let Some(bg) = Color::hex(hex) else { return self };
if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
c.bg = Some(bg);
}
self
}
pub fn pad_x(&mut self, px: f32) -> &mut Self {
self.style.pad_x = Some(px.max(0.0));
self
}
pub fn pad_y(&mut self, px: f32) -> &mut Self {
self.style.pad_y = Some(px.max(0.0));
self
}
pub fn expand(&mut self) -> &mut Self {
self.style.expand = true;
self
}
pub fn table_align(&mut self, a: Align) -> &mut Self {
self.style.align = a;
self
}
pub fn grid_outer(&mut self, on: bool) -> &mut Self {
self.style.grid.outer = on;
self
}
pub fn grid_vertical(&mut self, on: bool) -> &mut Self {
self.style.grid.vertical = on;
self
}
pub fn grid_horizontal(&mut self, on: bool) -> &mut Self {
self.style.grid.horizontal = on;
self
}
pub fn no_grid(&mut self) -> &mut Self {
self.style.grid.outer = false;
self.style.grid.vertical = false;
self.style.grid.horizontal = false;
self
}
pub fn header_fill(&mut self, on: bool) -> &mut Self {
self.style.header_fill = on;
self
}
}
pub struct RowBuilder {
cells: Vec<Cell>,
}
impl RowBuilder {
pub fn cell<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
let mut pb = ParaBuilder::new();
let _ = f(&mut pb);
self.cells.push(Cell { inlines: pb.into_inlines(), bg: None });
self
}
pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
self.cells.push(text_cell(s));
self
}
}
fn text_cell(s: impl Into<String>) -> Cell {
Cell { inlines: vec![Inline::Text { text: s.into(), style: TextStyle::default() }], bg: None }
}
fn style_cell<R>(cell: &mut Cell, f: &impl Fn(&mut StyleBuilder) -> R) {
for inl in &mut cell.inlines {
if let Inline::Text { style, .. } = inl {
let mut sb = StyleBuilder { style: style.clone() };
let _ = f(&mut sb);
*style = sb.style;
}
}
}
pub struct ColumnsBuilder {
gap: Option<f32>,
cols: Vec<Column>,
}
impl ColumnsBuilder {
pub fn gap(&mut self, g: f32) -> &mut Self {
if g.is_finite() && g >= 0.0 {
self.gap = Some(g);
}
self
}
pub fn col<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
self.col_weighted(1.0, f)
}
pub fn col_weighted<R>(&mut self, weight: f32, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
let weight = if weight.is_finite() && weight > 0.0 { weight } else { 1.0 };
let mut inner = Doc::new();
let _ = f(&mut inner);
self.cols.push(Column { blocks: inner.blocks, weight });
self
}
pub fn panel<R>(&mut self, f: impl FnOnce(&mut PanelBuilder) -> R) -> &mut Self {
self.panel_weighted(1.0, f)
}
pub fn panel_weighted<R>(
&mut self,
weight: f32,
f: impl FnOnce(&mut PanelBuilder) -> R,
) -> &mut Self {
let weight = if weight.is_finite() && weight > 0.0 { weight } else { 1.0 };
let mut pb = PanelBuilder::new();
let _ = f(&mut pb);
self.cols.push(Column { blocks: vec![Block::Panel(pb.into_panel())], weight });
self
}
}
pub struct PanelBuilder {
doc: Doc,
decor: PanelDecor,
}
impl PanelBuilder {
fn new() -> Self {
Self { doc: Doc::new(), decor: PanelDecor::default() }
}
fn into_panel(self) -> Panel {
Panel { blocks: self.doc.blocks, decor: self.decor }
}
pub fn bg(&mut self, hex: &str) -> &mut Self {
if let Some(c) = Color::hex(hex) {
self.decor.bg = Some(c);
}
self
}
pub fn border(&mut self, width: f32, hex: &str) -> &mut Self {
if width > 0.0 && width.is_finite() {
if let Some(color) = Color::hex(hex) {
self.decor.border = Some(ImageBorder { width, color });
}
}
self
}
pub fn rounded(&mut self, radius: f32) -> &mut Self {
if radius.is_finite() && radius >= 0.0 {
self.decor.radius = Some(radius);
}
self
}
pub fn pad(&mut self, px: f32) -> &mut Self {
if px.is_finite() && px >= 0.0 {
self.decor.pad = Some(px);
}
self
}
pub fn shadow(&mut self) -> &mut Self {
self.decor.shadow = Some(Shadow::default());
self
}
pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
if let Some(color) = Color::hex(hex) {
self.decor.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
}
self
}
}
impl std::ops::Deref for PanelBuilder {
type Target = Doc;
fn deref(&self) -> &Doc {
&self.doc
}
}
impl std::ops::DerefMut for PanelBuilder {
fn deref_mut(&mut self) -> &mut Doc {
&mut self.doc
}
}
pub struct ProgressBuilder {
p: Progress,
}
impl ProgressBuilder {
pub fn height(&mut self, h: f32) -> &mut Self {
self.p.height = h;
self
}
pub fn fill(&mut self, hex: &str) -> &mut Self {
self.p.fill = Color::hex(hex).or(self.p.fill);
self
}
pub fn track(&mut self, hex: &str) -> &mut Self {
self.p.track = Color::hex(hex).or(self.p.track);
self
}
pub fn radius(&mut self, r: f32) -> &mut Self {
self.p.radius = Some(r);
self
}
pub fn width_px(&mut self, px: f32) -> &mut Self {
self.p.width = Some(Length::Px(px));
self
}
pub fn width_percent(&mut self, pct: f32) -> &mut Self {
self.p.width = Some(Length::Percent(pct));
self
}
pub fn align(&mut self, a: Align) -> &mut Self {
self.p.align = a;
self
}
}
pub struct ListBuilder {
kind: ListKind,
start: u32,
items: Vec<ListItem>,
}
impl ListBuilder {
pub fn start(&mut self, n: u32) -> &mut Self {
self.start = n;
self
}
pub fn item<R>(&mut self, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
let mut inner = Doc::new();
let _ = f(&mut inner);
self.items.push(ListItem { blocks: inner.blocks, check: None });
self
}
pub fn task<R>(&mut self, done: bool, f: impl FnOnce(&mut Doc) -> R) -> &mut Self {
let mut inner = Doc::new();
let _ = f(&mut inner);
self.items.push(ListItem { blocks: inner.blocks, check: Some(done) });
self
}
}
pub struct ImageBuilder {
width: Option<Length>,
align: Align,
caption: Option<Vec<Inline>>,
decor: ImageDecor,
}
impl ImageBuilder {
pub fn width_px(&mut self, px: f32) -> &mut Self {
self.width = Some(Length::Px(px));
self
}
pub fn width_percent(&mut self, pct: f32) -> &mut Self {
self.width = Some(Length::Percent(pct));
self
}
pub fn align(&mut self, a: Align) -> &mut Self {
self.align = a;
self
}
pub fn caption(&mut self, s: impl Into<String>) -> &mut Self {
self.caption = Some(vec![Inline::Text { text: s.into(), style: TextStyle::default() }]);
self
}
pub fn caption_with<R>(&mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> &mut Self {
let mut pb = ParaBuilder::new();
let _ = f(&mut pb);
self.caption = Some(pb.inlines);
self
}
pub fn badge<R>(
&mut self,
text: impl Into<String>,
f: impl FnOnce(&mut BadgeBuilder) -> R,
) -> &mut Self {
let mut bb = BadgeBuilder { badge: Badge::new(text) };
let _ = f(&mut bb);
self.decor.badge = Some(bb.badge);
self
}
pub fn border(&mut self, width: f32, hex: &str) -> &mut Self {
if width > 0.0 && width.is_finite() {
if let Some(color) = Color::hex(hex) {
self.decor.border = Some(ImageBorder { width, color });
}
}
self
}
pub fn watermark<R>(
&mut self,
text: impl Into<String>,
f: impl FnOnce(&mut WatermarkBuilder) -> R,
) -> &mut Self {
let mut wb = WatermarkBuilder { wm: Watermark::new(text) };
let _ = f(&mut wb);
self.decor.watermark = Some(wb.wm);
self
}
pub fn rounded(&mut self, radius: f32) -> &mut Self {
if radius.is_finite() && radius > 0.0 {
self.decor.radius = radius;
}
self
}
pub fn shadow(&mut self) -> &mut Self {
self.decor.shadow = Some(Shadow::default());
self
}
pub fn shadow_with(&mut self, dx: f32, dy: f32, blur: f32, hex: &str) -> &mut Self {
if let Some(color) = Color::hex(hex) {
self.decor.shadow = Some(Shadow { dx, dy, blur: blur.max(0.0), color });
}
self
}
}
pub struct BadgeBuilder {
badge: Badge,
}
impl BadgeBuilder {
pub fn anchor(&mut self, a: Anchor) -> &mut Self {
self.badge.anchor = a;
self
}
pub fn bg(&mut self, hex: &str) -> &mut Self {
if let Some(c) = Color::hex(hex) {
self.badge.bg = c;
}
self
}
pub fn fg(&mut self, hex: &str) -> &mut Self {
if let Some(c) = Color::hex(hex) {
self.badge.fg = c;
}
self
}
pub fn size(&mut self, mult: f32) -> &mut Self {
if mult.is_finite() && mult > 0.0 {
self.badge.size = mult;
}
self
}
}
pub struct WatermarkBuilder {
wm: Watermark,
}
impl WatermarkBuilder {
pub fn anchor(&mut self, a: Anchor) -> &mut Self {
self.wm.anchor = a;
self
}
pub fn color(&mut self, hex: &str) -> &mut Self {
if let Some(c) = Color::hex(hex) {
self.wm.color = c;
}
self
}
pub fn size(&mut self, mult: f32) -> &mut Self {
if mult.is_finite() && mult > 0.0 {
self.wm.size = mult;
}
self
}
}