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}
55
56#[derive(Clone, Debug)]
58pub struct Table {
59 pub header: Option<Vec<Cell>>,
61 pub rows: Vec<Vec<Cell>>,
63 pub cols: Vec<ColSpec>,
65 pub style: TableStyle,
67}
68
69#[derive(Clone, Debug)]
71pub struct TableStyle {
72 pub pad_x: Option<f32>,
74 pub pad_y: Option<f32>,
76 pub grid: TableGrid,
78 pub header_fill: bool,
80 pub expand: bool,
83}
84
85impl Default for TableStyle {
86 fn default() -> Self {
87 Self { pad_x: None, pad_y: None, grid: TableGrid::default(), header_fill: true, expand: false }
88 }
89}
90
91#[derive(Clone, Copy, Debug)]
93pub struct TableGrid {
94 pub outer: bool,
96 pub vertical: bool,
98 pub horizontal: bool,
100}
101
102impl Default for TableGrid {
103 fn default() -> Self {
104 Self { outer: true, vertical: true, horizontal: true }
105 }
106}
107
108#[derive(Clone, Debug)]
110pub struct ColSpec {
111 pub align: Align,
113 pub width: Option<Length>,
115}
116
117impl Default for ColSpec {
118 fn default() -> Self {
119 Self { align: Align::Left, width: None }
120 }
121}
122
123#[derive(Clone, Debug)]
125pub struct Cell {
126 pub inlines: Vec<Inline>,
128 pub bg: Option<Color>,
130}
131
132#[derive(Clone, Debug)]
134pub struct Columns {
135 pub cols: Vec<Column>,
137 pub gap: Option<f32>,
139}
140
141#[derive(Clone, Debug)]
143pub struct Column {
144 pub blocks: Vec<Block>,
146 pub weight: f32,
148}
149
150#[derive(Clone, Debug)]
152pub struct Progress {
153 pub value: f32,
155 pub height: f32,
157 pub fill: Option<Color>,
159 pub track: Option<Color>,
161 pub radius: Option<f32>,
163 pub width: Option<Length>,
165 pub align: Align,
167}
168
169#[derive(Clone, Debug)]
171pub struct List {
172 pub kind: ListKind,
174 pub start: u32,
176 pub items: Vec<ListItem>,
178}
179
180#[derive(Clone, Copy, Debug, PartialEq, Eq)]
182pub enum ListKind {
183 Unordered,
185 Ordered,
187}
188
189#[derive(Clone, Debug)]
191pub struct ListItem {
192 pub blocks: Vec<Block>,
194 pub check: Option<bool>,
197}
198
199#[derive(Clone, Debug)]
201pub struct BlockImage {
202 pub src: ImageSource,
204 pub width: Option<Length>,
206 pub align: Align,
208 pub caption: Option<Vec<Inline>>,
210 pub decor: ImageDecor,
212}
213
214#[derive(Clone, Debug, Default)]
216pub struct ImageDecor {
217 pub badge: Option<Badge>,
219 pub border: Option<ImageBorder>,
221 pub watermark: Option<Watermark>,
223 pub radius: f32,
225 pub shadow: Option<Shadow>,
227}
228
229#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
231pub enum Anchor {
232 TopLeft,
234 #[default]
236 TopRight,
237 BottomLeft,
239 BottomRight,
241 Center,
243}
244
245#[derive(Clone, Debug)]
247pub struct Badge {
248 pub text: String,
250 pub anchor: Anchor,
252 pub bg: Color,
254 pub fg: Color,
256 pub size: f32,
258}
259
260impl Badge {
261 pub fn new(text: impl Into<String>) -> Self {
263 Self {
264 text: text.into(),
265 anchor: Anchor::TopRight,
266 bg: Color::rgba(0, 0, 0, 184),
267 fg: Color::rgb(255, 255, 255),
268 size: 0.75,
269 }
270 }
271}
272
273#[derive(Clone, Copy, Debug, PartialEq)]
275pub struct ImageBorder {
276 pub width: f32,
278 pub color: Color,
280}
281
282#[derive(Clone, Debug)]
284pub struct Watermark {
285 pub text: String,
287 pub anchor: Anchor,
289 pub color: Color,
291 pub size: f32,
293}
294
295impl Watermark {
296 pub fn new(text: impl Into<String>) -> Self {
298 Self {
299 text: text.into(),
300 anchor: Anchor::BottomRight,
301 color: Color::rgba(255, 255, 255, 102),
302 size: 0.9,
303 }
304 }
305}
306
307#[derive(Clone, Debug)]
309pub enum Inline {
310 Text {
312 text: String,
314 style: TextStyle,
316 },
317 Code(String),
319 LineBreak,
321}
322
323#[derive(Clone, Debug, PartialEq)]
325pub struct TextStyle {
326 pub weight: Option<u16>,
329 pub italic: bool,
331 pub underline: bool,
333 pub strike: bool,
335 pub color: Option<Color>,
337 pub highlight: Option<Highlight>,
339 pub size: f32,
341 pub font: FontRole,
343 pub link: bool,
346 pub shadow: Option<Shadow>,
348 pub ring: Option<RingMark>,
351 pub dot: Option<DotMark>,
353 pub aside: Option<AsideSide>,
358}
359
360#[derive(Clone, Copy, Debug, PartialEq, Eq)]
362pub enum AsideSide {
363 Left,
365 Right,
367}
368
369#[derive(Clone, Copy, Debug, Default, PartialEq)]
371pub struct RingMark {
372 pub color: Option<Color>,
374 pub rx: Option<f32>,
376 pub ry: Option<f32>,
378 pub width: Option<f32>,
380 pub each: bool,
383}
384
385#[derive(Clone, Copy, Debug, Default, PartialEq)]
387pub struct DotMark {
388 pub color: Option<Color>,
390 pub radius: Option<f32>,
392 pub each: bool,
394}
395
396impl Default for TextStyle {
397 fn default() -> Self {
398 Self {
399 weight: None,
400 italic: false,
401 underline: false,
402 strike: false,
403 color: None,
404 highlight: None,
405 size: 1.0,
406 font: FontRole::Sans,
407 link: false,
408 shadow: None,
409 ring: None,
410 dot: None,
411 aside: None,
412 }
413 }
414}
415
416#[derive(Clone, Copy, Debug, PartialEq)]
419pub struct Shadow {
420 pub dx: f32,
422 pub dy: f32,
424 pub blur: f32,
426 pub color: Color,
428}
429
430impl Default for Shadow {
431 fn default() -> Self {
432 Self { dx: 0.0, dy: 2.0, blur: 6.0, color: Color::rgba(0, 0, 0, 64) }
434 }
435}
436
437#[derive(Clone, Copy, Debug, PartialEq, Eq)]
439pub enum Highlight {
440 Theme,
442 Custom(Color),
444}
445
446#[derive(Clone, Debug, PartialEq)]
448pub enum FontRole {
449 Sans,
451 Serif,
453 Mono,
455 Kai,
457 Named(String),
459}
460
461#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
463pub enum Align {
464 #[default]
466 Left,
467 Center,
469 Right,
471 Justify,
473}
474
475#[derive(Clone, Copy, Debug, PartialEq, Eq)]
477pub struct Color {
478 pub r: u8,
480 pub g: u8,
482 pub b: u8,
484 pub a: u8,
486}
487
488impl Color {
489 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
491 Self { r, g, b, a: 255 }
492 }
493
494 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
496 Self { r, g, b, a }
497 }
498
499 pub fn hex(s: &str) -> Option<Self> {
502 let h = s.strip_prefix('#').unwrap_or(s);
503 if !h.bytes().all(|b| b.is_ascii_hexdigit()) {
504 return None;
505 }
506 let n = |slice: &str| u8::from_str_radix(slice, 16).ok();
507 match h.len() {
508 3 => {
509 let b = h.as_bytes();
510 let dup = |c: u8| {
511 let d = (c as char).to_digit(16)? as u8;
512 Some(d << 4 | d)
513 };
514 Some(Self::rgb(dup(b[0])?, dup(b[1])?, dup(b[2])?))
515 }
516 6 => Some(Self::rgb(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?)),
517 8 => Some(Self::rgba(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?, n(&h[6..8])?)),
518 _ => None,
519 }
520 }
521}
522
523#[derive(Clone, Copy, Debug, PartialEq)]
525pub enum Length {
526 Px(f32),
528 Percent(f32),
530}
531
532#[derive(Clone, Debug)]
534pub enum ImageSource {
535 Bytes(Vec<u8>),
537 Path(PathBuf),
539 Named(String),
541}