1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
// SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: Apache-2.0 or MIT
//! A renderer for HTML documents.
//!
//! *Requires the `html` feature (enabled per default).*
//!
//! This module provides the [`Renderer`][] struct, a renderer that uses [`html2text`][] to render
//! HTML documents. You can custommize the renderer by settting a [`TextDecorator`][] and a
//! [`Converter`][]. The [`TextDecorator`][] is used by [`html2text`][] to convert the HTML DOM
//! into annotated strings. The [`Converter`][] is used by the renderer to interpret the
//! annotations and to extract the text format and links.
//!
//! [`html2text`]: https://docs.rs/html2text/latest/html2text/
//! [`TextDecorator`]: https://docs.rs/html2text/latest/html2text/render/text_renderer/trait.TextDecorator.html
//! [`Renderer`]: struct.Renderer.html
//! [`Converter`]: trait.Converter.html
use cursive_core::theme;
use html2text::render::text_renderer;
use crate::{Element, RenderedDocument};
/// A renderer for HTML documents that uses the default rich text decorator and converter.
pub type RichRenderer = Renderer<text_renderer::RichDecorator, RichConverter>;
/// A renderer for HTML documents.
///
/// This renderer uses [`html2text`][] to parse and render an HTML document. The provided document
/// is only parsed once: when the instance is constructed. Then it is rendered every time the
/// width of the view changes.
///
/// You can custommize the renderer by settting a custom [`TextDecorator`][] and [`Converter`][].
/// The [`TextDecorator`][] is used by [`html2text`][] to convert the HTML DOM into annotated
/// strings. The [`Converter`][] is used by the renderer to interpret the annotations and to
/// extract the text format and links.
///
/// Per default, the renderer uses the [`RichDecorator`][] and the [`RichConverter`][].
///
/// [`html2text`]: https://docs.rs/html2text/latest/html2text/
/// [`TextDecorator`]: https://docs.rs/html2text/latest/html2text/render/text_renderer/trait.TextDecorator.html
/// [`RichDecorator`]: https://docs.rs/html2text/latest/html2text/render/text_renderer/trait.RichDecorator.html
/// [`Converter`]: trait.Converter.html
/// [`RichConverter`]: trait.RichConverter.html
pub struct Renderer<D: text_renderer::TextDecorator + Clone, C: Converter<D::Annotation>> {
render_tree: html2text::RenderTree,
decorator: D,
converter: C,
}
/// A converter for HTML annotations.
///
/// This trait extracts the text formatting and links from the annotations created by a
/// [`TextDecorator`][].
///
/// [`TextDecorator`]: https://docs.rs/html2text/latest/html2text/render/text_renderer/trait.TextDecorator.html
pub trait Converter<A> {
/// Returns the style for the given annotation (if any).
fn get_style(&self, annotation: &A) -> Option<theme::Style>;
/// Returns the link target for the given annotation (if any).
fn get_link<'a>(&self, annotation: &'a A) -> Option<&'a str>;
}
/// A converter for [`RichAnnotation`][].
///
/// Besides the straightforward mappings of links and text effects, this converter styles links
/// with the underline effect and code snippets with the secondary palette color.
///
/// [`RichAnnotation`]: https://docs.rs/html2text/latest/html2text/render/text_renderer/enum.RichAnnotation.html
pub struct RichConverter;
impl Renderer<text_renderer::RichDecorator, RichConverter> {
/// Creates a new renderer for the given HTML document using the default settings.
pub fn new(html: &str) -> Renderer<text_renderer::RichDecorator, RichConverter> {
Renderer::custom(html, text_renderer::RichDecorator::new(), RichConverter)
}
}
impl<D: text_renderer::TextDecorator + Clone, C: Converter<D::Annotation>> Renderer<D, C> {
/// Creates a new renderer for the given HTML document using a custom decorator and converter.
pub fn custom(html: &str, decorator: D, converter: C) -> Renderer<D, C> {
Renderer {
render_tree: html2text::parse(html.as_bytes()),
decorator,
converter,
}
}
}
impl<D: text_renderer::TextDecorator + Clone, C: Converter<D::Annotation>> super::Renderer
for Renderer<D, C>
{
fn render(&self, constraint: cursive_core::XY<usize>) -> RenderedDocument {
let mut doc = RenderedDocument::new(constraint);
let lines = self
.render_tree
.clone()
.render(std::cmp::max(5, constraint.x), self.decorator.clone())
.into_lines();
for line in lines {
let mut elements = Vec::new();
for element in line.iter() {
if let text_renderer::TaggedLineElement::Str(ts) = element {
let styles: Vec<_> = ts
.tag
.iter()
.filter_map(|a| self.converter.get_style(a))
.collect();
let link_target = ts
.tag
.iter()
.find_map(|a| self.converter.get_link(a))
.map(ToOwned::to_owned);
elements.push(Element::new(
ts.s.clone(),
theme::Style::merge(&styles),
link_target,
));
}
}
doc.push_line(elements);
}
doc
}
}
impl Converter<text_renderer::RichAnnotation> for RichConverter {
fn get_style(&self, annotation: &text_renderer::RichAnnotation) -> Option<theme::Style> {
use text_renderer::RichAnnotation;
match annotation {
RichAnnotation::Default => None,
RichAnnotation::Link(_) => Some(theme::Effect::Underline.into()),
RichAnnotation::Image => None,
RichAnnotation::Emphasis => Some(theme::Effect::Italic.into()),
RichAnnotation::Strong => Some(theme::Effect::Bold.into()),
RichAnnotation::Strikeout => Some(theme::Effect::Strikethrough.into()),
RichAnnotation::Code => Some(theme::PaletteColor::Secondary.into()),
RichAnnotation::Preformat(_) => None,
}
}
fn get_link<'a>(&self, annotation: &'a text_renderer::RichAnnotation) -> Option<&'a str> {
if let text_renderer::RichAnnotation::Link(target) = annotation {
Some(target)
} else {
None
}
}
}