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, 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 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 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 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 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 bg = Color::hex(hex);
if let Some(h) = self.header.as_mut().and_then(|h| h.get_mut(col)) {
h.bg = bg;
}
for row in &mut self.rows {
if let Some(c) = row.get_mut(col) {
c.bg = bg;
}
}
self
}
pub fn row_fill(&mut self, row: usize, hex: &str) -> &mut Self {
let bg = Color::hex(hex);
if let Some(r) = self.rows.get_mut(row) {
for c in r.iter_mut() {
c.bg = bg;
}
}
self
}
pub fn cell_fill(&mut self, row: usize, col: usize, hex: &str) -> &mut Self {
if let Some(c) = self.rows.get_mut(row).and_then(|r| r.get_mut(col)) {
c.bg = Color::hex(hex);
}
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 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
}
}
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 {
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 mut inner = Doc::new();
let _ = f(&mut inner);
self.cols.push(Column { blocks: inner.blocks, weight });
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
}
}