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 pub align: Align,
111}
112
113impl Default for TableStyle {
114 fn default() -> Self {
115 Self {
116 pad_x: None,
117 pad_y: None,
118 grid: TableGrid::default(),
119 header_fill: true,
120 expand: false,
121 align: Align::Left,
122 }
123 }
124}
125
126#[derive(Clone, Copy, Debug)]
128pub struct TableGrid {
129 pub outer: bool,
131 pub vertical: bool,
133 pub horizontal: bool,
135}
136
137impl Default for TableGrid {
138 fn default() -> Self {
139 Self { outer: true, vertical: true, horizontal: true }
140 }
141}
142
143#[derive(Clone, Debug)]
145pub struct ColSpec {
146 pub align: Align,
148 pub width: Option<Length>,
150}
151
152impl Default for ColSpec {
153 fn default() -> Self {
154 Self { align: Align::Left, width: None }
155 }
156}
157
158#[derive(Clone, Debug)]
160pub struct Cell {
161 pub inlines: Vec<Inline>,
163 pub bg: Option<Color>,
165}
166
167#[derive(Clone, Debug)]
169pub struct Columns {
170 pub cols: Vec<Column>,
172 pub gap: Option<f32>,
174}
175
176#[derive(Clone, Debug)]
178pub struct Column {
179 pub blocks: Vec<Block>,
181 pub weight: f32,
183}
184
185#[derive(Clone, Debug)]
187pub struct Progress {
188 pub value: f32,
190 pub height: f32,
192 pub fill: Option<Color>,
194 pub track: Option<Color>,
196 pub radius: Option<f32>,
198 pub width: Option<Length>,
200 pub align: Align,
202}
203
204#[derive(Clone, Debug)]
206pub struct List {
207 pub kind: ListKind,
209 pub start: u32,
211 pub items: Vec<ListItem>,
213}
214
215#[derive(Clone, Copy, Debug, PartialEq, Eq)]
217pub enum ListKind {
218 Unordered,
220 Ordered,
222}
223
224#[derive(Clone, Debug)]
226pub struct ListItem {
227 pub blocks: Vec<Block>,
229 pub check: Option<bool>,
232}
233
234#[derive(Clone, Debug)]
236pub struct BlockImage {
237 pub src: ImageSource,
239 pub width: Option<Length>,
241 pub align: Align,
243 pub caption: Option<Vec<Inline>>,
245 pub decor: ImageDecor,
247}
248
249#[derive(Clone, Debug, Default)]
251pub struct ImageDecor {
252 pub badge: Option<Badge>,
254 pub border: Option<ImageBorder>,
256 pub watermark: Option<Watermark>,
258 pub radius: f32,
260 pub shadow: Option<Shadow>,
262}
263
264#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
266pub enum Anchor {
267 TopLeft,
269 #[default]
271 TopRight,
272 BottomLeft,
274 BottomRight,
276 Center,
278}
279
280#[derive(Clone, Debug)]
282pub struct Badge {
283 pub text: String,
285 pub anchor: Anchor,
287 pub bg: Color,
289 pub fg: Color,
291 pub size: f32,
293}
294
295impl Badge {
296 pub fn new(text: impl Into<String>) -> Self {
298 Self {
299 text: text.into(),
300 anchor: Anchor::TopRight,
301 bg: Color::rgba(0, 0, 0, 184),
302 fg: Color::rgb(255, 255, 255),
303 size: 0.75,
304 }
305 }
306}
307
308#[derive(Clone, Copy, Debug, PartialEq)]
310pub struct ImageBorder {
311 pub width: f32,
313 pub color: Color,
315}
316
317#[derive(Clone, Debug)]
319pub struct Watermark {
320 pub text: String,
322 pub anchor: Anchor,
324 pub color: Color,
326 pub size: f32,
328}
329
330impl Watermark {
331 pub fn new(text: impl Into<String>) -> Self {
333 Self { text: text.into(), anchor: Anchor::BottomRight, color: Color::rgba(255, 255, 255, 102), size: 0.9 }
334 }
335}
336
337#[derive(Clone, Debug)]
339pub enum Inline {
340 Text {
342 text: String,
344 style: TextStyle,
346 },
347 Code(String),
349 LineBreak,
351}
352
353#[derive(Clone, Debug, PartialEq)]
355pub struct TextStyle {
356 pub weight: Option<u16>,
359 pub italic: bool,
361 pub underline: bool,
363 pub strike: bool,
365 pub color: Option<Color>,
367 pub highlight: Option<Highlight>,
369 pub size: f32,
371 pub font: FontRole,
373 pub link: bool,
376 pub shadow: Option<Shadow>,
378 pub ring: Option<RingMark>,
381 pub dot: Option<DotMark>,
383 pub aside: Option<AsideSide>,
388}
389
390#[derive(Clone, Copy, Debug, PartialEq, Eq)]
392pub enum AsideSide {
393 Left,
395 Right,
397}
398
399#[derive(Clone, Copy, Debug, Default, PartialEq)]
401pub struct RingMark {
402 pub color: Option<Color>,
404 pub rx: Option<f32>,
406 pub ry: Option<f32>,
408 pub width: Option<f32>,
410 pub each: bool,
413}
414
415#[derive(Clone, Copy, Debug, Default, PartialEq)]
417pub struct DotMark {
418 pub color: Option<Color>,
420 pub radius: Option<f32>,
422 pub each: bool,
424}
425
426impl Default for TextStyle {
427 fn default() -> Self {
428 Self {
429 weight: None,
430 italic: false,
431 underline: false,
432 strike: false,
433 color: None,
434 highlight: None,
435 size: 1.0,
436 font: FontRole::Sans,
437 link: false,
438 shadow: None,
439 ring: None,
440 dot: None,
441 aside: None,
442 }
443 }
444}
445
446#[derive(Clone, Copy, Debug, PartialEq)]
449pub struct Shadow {
450 pub dx: f32,
452 pub dy: f32,
454 pub blur: f32,
456 pub color: Color,
458}
459
460impl Default for Shadow {
461 fn default() -> Self {
462 Self { dx: 0.0, dy: 2.0, blur: 6.0, color: Color::rgba(0, 0, 0, 64) }
464 }
465}
466
467#[derive(Clone, Copy, Debug, PartialEq, Eq)]
469pub enum Highlight {
470 Theme,
472 Custom(Color),
474}
475
476#[derive(Clone, Debug, PartialEq)]
478pub enum FontRole {
479 Sans,
481 Serif,
483 Mono,
485 Kai,
487 Named(String),
489}
490
491#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
493pub enum Align {
494 #[default]
496 Left,
497 Center,
499 Right,
501 Justify,
503}
504
505#[derive(Clone, Copy, Debug, PartialEq, Eq)]
507pub struct Color {
508 pub r: u8,
510 pub g: u8,
512 pub b: u8,
514 pub a: u8,
516}
517
518impl Color {
519 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
521 Self { r, g, b, a: 255 }
522 }
523
524 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
526 Self { r, g, b, a }
527 }
528
529 pub fn hex(s: &str) -> Option<Self> {
532 let h = s.strip_prefix('#').unwrap_or(s);
533 if !h.bytes().all(|b| b.is_ascii_hexdigit()) {
534 return None;
535 }
536 let n = |slice: &str| u8::from_str_radix(slice, 16).ok();
537 match h.len() {
538 3 => {
539 let b = h.as_bytes();
540 let dup = |c: u8| {
541 let d = (c as char).to_digit(16)? as u8;
542 Some(d << 4 | d)
543 };
544 Some(Self::rgb(dup(b[0])?, dup(b[1])?, dup(b[2])?))
545 }
546 6 => Some(Self::rgb(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?)),
547 8 => Some(Self::rgba(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?, n(&h[6..8])?)),
548 _ => None,
549 }
550 }
551}
552
553#[derive(Clone, Copy, Debug, PartialEq)]
555pub enum Length {
556 Px(f32),
558 Percent(f32),
560}
561
562#[derive(Clone, Debug)]
564pub enum ImageSource {
565 Bytes(Vec<u8>),
567 Path(PathBuf),
569 Named(String),
571}