1use std::path::PathBuf;
6
7#[derive(Clone, Debug, Default)]
9pub struct Document {
10 pub blocks: Vec<Block>,
12}
13
14#[derive(Clone, Debug)]
16pub enum Block {
17 Heading {
19 level: u8,
21 inlines: Vec<Inline>,
23 align: Align,
25 },
26 Paragraph {
28 inlines: Vec<Inline>,
30 align: Align,
32 },
33 List(List),
35 Quote(Vec<Block>),
37 Code {
39 lang: Option<String>,
41 text: String,
43 },
44 Divider,
46 Image(BlockImage),
48 Columns(Columns),
50 Table(Table),
52 Progress(Progress),
54 Panel(Panel),
56}
57
58#[derive(Clone, Debug)]
60pub struct Panel {
61 pub blocks: Vec<Block>,
63 pub decor: PanelDecor,
65}
66
67#[derive(Clone, Debug, Default)]
69pub struct PanelDecor {
70 pub bg: Option<Color>,
72 pub border: Option<ImageBorder>,
74 pub radius: Option<f32>,
76 pub pad: Option<f32>,
78 pub shadow: Option<Shadow>,
80}
81
82#[derive(Clone, Debug)]
84pub struct Table {
85 pub header: Option<Vec<Cell>>,
87 pub rows: Vec<Vec<Cell>>,
89 pub cols: Vec<ColSpec>,
91 pub style: TableStyle,
93}
94
95#[derive(Clone, Debug)]
97pub struct TableStyle {
98 pub pad_x: Option<f32>,
100 pub pad_y: Option<f32>,
102 pub grid: TableGrid,
104 pub header_fill: bool,
106 pub expand: bool,
109}
110
111impl Default for TableStyle {
112 fn default() -> Self {
113 Self { pad_x: None, pad_y: None, grid: TableGrid::default(), header_fill: true, expand: false }
114 }
115}
116
117#[derive(Clone, Copy, Debug)]
119pub struct TableGrid {
120 pub outer: bool,
122 pub vertical: bool,
124 pub horizontal: bool,
126}
127
128impl Default for TableGrid {
129 fn default() -> Self {
130 Self { outer: true, vertical: true, horizontal: true }
131 }
132}
133
134#[derive(Clone, Debug)]
136pub struct ColSpec {
137 pub align: Align,
139 pub width: Option<Length>,
141}
142
143impl Default for ColSpec {
144 fn default() -> Self {
145 Self { align: Align::Left, width: None }
146 }
147}
148
149#[derive(Clone, Debug)]
151pub struct Cell {
152 pub inlines: Vec<Inline>,
154 pub bg: Option<Color>,
156}
157
158#[derive(Clone, Debug)]
160pub struct Columns {
161 pub cols: Vec<Column>,
163 pub gap: Option<f32>,
165}
166
167#[derive(Clone, Debug)]
169pub struct Column {
170 pub blocks: Vec<Block>,
172 pub weight: f32,
174}
175
176#[derive(Clone, Debug)]
178pub struct Progress {
179 pub value: f32,
181 pub height: f32,
183 pub fill: Option<Color>,
185 pub track: Option<Color>,
187 pub radius: Option<f32>,
189 pub width: Option<Length>,
191 pub align: Align,
193}
194
195#[derive(Clone, Debug)]
197pub struct List {
198 pub kind: ListKind,
200 pub start: u32,
202 pub items: Vec<ListItem>,
204}
205
206#[derive(Clone, Copy, Debug, PartialEq, Eq)]
208pub enum ListKind {
209 Unordered,
211 Ordered,
213}
214
215#[derive(Clone, Debug)]
217pub struct ListItem {
218 pub blocks: Vec<Block>,
220 pub check: Option<bool>,
223}
224
225#[derive(Clone, Debug)]
227pub struct BlockImage {
228 pub src: ImageSource,
230 pub width: Option<Length>,
232 pub align: Align,
234 pub caption: Option<Vec<Inline>>,
236 pub decor: ImageDecor,
238}
239
240#[derive(Clone, Debug, Default)]
242pub struct ImageDecor {
243 pub badge: Option<Badge>,
245 pub border: Option<ImageBorder>,
247 pub watermark: Option<Watermark>,
249 pub radius: f32,
251 pub shadow: Option<Shadow>,
253}
254
255#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
257pub enum Anchor {
258 TopLeft,
260 #[default]
262 TopRight,
263 BottomLeft,
265 BottomRight,
267 Center,
269}
270
271#[derive(Clone, Debug)]
273pub struct Badge {
274 pub text: String,
276 pub anchor: Anchor,
278 pub bg: Color,
280 pub fg: Color,
282 pub size: f32,
284}
285
286impl Badge {
287 pub fn new(text: impl Into<String>) -> Self {
289 Self {
290 text: text.into(),
291 anchor: Anchor::TopRight,
292 bg: Color::rgba(0, 0, 0, 184),
293 fg: Color::rgb(255, 255, 255),
294 size: 0.75,
295 }
296 }
297}
298
299#[derive(Clone, Copy, Debug, PartialEq)]
301pub struct ImageBorder {
302 pub width: f32,
304 pub color: Color,
306}
307
308#[derive(Clone, Debug)]
310pub struct Watermark {
311 pub text: String,
313 pub anchor: Anchor,
315 pub color: Color,
317 pub size: f32,
319}
320
321impl Watermark {
322 pub fn new(text: impl Into<String>) -> Self {
324 Self {
325 text: text.into(),
326 anchor: Anchor::BottomRight,
327 color: Color::rgba(255, 255, 255, 102),
328 size: 0.9,
329 }
330 }
331}
332
333#[derive(Clone, Debug)]
335pub enum Inline {
336 Text {
338 text: String,
340 style: TextStyle,
342 },
343 Code(String),
345 LineBreak,
347}
348
349#[derive(Clone, Debug, PartialEq)]
351pub struct TextStyle {
352 pub weight: Option<u16>,
355 pub italic: bool,
357 pub underline: bool,
359 pub strike: bool,
361 pub color: Option<Color>,
363 pub highlight: Option<Highlight>,
365 pub size: f32,
367 pub font: FontRole,
369 pub link: bool,
372 pub shadow: Option<Shadow>,
374 pub ring: Option<RingMark>,
377 pub dot: Option<DotMark>,
379 pub aside: Option<AsideSide>,
384}
385
386#[derive(Clone, Copy, Debug, PartialEq, Eq)]
388pub enum AsideSide {
389 Left,
391 Right,
393}
394
395#[derive(Clone, Copy, Debug, Default, PartialEq)]
397pub struct RingMark {
398 pub color: Option<Color>,
400 pub rx: Option<f32>,
402 pub ry: Option<f32>,
404 pub width: Option<f32>,
406 pub each: bool,
409}
410
411#[derive(Clone, Copy, Debug, Default, PartialEq)]
413pub struct DotMark {
414 pub color: Option<Color>,
416 pub radius: Option<f32>,
418 pub each: bool,
420}
421
422impl Default for TextStyle {
423 fn default() -> Self {
424 Self {
425 weight: None,
426 italic: false,
427 underline: false,
428 strike: false,
429 color: None,
430 highlight: None,
431 size: 1.0,
432 font: FontRole::Sans,
433 link: false,
434 shadow: None,
435 ring: None,
436 dot: None,
437 aside: None,
438 }
439 }
440}
441
442#[derive(Clone, Copy, Debug, PartialEq)]
445pub struct Shadow {
446 pub dx: f32,
448 pub dy: f32,
450 pub blur: f32,
452 pub color: Color,
454}
455
456impl Default for Shadow {
457 fn default() -> Self {
458 Self { dx: 0.0, dy: 2.0, blur: 6.0, color: Color::rgba(0, 0, 0, 64) }
460 }
461}
462
463#[derive(Clone, Copy, Debug, PartialEq, Eq)]
465pub enum Highlight {
466 Theme,
468 Custom(Color),
470}
471
472#[derive(Clone, Debug, PartialEq)]
474pub enum FontRole {
475 Sans,
477 Serif,
479 Mono,
481 Kai,
483 Named(String),
485}
486
487#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
489pub enum Align {
490 #[default]
492 Left,
493 Center,
495 Right,
497 Justify,
499}
500
501#[derive(Clone, Copy, Debug, PartialEq, Eq)]
503pub struct Color {
504 pub r: u8,
506 pub g: u8,
508 pub b: u8,
510 pub a: u8,
512}
513
514impl Color {
515 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
517 Self { r, g, b, a: 255 }
518 }
519
520 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
522 Self { r, g, b, a }
523 }
524
525 pub fn hex(s: &str) -> Option<Self> {
528 let h = s.strip_prefix('#').unwrap_or(s);
529 if !h.bytes().all(|b| b.is_ascii_hexdigit()) {
530 return None;
531 }
532 let n = |slice: &str| u8::from_str_radix(slice, 16).ok();
533 match h.len() {
534 3 => {
535 let b = h.as_bytes();
536 let dup = |c: u8| {
537 let d = (c as char).to_digit(16)? as u8;
538 Some(d << 4 | d)
539 };
540 Some(Self::rgb(dup(b[0])?, dup(b[1])?, dup(b[2])?))
541 }
542 6 => Some(Self::rgb(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?)),
543 8 => Some(Self::rgba(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?, n(&h[6..8])?)),
544 _ => None,
545 }
546 }
547}
548
549#[derive(Clone, Copy, Debug, PartialEq)]
551pub enum Length {
552 Px(f32),
554 Percent(f32),
556}
557
558#[derive(Clone, Debug)]
560pub enum ImageSource {
561 Bytes(Vec<u8>),
563 Path(PathBuf),
565 Named(String),
567}