Skip to content

Commit

Permalink
Merge pull request #101 from jugglerchris/add_wrap_width
Browse files Browse the repository at this point in the history
Add wrap width
  • Loading branch information
jugglerchris authored Dec 22, 2023
2 parents 2a1f758 + 4c63620 commit 6e230ed
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 24 deletions.
72 changes: 51 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -935,22 +935,24 @@ struct HtmlContext {
style_data: css::StyleData,
#[cfg(feature = "css")]
use_doc_css: bool,

max_wrap_width: Option<usize>,
}

fn dom_to_render_tree_with_context<T: Write>(
handle: Handle,
err_out: &mut T,
mut context: HtmlContext)
context: &mut HtmlContext)
-> Result<Option<RenderNode>> {
html_trace!("### dom_to_render_tree: HTML: {:?}", handle);
#[cfg(feature = "css")]
if context.use_doc_css {
let mut doc_style_data = css::dom_to_stylesheet(handle.clone(), err_out)?;
doc_style_data.merge(context.style_data);
doc_style_data.merge(std::mem::take(&mut context.style_data));
context.style_data = doc_style_data;
}

let result = tree_map_reduce(&mut context, handle, |context, handle| {
let result = tree_map_reduce(context, handle, |context, handle| {
process_dom_node(handle, err_out, context)
});

Expand All @@ -960,7 +962,7 @@ fn dom_to_render_tree_with_context<T: Write>(

/// Convert a DOM tree or subtree into a render tree.
pub fn dom_to_render_tree<T: Write>(handle: Handle, err_out: &mut T) -> Result<Option<RenderNode>> {
dom_to_render_tree_with_context(handle, err_out, Default::default())
dom_to_render_tree_with_context(handle, err_out, &mut Default::default())
}

fn pending<'a, F>(handle: Handle, f: F) -> TreeMapResult<'a, HtmlContext, Handle, RenderNode>
Expand Down Expand Up @@ -1734,36 +1736,49 @@ pub mod config {
pub struct Config<D: TextDecorator> {
decorator: D,

max_wrap_width: Option<usize>,

#[cfg(feature = "css")]
style: StyleData,
#[cfg(feature = "css")]
use_doc_css: bool,
}

impl<D: TextDecorator> Config<D> {
/// Make the HtmlContext from self.
fn make_context(&mut self) -> HtmlContext {
HtmlContext {
#[cfg(feature = "css")]
style_data: std::mem::take(&mut self.style),
#[cfg(feature = "css")]
use_doc_css: self.use_doc_css,

max_wrap_width: self.max_wrap_width,
}
}
/// Parse with context.
fn do_parse<R: std::io::Read>(&mut self, input: R) -> Result<RenderTree> {
fn do_parse<R: std::io::Read>(&mut self, context: &mut HtmlContext, input: R) -> Result<RenderTree> {
super::parse_with_context(
input,
HtmlContext {
#[cfg(feature = "css")]
style_data: std::mem::take(&mut self.style),
#[cfg(feature = "css")]
use_doc_css: self.use_doc_css,
})
context)
}

/// Reads HTML from `input`, and returns a `String` with text wrapped to
/// `width` columns.
pub fn string_from_read<R: std::io::Read>(mut self, input: R, width: usize) -> Result<String> {
Ok(self.do_parse(input)?.render(width, self.decorator)?.into_string()?)
let mut context = self.make_context();
Ok(self.do_parse(&mut context, input)?
.render_with_context(&mut context, width, self.decorator)?
.into_string()?)
}

/// Reads HTML from `input`, and returns text wrapped to `width` columns.
/// The text is returned as a `Vec<TaggedLine<_>>`; the annotations are vectors
/// of the provided text decorator's `Annotation`. The "outer" annotation comes first in
/// the `Vec`.
pub fn lines_from_read<R: std::io::Read>(mut self, input: R, width: usize) -> Result<Vec<TaggedLine<Vec<D::Annotation>>>> {
Ok(self.do_parse(input)?
let mut context = self.make_context();
Ok(self.do_parse(&mut context, input)?
.render(width, self.decorator)?
.into_lines()?)
}
Expand All @@ -1779,9 +1794,15 @@ pub mod config {
#[cfg(feature = "css")]
/// Parse CSS from any <style> elements and use supported rules.
pub fn use_doc_css(mut self) -> Self {
{
self.use_doc_css = true;
}
self.use_doc_css = true;
self
}

/// Set the maximum text wrap width.
/// When set, paragraphs will be wrapped to that width even if there
/// is more total width available for rendering.
pub fn max_wrap_width(mut self, wrap_width: usize) -> Self {
self.max_wrap_width = Some(wrap_width);
self
}
}
Expand All @@ -1803,7 +1824,8 @@ pub mod config {
{
use std::fmt::Write;

let lines = self.do_parse(input)?
let mut context = self.make_context();
let lines = self.do_parse(&mut context, input)?
.render(width, self.decorator)?
.into_lines()?;

Expand All @@ -1826,6 +1848,7 @@ pub mod config {
style: Default::default(),
#[cfg(feature = "css")]
use_doc_css: false,
max_wrap_width: None,
}
}

Expand All @@ -1837,6 +1860,7 @@ pub mod config {
style: Default::default(),
#[cfg(feature = "css")]
use_doc_css: false,
max_wrap_width: None,
}
}

Expand All @@ -1848,6 +1872,7 @@ pub mod config {
style: Default::default(),
#[cfg(feature = "css")]
use_doc_css: false,
max_wrap_width: None,
}
}
}
Expand All @@ -1861,15 +1886,20 @@ pub struct RenderTree(RenderNode);

impl RenderTree {
/// Render this document using the given `decorator` and wrap it to `width` columns.
pub fn render<D: TextDecorator>(self, width: usize, decorator: D) -> Result<RenderedText<D>> {
fn render_with_context<D: TextDecorator>(self, context: &mut HtmlContext, width: usize, decorator: D) -> Result<RenderedText<D>> {
if width == 0 {
return Err(Error::TooNarrow);
}
let builder = SubRenderer::new(width, decorator);
let builder = SubRenderer::new(width, context.max_wrap_width, decorator);
let builder = render_tree_to_string(builder, self.0, &mut Discard {})?;
Ok(RenderedText(builder))
}

/// Render this document using the given `decorator` and wrap it to `width` columns.
pub fn render<D: TextDecorator>(self, width: usize, decorator: D) -> Result<RenderedText<D>> {
self.render_with_context(&mut Default::default(), width, decorator)
}

/// Render this document as plain text using the [`PlainDecorator`][] and wrap it to `width`
/// columns.
///
Expand Down Expand Up @@ -1908,7 +1938,7 @@ impl<D: TextDecorator> RenderedText<D> {
}

fn parse_with_context(mut input: impl io::Read,
context: HtmlContext,
context: &mut HtmlContext,
) -> Result<RenderTree> {
let opts = ParseOpts {
tree_builder: TreeBuilderOpts {
Expand All @@ -1928,7 +1958,7 @@ fn parse_with_context(mut input: impl io::Read,

/// Reads and parses HTML from `input` and prepares a render tree.
pub fn parse(input: impl io::Read) -> Result<RenderTree> {
parse_with_context(input, Default::default())
parse_with_context(input, &mut Default::default())
}

/// Reads HTML from `input`, decorates it using `decorator`, and
Expand Down
12 changes: 9 additions & 3 deletions src/render/text_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,7 @@ impl<T: PartialEq + Eq + Clone + Debug + Default> RenderLine<T> {
#[derive(Clone)]
pub struct SubRenderer<D: TextDecorator> {
width: usize,
wrap_width: Option<usize>,
lines: LinkedList<RenderLine<Vec<D::Annotation>>>,
/// True at the end of a block, meaning we should add
/// a blank line if any other text is added.
Expand Down Expand Up @@ -836,10 +837,11 @@ impl<D: TextDecorator> SubRenderer<D> {
}

/// Construct a new empty SubRenderer.
pub fn new(width: usize, decorator: D) -> SubRenderer<D> {
pub fn new(width: usize, wrap_width: Option<usize>, decorator: D) -> SubRenderer<D> {
html_trace!("new({})", width);
SubRenderer {
width,
wrap_width,
lines: LinkedList::new(),
at_block_end: false,
wrapping: None,
Expand All @@ -852,7 +854,11 @@ impl<D: TextDecorator> SubRenderer<D> {

fn ensure_wrapping_exists(&mut self) {
if self.wrapping.is_none() {
self.wrapping = Some(WrappedBlock::new(self.width));
let wwidth = match self.wrap_width {
Some(ww) => ww.min(self.width),
None => self.width
};
self.wrapping = Some(WrappedBlock::new(wwidth));
}
}

Expand Down Expand Up @@ -1007,7 +1013,7 @@ impl<D: TextDecorator> Renderer for SubRenderer<D> {
if width < 1 {
return Err(Error::TooNarrow);
}
Ok(SubRenderer::new(width, self.decorator.make_subblock_decorator()))
Ok(SubRenderer::new(width, self.wrap_width, self.decorator.make_subblock_decorator()))
}

fn start_block(&mut self) -> crate::Result<()> {
Expand Down
56 changes: 56 additions & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ fn test_html(input: &[u8], expected: &str, width: usize) {
let output = from_read(input, width);
assert_eq_str!(output, expected);
}
fn test_html_maxwrap(input: &[u8], expected: &str, width: usize, wrap_width: usize) {
let result = config::plain()
.max_wrap_width(wrap_width)
.string_from_read(input, width).unwrap();
assert_eq_str!(result, expected);
}
#[cfg(feature = "css")]
fn test_html_css(input: &[u8], expected: &str, width: usize) {
let result = config::plain()
Expand Down Expand Up @@ -617,6 +623,56 @@ wrapped.
);
}

#[test]
fn test_wrap_max() {
test_html_maxwrap(br#"
<p>This is a bit of text to wrap<p>
<ul>
<li>This is a bit of text to wrap too</li>
</li>
</ul>"#,
r#"This is a
bit of
text to
wrap
* This is a
bit of
text to
wrap too
"#, 20, 10)
}

#[test]
fn test_wrap_max2() {
test_html_maxwrap(br#"
<p>plain para at the full screen width</p>
<ul>
<li>bullet point uses same width so its margin is 2 chars further right
<ul><li>nested bullets in turn move 2 chars right each time
<ul><li>result: you never get text squashed too narrow</li></ul>
</li></ul>
</li></ul>"#,
r#"plain para at the
full screen width
* bullet point uses
same width so its
margin is 2 chars
further right
* nested bullets in
turn move 2 chars
right each time
* result: you never
get text squashed
too narrow
"#, 80, 17);
}

#[test]
fn test_div() {
test_html(
Expand Down

0 comments on commit 6e230ed

Please sign in to comment.