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 {
334 text: text.into(),
335 anchor: Anchor::BottomRight,
336 color: Color::rgba(255, 255, 255, 102),
337 size: 0.9,
338 }
339 }
340}
341
342#[derive(Clone, Debug)]
344pub enum Inline {
345 Text {
347 text: String,
349 style: TextStyle,
351 },
352 Code(String),
354 LineBreak,
356}
357
358#[derive(Clone, Debug, PartialEq)]
360pub struct TextStyle {
361 pub weight: Option<u16>,
364 pub italic: bool,
366 pub underline: bool,
368 pub strike: bool,
370 pub color: Option<Color>,
372 pub highlight: Option<Highlight>,
374 pub size: f32,
376 pub font: FontRole,
378 pub link: bool,
381 pub shadow: Option<Shadow>,
383 pub ring: Option<RingMark>,
386 pub dot: Option<DotMark>,
388 pub aside: Option<AsideSide>,
393}
394
395#[derive(Clone, Copy, Debug, PartialEq, Eq)]
397pub enum AsideSide {
398 Left,
400 Right,
402}
403
404#[derive(Clone, Copy, Debug, Default, PartialEq)]
406pub struct RingMark {
407 pub color: Option<Color>,
409 pub rx: Option<f32>,
411 pub ry: Option<f32>,
413 pub width: Option<f32>,
415 pub each: bool,
418}
419
420#[derive(Clone, Copy, Debug, Default, PartialEq)]
422pub struct DotMark {
423 pub color: Option<Color>,
425 pub radius: Option<f32>,
427 pub each: bool,
429}
430
431impl Default for TextStyle {
432 fn default() -> Self {
433 Self {
434 weight: None,
435 italic: false,
436 underline: false,
437 strike: false,
438 color: None,
439 highlight: None,
440 size: 1.0,
441 font: FontRole::Sans,
442 link: false,
443 shadow: None,
444 ring: None,
445 dot: None,
446 aside: None,
447 }
448 }
449}
450
451#[derive(Clone, Copy, Debug, PartialEq)]
454pub struct Shadow {
455 pub dx: f32,
457 pub dy: f32,
459 pub blur: f32,
461 pub color: Color,
463}
464
465impl Default for Shadow {
466 fn default() -> Self {
467 Self { dx: 0.0, dy: 2.0, blur: 6.0, color: Color::rgba(0, 0, 0, 64) }
469 }
470}
471
472#[derive(Clone, Copy, Debug, PartialEq, Eq)]
474pub enum Highlight {
475 Theme,
477 Custom(Color),
479}
480
481#[derive(Clone, Debug, PartialEq)]
483pub enum FontRole {
484 Sans,
486 Serif,
488 Mono,
490 Kai,
492 Named(String),
494}
495
496#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
498pub enum Align {
499 #[default]
501 Left,
502 Center,
504 Right,
506 Justify,
508}
509
510#[derive(Clone, Copy, Debug, PartialEq, Eq)]
512pub struct Color {
513 pub r: u8,
515 pub g: u8,
517 pub b: u8,
519 pub a: u8,
521}
522
523impl Color {
524 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
526 Self { r, g, b, a: 255 }
527 }
528
529 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
531 Self { r, g, b, a }
532 }
533
534 pub fn hex(s: &str) -> Option<Self> {
537 let h = s.strip_prefix('#').unwrap_or(s);
538 if !h.bytes().all(|b| b.is_ascii_hexdigit()) {
539 return None;
540 }
541 let n = |slice: &str| u8::from_str_radix(slice, 16).ok();
542 match h.len() {
543 3 => {
544 let b = h.as_bytes();
545 let dup = |c: u8| {
546 let d = (c as char).to_digit(16)? as u8;
547 Some(d << 4 | d)
548 };
549 Some(Self::rgb(dup(b[0])?, dup(b[1])?, dup(b[2])?))
550 }
551 6 => Some(Self::rgb(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?)),
552 8 => Some(Self::rgba(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?, n(&h[6..8])?)),
553 _ => None,
554 }
555 }
556}
557
558#[derive(Clone, Copy, Debug, PartialEq)]
560pub enum Length {
561 Px(f32),
563 Percent(f32),
565}
566
567#[derive(Clone, Debug)]
569pub enum ImageSource {
570 Bytes(Vec<u8>),
572 Path(PathBuf),
574 Named(String),
576}