//! The document's internal shape: one block per heading, paragraph, code //! block, list item, quoted paragraph, or thematic break. //! //! **This never crosses the `.gdc` boundary.** A `.gdc` writes //! `(markdown :source page-text)` and the string is the whole interface; that //! the widget turns it into these is the widget's business, exactly as it is //! the button's business that its label becomes a parley layout. A document is //! not a declaration. //! //! A block is one [`Paragraph`] (two, for a list item: the marker is laid out //! separately so it never wraps into the body), because a `Widget` cannot own //! child widgets — the tree owns children — so the document has to measure and //! paint itself. That is fine, and it is the reason `Paragraph` is a //! `guiduck-core` type rather than something inside `Text`. use guiduck_core::graphic::Graphic; use guiduck_core::text::Paragraph; use guiduck_scene::geom::Point; /// What a block *is*, which is the only thing that decides how it is dressed /// and placed. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BlockKind { /// `# …` — the level, 1–6. Heading(u8), Paragraph, /// A fenced or indented code block: monospaced, on its own background. Code, /// One item of a bulleted or numbered list. Carries a marker paragraph. ListItem, /// `---`. The one block with nothing to say, so it has no paragraph. Rule, /// A pipe table. Its cells are in [`Block::table`], not the single body. Table, /// `![alt](src)` alone on a line. Its content is in [`Block::image`]. Image, } /// A block-level image: an `![alt](src)` that is the whole of its paragraph. /// /// The `src` is opaque to the widget — an application-supplied resolver turns it /// into pixels, exactly as a link handler turns a target string into a /// navigation. Until (or unless) it resolves, the alt text stands in, so a /// document with an unresolvable image still reads. pub struct ImageContent { pub src: String, /// The alt text, laid out as the fallback shown when `graphic` is `None`. pub alt: Paragraph, /// The decoded image the resolver supplied, if any. pub graphic: Option, /// The resolver reported the image as still loading (`DeferredImage`): the /// alt text stands in for now, and the widget keeps asking until it /// resolves or is declined. pub deferred: bool, } /// A column's horizontal alignment, from the table's delimiter row. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CellAlign { Start, Center, End, } /// One cell of a table: its prose, and where the prose landed once the grid was /// placed. pub struct TableCell { pub body: Paragraph, /// The cell content's top-left in the widget's coordinates, filled by /// layout. Aligned within the column, so it is not simply the column's /// left edge. pub origin: Point, } /// A table: a grid of [`TableCell`]s, the header rows first, plus the geometry /// the layout pass resolves. /// /// Rows are padded to the column count, so `rows[r][c]` is always valid for /// `c < aligns.len()` — a ragged row from a short source line is completed with /// empty cells rather than special-cased at every use. pub struct Table { /// Per-column alignment; `aligns.len()` is the column count. pub aligns: Vec, /// Every row, the first `header_rows` of them the header. pub rows: Vec>, /// How many leading rows are the header (1 for a CommonMark table). pub header_rows: usize, /// Column boundaries in widget coordinates: `col_edges[c]..col_edges[c + 1]` /// is column `c`'s box, borders included. Length is the column count + 1. pub col_edges: Vec, /// Row boundaries in widget coordinates, the same shape as /// [`col_edges`](Self::col_edges) down the other axis. pub row_edges: Vec, } impl Table { pub fn columns(&self) -> usize { self.aligns.len() } } pub struct Block { pub kind: BlockKind, /// How many block quotes enclose this block; 0 is unquoted. A count rather /// than nesting, because what it buys — one rule per level down the left, /// and the content pushed in that far — is a function of the number alone. pub quote_depth: u8, /// How many lists enclose this block; 0 is not in a list. The item's own /// level is included, so an item at depth 1 has its body one /// `list-indent` in and its marker at the enclosing left edge. pub list_depth: usize, /// A list item's `•` or `3.`, laid out on its own so a long body never /// wraps underneath it and a wide marker never squeezes it. pub marker: Option, /// The block's prose. A [`BlockKind::Rule`] and a [`BlockKind::Table`] have /// none — a table's text is in its cells. pub body: Option, /// The grid, for a [`BlockKind::Table`]. pub table: Option, /// The picture, for a [`BlockKind::Image`]. pub image: Option, /// Where the body's top-left landed in the widget's coordinates. Filled by /// the widget's layout pass; meaningless before it. pub body_origin: Point, /// Where the marker's top-left landed. pub marker_origin: Point, /// The block's own vertical extent — the padded box for a code block, the /// rule's thickness for a break — which is what the quote bars and the code /// background are drawn against and what hit testing narrows on. pub top: f64, pub height: f64, /// The block's left edge: quote and list nesting, resolved to pixels. pub indent: f64, } impl Block { pub fn new( kind: BlockKind, quote_depth: u8, list_depth: usize, body: Option, ) -> Self { Self { kind, quote_depth, list_depth, marker: None, body, table: None, image: None, body_origin: Point::ORIGIN, marker_origin: Point::ORIGIN, top: 0.0, height: 0.0, indent: 0.0, } } /// Whether `y`, in the widget's coordinates, is within this block. pub fn contains_y(&self, y: f64) -> bool { y >= self.top && y < self.top + self.height } }