mod block;
mod model;
mod table;
mod wrap;
pub use model::{
CodeLayout, LayoutBlock, LayoutDocument, LayoutLine, LayoutSpan, LinkTarget, RuleLayout,
SemanticStyle, TableLayout, TextBlock,
};
pub use table::TableLayoutEngine;
use thiserror::Error;
use mdsee_core::Document;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LayoutOptions {
pub terminal_width: u16,
pub max_width: u16,
pub margin: u16,
}
impl Default for LayoutOptions {
fn default() -> Self {
Self {
terminal_width: 80,
max_width: 100,
margin: 2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LayoutContext {
pub terminal_width: u16,
pub content_width: u16,
}
impl LayoutContext {
pub fn from_options(options: &LayoutOptions) -> Self {
let inner = options
.terminal_width
.saturating_sub(options.margin.saturating_mul(2));
let content_width = inner.min(options.max_width).max(1);
Self {
terminal_width: options.terminal_width,
content_width,
}
}
}
#[derive(Debug, Error)]
pub enum LayoutError {
#[error("layout failed")]
LayoutFailed,
}
pub fn layout(document: &Document, options: &LayoutOptions) -> Result<LayoutDocument, LayoutError> {
let ctx = LayoutContext::from_options(options);
let blocks = document
.blocks
.iter()
.map(|block| block::layout_block(block, &ctx))
.collect();
Ok(LayoutDocument { blocks })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn content_width_is_min_of_inner_width_and_max_width() {
let ctx = LayoutContext::from_options(&LayoutOptions {
terminal_width: 80,
max_width: 100,
margin: 2,
});
assert_eq!(ctx.content_width, 76);
let ctx = LayoutContext::from_options(&LayoutOptions {
terminal_width: 120,
max_width: 100,
margin: 2,
});
assert_eq!(ctx.content_width, 100);
}
#[test]
fn content_width_never_drops_below_one() {
let ctx = LayoutContext::from_options(&LayoutOptions {
terminal_width: 0,
max_width: 100,
margin: 2,
});
assert_eq!(ctx.content_width, 1);
}
}