Skip to main content

leaf_core/
style.rs

1//! A toolkit-neutral text style — the seam that lets one document model drive
2//! any frontend.
3//!
4//! The WYSIWYG builder ([`crate::wysiwyg`]) tags each rendered glyph with one of
5//! these instead of a `ratatui::Style` or a `gpui::TextStyle`, so the caret
6//! model and the AST→glyph layout stay free of any GUI/TUI dependency.
7//!
8//! What core records is *what a glyph is*, never *what color to paint it*: a
9//! [`Role`] (heading, code, link, a list bullet, …) plus the portable emphasis
10//! the author actually wrote (`**bold**`, `*em*`, `{+ins+}`, `{-del-}`). Palette
11//! is presentation, and presentation belongs to the frontend — a terminal tells
12//! a heading from body text by color because color is all it can vary, while a
13//! GUI varies size and font instead. So each frontend maps a [`Role`] to its own
14//! look: `leaf-tui` turns it into terminal colors, `leaf-gpui` into an `Hsla`
15//! plus a font size and family. Core stays out of that argument.
16
17/// What a glyph *is*, typographically — the semantic role a frontend maps to its
18/// own presentation. Mutually exclusive per glyph (a glyph is a heading, or a
19/// link, or body text — not two at once); the compositional emphasis a run can
20/// also carry lives in [`Style`]'s `bold`/`italic`/`underline`/`strikethrough`
21/// flags alongside this.
22#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
23pub enum Role {
24    /// Ordinary prose — the surface's default text.
25    #[default]
26    Body,
27    /// A heading of the given level (1 = top). A GUI scales the font by level; a
28    /// terminal cycles a color by it.
29    Heading(u8),
30    /// Code — inline `` `verbatim` `` or a fenced block. A GUI renders it in a
31    /// monospace family; a terminal tints it.
32    Code,
33    /// A hyperlink's visible text (or bare URL/email).
34    Link,
35    /// Highlighted / marked text (`==mark==`).
36    Mark,
37    /// A list item's bullet or number — synthetic decoration, not authored text.
38    ListMarker,
39    /// A block quote's gutter (`│`), drawn down its left edge.
40    QuoteGutter,
41    /// A drawn rule: a thematic break (`───`) or a table's borders. A GUI that
42    /// draws its own tables ignores the border glyphs; the rule still reaches it.
43    Rule,
44    /// Raw markup a revealed line is showing: the `*` around an emphasis, the
45    /// `# ` opening a heading, a link's `](dest)`. Only ever emitted for the
46    /// caret's line under [`MarkupMode::Full`](crate::MarkupMode::Full) —
47    /// every other line resolves its markup away and has none of these.
48    ///
49    /// A role rather than a `Style` flag because it is what the glyph *is*: the
50    /// delimiter of an emphasis is not itself emphasised text. A frontend
51    /// typically dims it, so the revealed line still reads as prose with its
52    /// scaffolding visible rather than as source code. One that doesn't map it
53    /// draws it as body text, which is correct if unsubtle.
54    Delimiter,
55    /// A block-level image's placeholder text (`🖼 alt`). The glyphs are a
56    /// *default* rendering any surface can paint as-is (a terminal shows the
57    /// label); an image-capable frontend skips the placeholder row named by the
58    /// map's [`ImageInfo`](crate::wysiwyg::ImageInfo) `rows_span` and paints the
59    /// real picture in its place — the same skip-the-picture contract
60    /// [`Role::Rule`] table borders use.
61    Image,
62}
63
64/// Which line a glyph sits on relative to the text around it.
65///
66/// Not a [`Role`], because a raised glyph keeps whatever it already was — the
67/// `1` of a footnote reference is still a link, an author's `^2^` inside a
68/// heading is still heading text. And not one of [`Style`]'s `bool` flags,
69/// because unlike bold-and-italic these do not compose: a glyph is raised, or
70/// lowered, or neither, and two flags would let a caller ask for both.
71///
72/// A frontend that ignores this draws every glyph on the normal baseline, which
73/// is what every frontend did before the variant existed.
74#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
75pub enum Baseline {
76    /// The ordinary text baseline.
77    #[default]
78    Normal,
79    /// Raised and typically drawn smaller — an author's `^x^`, and the label of
80    /// a footnote reference.
81    Super,
82    /// Lowered and typically drawn smaller — an author's `~x~`.
83    Sub,
84}
85
86/// A glyph's style: a typographic [`Role`] plus the compositional emphasis flags
87/// the author wrote. Deliberately *no* color — that is a frontend's call, keyed
88/// on the [`Role`]. Builder methods (`.bold`, `.italic`, …) mirror the shape of
89/// ratatui's `Style` so the WYSIWYG builder reads the same as it did before the
90/// split.
91#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
92pub struct Style {
93    pub bold: bool,
94    pub italic: bool,
95    pub underline: bool,
96    pub strikethrough: bool,
97    /// The typographic role — [`Role::Body`] for ordinary text.
98    pub role: Role,
99    /// Which line the glyph sits on — [`Baseline::Normal`] for ordinary text.
100    pub baseline: Baseline,
101}
102
103impl Style {
104    pub const fn bold(mut self) -> Self {
105        self.bold = true;
106        self
107    }
108
109    pub const fn italic(mut self) -> Self {
110        self.italic = true;
111        self
112    }
113
114    pub const fn underline(mut self) -> Self {
115        self.underline = true;
116        self
117    }
118
119    pub const fn strikethrough(mut self) -> Self {
120        self.strikethrough = true;
121        self
122    }
123
124    pub const fn role(mut self, r: Role) -> Self {
125        self.role = r;
126        self
127    }
128
129    pub const fn baseline(mut self, b: Baseline) -> Self {
130        self.baseline = b;
131        self
132    }
133}