use ratatui_core::layout::Rect;
use ratatui_core::text::Line;
use crate::components::Image;
use crate::components::text::line_width;
use crate::geometry::Size;
use crate::highlight::{CodeHighlighter, Highlighter};
use crate::style::{StyleSheet, Theme};
use crate::surface::Surface;
use crate::term::hyperlink::{BufferLink, LinkPolicy, apply_buffer_links};
use crate::term::image::{ImageLayer, ImageSupport};
use crate::view::{RenderCtx, View};
use super::FencedBlockRenderer;
use super::flatten::flatten_linked_into;
use super::image::{ImageResolver, MarkdownImage};
use super::parse::parse_with;
pub struct Markdown<'a> {
source: String,
highlighter: CodeHighlighter<'a>,
block_renderer: Option<&'a dyn FencedBlockRenderer>,
resolver: Option<&'a dyn ImageResolver>,
image_support: ImageSupport,
image_layer: Option<ImageLayer>,
link_policy: LinkPolicy,
}
impl<'a> Markdown<'a> {
pub fn new(source: impl Into<String>) -> Self {
Self {
source: source.into(),
highlighter: CodeHighlighter::Plain,
block_renderer: None,
resolver: None,
image_support: ImageSupport::None,
image_layer: None,
link_policy: LinkPolicy::default(),
}
}
pub fn highlighter(mut self, highlighter: &'a dyn Highlighter) -> Self {
self.highlighter = CodeHighlighter::With(highlighter);
self
}
pub fn block_renderer(mut self, renderer: &'a dyn FencedBlockRenderer) -> Self {
self.block_renderer = Some(renderer);
self
}
pub fn link_policy(mut self, policy: LinkPolicy) -> Self {
self.link_policy = policy;
self
}
pub fn images(
mut self,
resolver: &'a dyn ImageResolver,
support: ImageSupport,
layer: &ImageLayer,
) -> Self {
self.resolver = Some(resolver);
self.image_support = support;
self.image_layer = Some(layer.clone());
self
}
fn lines_images_links(
&self,
width: u16,
theme: &Theme,
sheet: &StyleSheet,
) -> (Vec<Line<'static>>, Vec<MarkdownImage>, Vec<BufferLink>) {
let items = parse_with(&self.source, theme, sheet, self.highlighter, self.resolver);
let mut images = Vec::new();
let (lines, links) =
flatten_linked_into(&items, width, theme, self.block_renderer, &mut images);
(lines, images, links)
}
}
impl View for Markdown<'_> {
fn measure(&self, available: Size) -> Size {
let (lines, _, _) =
self.lines_images_links(available.width, &Theme::default(), &StyleSheet::default());
let width = lines.iter().map(line_width).max().unwrap_or(0);
Size::new(width.min(available.width), lines.len() as u16)
}
fn render(&self, area: Rect, surface: &mut Surface, ctx: &RenderCtx) {
let (lines, images, links) = self.lines_images_links(area.width, ctx.theme, &ctx.sheet);
for (row, line) in lines.iter().enumerate() {
let y = area.y.saturating_add(row as u16);
if y >= area.bottom() {
break;
}
let mut x = area.x;
for span in &line.spans {
if x >= area.right() {
break;
}
x = surface.set_string(x, y, span.content.as_ref(), span.style);
}
}
apply_buffer_links(
surface.buffer_mut(),
ratatui_core::layout::Position {
x: area.x,
y: area.y,
},
&links,
self.link_policy,
);
for img in images {
let y = area.y.saturating_add(img.row);
let x = area.x.saturating_add(img.indent);
if y >= area.bottom() || x >= area.right() {
continue;
}
let rect = Rect {
x,
y,
width: img.cols.min(area.right() - x),
height: img.rows.min(area.bottom() - y),
};
let mut image = Image::new(img.data, img.cols, img.rows)
.support(self.image_support)
.alt(img.alt);
if let Some(layer) = &self.image_layer {
image = image.in_layer(layer);
}
image.render(rect, surface, ctx);
}
}
}