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}
53
54#[derive(Clone, Debug)]
56pub struct Table {
57 pub header: Option<Vec<Cell>>,
59 pub rows: Vec<Vec<Cell>>,
61 pub cols: Vec<ColSpec>,
63 pub style: TableStyle,
65}
66
67#[derive(Clone, Debug)]
69pub struct TableStyle {
70 pub pad_x: Option<f32>,
72 pub pad_y: Option<f32>,
74 pub grid: TableGrid,
76 pub header_fill: bool,
78 pub expand: bool,
81}
82
83impl Default for TableStyle {
84 fn default() -> Self {
85 Self { pad_x: None, pad_y: None, grid: TableGrid::default(), header_fill: true, expand: false }
86 }
87}
88
89#[derive(Clone, Copy, Debug)]
91pub struct TableGrid {
92 pub outer: bool,
94 pub vertical: bool,
96 pub horizontal: bool,
98}
99
100impl Default for TableGrid {
101 fn default() -> Self {
102 Self { outer: true, vertical: true, horizontal: true }
103 }
104}
105
106#[derive(Clone, Debug)]
108pub struct ColSpec {
109 pub align: Align,
111 pub width: Option<Length>,
113}
114
115impl Default for ColSpec {
116 fn default() -> Self {
117 Self { align: Align::Left, width: None }
118 }
119}
120
121#[derive(Clone, Debug)]
123pub struct Cell {
124 pub inlines: Vec<Inline>,
126 pub bg: Option<Color>,
128}
129
130#[derive(Clone, Debug)]
132pub struct Columns {
133 pub cols: Vec<Column>,
135 pub gap: Option<f32>,
137}
138
139#[derive(Clone, Debug)]
141pub struct Column {
142 pub blocks: Vec<Block>,
144 pub weight: f32,
146}
147
148#[derive(Clone, Debug)]
150pub struct List {
151 pub kind: ListKind,
153 pub start: u32,
155 pub items: Vec<ListItem>,
157}
158
159#[derive(Clone, Copy, Debug, PartialEq, Eq)]
161pub enum ListKind {
162 Unordered,
164 Ordered,
166}
167
168#[derive(Clone, Debug)]
170pub struct ListItem {
171 pub blocks: Vec<Block>,
173 pub check: Option<bool>,
176}
177
178#[derive(Clone, Debug)]
180pub struct BlockImage {
181 pub src: ImageSource,
183 pub width: Option<Length>,
185 pub align: Align,
187 pub caption: Option<Vec<Inline>>,
189 pub decor: ImageDecor,
191}
192
193#[derive(Clone, Debug, Default)]
195pub struct ImageDecor {
196 pub badge: Option<Badge>,
198 pub border: Option<ImageBorder>,
200 pub watermark: Option<Watermark>,
202 pub radius: f32,
204 pub shadow: Option<Shadow>,
206}
207
208#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
210pub enum Anchor {
211 TopLeft,
213 #[default]
215 TopRight,
216 BottomLeft,
218 BottomRight,
220 Center,
222}
223
224#[derive(Clone, Debug)]
226pub struct Badge {
227 pub text: String,
229 pub anchor: Anchor,
231 pub bg: Color,
233 pub fg: Color,
235 pub size: f32,
237}
238
239impl Badge {
240 pub fn new(text: impl Into<String>) -> Self {
242 Self {
243 text: text.into(),
244 anchor: Anchor::TopRight,
245 bg: Color::rgba(0, 0, 0, 184),
246 fg: Color::rgb(255, 255, 255),
247 size: 0.75,
248 }
249 }
250}
251
252#[derive(Clone, Copy, Debug, PartialEq)]
254pub struct ImageBorder {
255 pub width: f32,
257 pub color: Color,
259}
260
261#[derive(Clone, Debug)]
263pub struct Watermark {
264 pub text: String,
266 pub anchor: Anchor,
268 pub color: Color,
270 pub size: f32,
272}
273
274impl Watermark {
275 pub fn new(text: impl Into<String>) -> Self {
277 Self {
278 text: text.into(),
279 anchor: Anchor::BottomRight,
280 color: Color::rgba(255, 255, 255, 102),
281 size: 0.9,
282 }
283 }
284}
285
286#[derive(Clone, Debug)]
288pub enum Inline {
289 Text {
291 text: String,
293 style: TextStyle,
295 },
296 Code(String),
298 LineBreak,
300}
301
302#[derive(Clone, Debug, PartialEq)]
304pub struct TextStyle {
305 pub weight: Option<u16>,
308 pub italic: bool,
310 pub underline: bool,
312 pub strike: bool,
314 pub color: Option<Color>,
316 pub highlight: Option<Highlight>,
318 pub size: f32,
320 pub font: FontRole,
322 pub link: bool,
325 pub shadow: Option<Shadow>,
327}
328
329impl Default for TextStyle {
330 fn default() -> Self {
331 Self {
332 weight: None,
333 italic: false,
334 underline: false,
335 strike: false,
336 color: None,
337 highlight: None,
338 size: 1.0,
339 font: FontRole::Sans,
340 link: false,
341 shadow: None,
342 }
343 }
344}
345
346#[derive(Clone, Copy, Debug, PartialEq)]
349pub struct Shadow {
350 pub dx: f32,
352 pub dy: f32,
354 pub blur: f32,
356 pub color: Color,
358}
359
360impl Default for Shadow {
361 fn default() -> Self {
362 Self { dx: 0.0, dy: 2.0, blur: 6.0, color: Color::rgba(0, 0, 0, 64) }
364 }
365}
366
367#[derive(Clone, Copy, Debug, PartialEq, Eq)]
369pub enum Highlight {
370 Theme,
372 Custom(Color),
374}
375
376#[derive(Clone, Debug, PartialEq)]
378pub enum FontRole {
379 Sans,
381 Serif,
383 Mono,
385 Kai,
387 Named(String),
389}
390
391#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
393pub enum Align {
394 #[default]
396 Left,
397 Center,
399 Right,
401 Justify,
403}
404
405#[derive(Clone, Copy, Debug, PartialEq, Eq)]
407pub struct Color {
408 pub r: u8,
410 pub g: u8,
412 pub b: u8,
414 pub a: u8,
416}
417
418impl Color {
419 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
421 Self { r, g, b, a: 255 }
422 }
423
424 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
426 Self { r, g, b, a }
427 }
428
429 pub fn hex(s: &str) -> Option<Self> {
432 let h = s.strip_prefix('#').unwrap_or(s);
433 if !h.bytes().all(|b| b.is_ascii_hexdigit()) {
434 return None;
435 }
436 let n = |slice: &str| u8::from_str_radix(slice, 16).ok();
437 match h.len() {
438 3 => {
439 let b = h.as_bytes();
440 let dup = |c: u8| {
441 let d = (c as char).to_digit(16)? as u8;
442 Some(d << 4 | d)
443 };
444 Some(Self::rgb(dup(b[0])?, dup(b[1])?, dup(b[2])?))
445 }
446 6 => Some(Self::rgb(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?)),
447 8 => Some(Self::rgba(n(&h[0..2])?, n(&h[2..4])?, n(&h[4..6])?, n(&h[6..8])?)),
448 _ => None,
449 }
450 }
451}
452
453#[derive(Clone, Copy, Debug, PartialEq)]
455pub enum Length {
456 Px(f32),
458 Percent(f32),
460}
461
462#[derive(Clone, Debug)]
464pub enum ImageSource {
465 Bytes(Vec<u8>),
467 Path(PathBuf),
469 Named(String),
471}