use std::path::PathBuf;
#[derive(Clone, Debug, Default)]
pub struct Document {
pub blocks: Vec<Block>,
}
#[derive(Clone, Debug)]
pub enum Block {
Heading {
level: u8,
inlines: Vec<Inline>,
align: Align,
},
Paragraph {
inlines: Vec<Inline>,
align: Align,
},
List(List),
Quote(Vec<Block>),
Code {
lang: Option<String>,
text: String,
},
Divider,
Image(BlockImage),
Columns(Columns),
Table(Table),
Progress(Progress),
}
#[derive(Clone, Debug)]
pub struct Table {
pub header: Option<Vec<Cell>>,
pub rows: Vec<Vec<Cell>>,
pub cols: Vec<ColSpec>,
pub style: TableStyle,
}
#[derive(Clone, Debug)]
pub struct TableStyle {
pub pad_x: Option<f32>,
pub pad_y: Option<f32>,
pub grid: TableGrid,
pub header_fill: bool,
pub expand: bool,
}
impl Default for TableStyle {
fn default() -> Self {
Self { pad_x: None, pad_y: None, grid: TableGrid::default(), header_fill: true, expand: false }
}
}
#[derive(Clone, Copy, Debug)]
pub struct TableGrid {
pub outer: bool,
pub vertical: bool,
pub horizontal: bool,
}
impl Default for TableGrid {
fn default() -> Self {
Self { outer: true, vertical: true, horizontal: true }
}
}
#[derive(Clone, Debug)]
pub struct ColSpec {
pub align: Align,
pub width: Option<Length>,
}
impl Default for ColSpec {
fn default() -> Self {
Self { align: Align::Left, width: None }
}
}
#[derive(Clone, Debug)]
pub struct Cell {
pub inlines: Vec<Inline>,
pub bg: Option<Color>,
}
#[derive(Clone, Debug)]
pub struct Columns {
pub cols: Vec<Column>,
pub gap: Option<f32>,
}
#[derive(Clone, Debug)]
pub struct Column {
pub blocks: Vec<Block>,
pub weight: f32,
}
#[derive(Clone, Debug)]
pub struct Progress {
pub value: f32,
pub height: f32,
pub fill: Option<Color>,
pub track: Option<Color>,
pub radius: Option<f32>,
pub width: Option<Length>,
pub align: Align,
}
#[derive(Clone, Debug)]
pub struct List {
pub kind: ListKind,
pub start: u32,
pub items: Vec<ListItem>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ListKind {
Unordered,
Ordered,
}
#[derive(Clone, Debug)]
pub struct ListItem {
pub blocks: Vec<Block>,
pub check: Option<bool>,
}
#[derive(Clone, Debug)]
pub struct BlockImage {
pub src: ImageSource,
pub width: Option<Length>,
pub align: Align,
pub caption: Option<Vec<Inline>>,
pub decor: ImageDecor,
}
#[derive(Clone, Debug, Default)]
pub struct ImageDecor {
pub badge: Option<Badge>,
pub border: Option<ImageBorder>,
pub watermark: Option<Watermark>,
pub radius: f32,
pub shadow: Option<Shadow>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum Anchor {
TopLeft,
#[default]
TopRight,
BottomLeft,
BottomRight,
Center,
}
#[derive(Clone, Debug)]
pub struct Badge {
pub text: String,
pub anchor: Anchor,
pub bg: Color,
pub fg: Color,
pub size: f32,
}
impl Badge {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
anchor: Anchor::TopRight,
bg: Color::rgba(0, 0, 0, 184),
fg: Color::rgb(255, 255, 255),
size: 0.75,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ImageBorder {
pub width: f32,
pub color: Color,
}
#[derive(Clone, Debug)]
pub struct Watermark {
pub text: String,
pub anchor: Anchor,
pub color: Color,
pub size: f32,
}
impl Watermark {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
anchor: Anchor::BottomRight,
color: Color::rgba(255, 255, 255, 102),
size: 0.9,
}
}
}
#[derive(Clone, Debug)]
pub enum Inline {
Text {
text: String,
style: TextStyle,
},
Code(String),
LineBreak,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TextStyle {
pub weight: Option<u16>,
pub italic: bool,
pub underline: bool,
pub strike: bool,
pub color: Option<Color>,
pub highlight: Option<Highlight>,
pub size: f32,
pub font: FontRole,
pub link: bool,
pub shadow: Option<Shadow>,
pub ring: Option<RingMark>,
pub dot: Option<DotMark>,
pub aside: Option<AsideSide>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AsideSide {
Left,
Right,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct RingMark {
pub color: Option<Color>,
pub rx: Option<f32>,
pub ry: Option<f32>,
pub width: Option<f32>,
pub each: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct DotMark {
pub color: Option<Color>,
pub radius: Option<f32>,
pub each: bool,
}
impl Default for TextStyle {
fn default() -> Self {
Self {
weight: None,
italic: false,
underline: false,
strike: false,
color: None,
highlight: None,
size: 1.0,
font: FontRole::Sans,
link: false,
shadow: None,
ring: None,
dot: None,
aside: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Shadow {
pub dx: f32,
pub dy: f32,
pub blur: f32,
pub color: Color,
}
impl Default for Shadow {
fn default() -> Self {
Self { dx: 0.0, dy: 2.0, blur: 6.0, color: Color::rgba(0, 0, 0, 64) }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Highlight {
Theme,
Custom(Color),
}
#[derive(Clone, Debug, PartialEq)]
pub enum FontRole {
Sans,
Serif,
Mono,
Kai,
Named(String),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum Align {
#[default]
Left,
Center,
Right,
Justify,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Color {
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b, a: 255 }
}
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
pub fn hex(s: &str) -> Option<Self> {
let h = s.strip_prefix('#').unwrap_or(s);
if !h.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
let n = |slice: &str| u8::from_str_radix(slice, 16).ok();
match h.len() {
3 => {
let b = h.as_bytes();
let dup = |c: u8| {
let d = (c as char).to_digit(16)? as u8;
Some(d << 4 | d)
};
Some(Self::rgb(dup(b[0])?, dup(b[1])?, dup(b[2])?))
}
6 => Some(Self::rgb(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?)),
8 => Some(Self::rgba(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?, n(&h[6..8])?)),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Length {
Px(f32),
Percent(f32),
}
#[derive(Clone, Debug)]
pub enum ImageSource {
Bytes(Vec<u8>),
Path(PathBuf),
Named(String),
}