#[allow(unused_imports)]
use {
crate::{
*,
minimad::*,
},
unicode_width::UnicodeWidthStr,
};
fn follow_up_composite<'s>(
fc: &FmtComposite<'s>,
skin: &MadSkin,
) -> FmtComposite<'s> {
let kind = match fc.kind {
CompositeKind::ListItem(l) => CompositeKind::ListItemFollowUp(l),
k => k,
};
let visible_length = match kind {
CompositeKind::ListItemFollowUp(l) if skin.list_items_indentation_mode == ListItemsIndentationMode::Block => 2+l as usize,
CompositeKind::Quote => 2,
_ => 0,
};
FmtComposite {
kind,
compounds: Vec::new(),
visible_length,
spacing: fc.spacing,
}
}
pub fn composite_kind_widths(
composite_kind: CompositeKind,
skin: &MadSkin,
) -> (usize, usize) {
match composite_kind {
CompositeKind::Paragraph => (0, 0),
CompositeKind::Header(_) => (0, 0),
CompositeKind::ListItem(depth) => {
let indent = 2 + depth as usize;
match skin.list_items_indentation_mode {
ListItemsIndentationMode::FirstLineOnly => (indent, 0),
ListItemsIndentationMode::Block => (indent, indent),
}
}
CompositeKind::ListItemFollowUp(depth) => {
let indent = 2 + depth as usize;
match skin.list_items_indentation_mode {
ListItemsIndentationMode::FirstLineOnly => (0, 0),
ListItemsIndentationMode::Block => (indent, indent),
}
}
CompositeKind::Code => (0, 0),
CompositeKind::Quote => (2, 2),
}
}
pub fn hard_wrap_composite<'s, 'c>(
src_composite: &'c FmtComposite<'s>,
width: usize,
skin: &MadSkin,
) -> Result<Vec<FmtComposite<'s>>, InsufficientWidthError> {
if width < 3 {
return Err(InsufficientWidthError{ available_width: width });
}
debug_assert!(src_composite.visible_length > width); let mut composites: Vec<FmtComposite<'s>> = Vec::new();
let (first_width, other_widths) = composite_kind_widths(src_composite.kind, skin);
let mut dst_composite = FmtComposite {
kind: src_composite.kind,
compounds: Vec::new(),
visible_length: first_width,
spacing: src_composite.spacing,
};
let compounds = &src_composite.compounds;
if
( compounds.len() == 2
&& compounds[0].src.width() + first_width <= width
&& compounds[1].src.width() + other_widths <= width
)
||
( compounds.len() == 3
&& compounds[0].src.width() + first_width <= width
&& compounds[2].src.width() + other_widths <= width
&& compounds[1].src.chars().all(char::is_whitespace)
)
{
dst_composite.add_compound(compounds[0].clone());
let mut new_dst_composite = follow_up_composite(&dst_composite, skin);
composites.push(dst_composite);
new_dst_composite.add_compound(compounds[compounds.len()-1].clone());
composites.push(new_dst_composite);
return Ok(composites);
}
let mut tokens = tokenize(&src_composite.compounds, width - first_width);
for token in tokens.drain(..) {
if dst_composite.visible_length + token.width > width {
if !token.blank { let mut repl_composite = follow_up_composite(&dst_composite, skin);
std::mem::swap(&mut dst_composite, &mut repl_composite);
composites.push(repl_composite);
dst_composite.add_compound(token.to_compound());
}
} else {
dst_composite.add_compound(token.to_compound());
}
}
composites.push(dst_composite);
Ok(composites)
}
pub fn hard_wrap_lines<'s>(
src_lines: Vec<FmtLine<'s>>,
width: usize,
skin: &MadSkin,
) -> Result<Vec<FmtLine<'s>>, InsufficientWidthError> {
let mut src_lines = src_lines;
let mut lines = Vec::new();
for src_line in src_lines.drain(..) {
if let FmtLine::Normal(fc) = src_line {
let (left_margin, right_margin) = skin
.line_style(fc.kind)
.margins_in(Some(width));
if fc.visible_length + left_margin + right_margin <= width {
lines.push(FmtLine::Normal(fc));
} else {
for fc in hard_wrap_composite(&fc, width - left_margin - right_margin, skin)? {
lines.push(FmtLine::Normal(fc));
}
}
} else {
lines.push(src_line);
}
}
Ok(lines)
}
#[cfg(test)]
mod wrap_tests {
use {
crate::{
displayable_line::DisplayableLine,
skin::MadSkin,
fit::wrap::*,
},
};
fn visible_fmt_line_length(skin: &MadSkin, line: &FmtLine<'_>) -> usize {
match line {
FmtLine::Normal(fc) => skin.visible_composite_length(fc.kind, &fc.compounds),
_ => 0, }
}
fn check_no_overflow(skin: &MadSkin, src: &str, width: usize) {
let text = skin.text(src, Some(width));
println!("------- test wrapping with width: {}", width);
for line in &text.lines {
let len = visible_fmt_line_length(skin, line);
println!(
"len:{: >4} | {}",
len,
DisplayableLine {
skin,
line,
width: None,
}
);
assert!(len <= width);
assert!(len > 0);
}
}
#[allow(clippy::needless_range_loop)]
fn check_line_lengths(skin: &MadSkin, src: &str, width: usize, lenghts: Vec<usize>) {
println!("====\ninput text:\n{}", &src);
let text = skin.text(src, Some(width));
assert_eq!(text.lines.len(), lenghts.len(), "same number of lines");
println!("====\nwrapped text:\n{}", &text);
for i in 0..lenghts.len() {
assert_eq!(
visible_fmt_line_length(skin, &text.lines[i]),
lenghts[i],
"expected length for line {} when wrapping at {}",
i,
width
);
}
}
#[test]
fn check_hard_wrapping_simple_text() {
let skin = crate::get_default_skin();
let src = "This is a *long* line which needs to be **broken**.\n\
And the text goes on with a list:\n\
* short item\n\
* a *somewhat longer item* (with a part in **bold**)";
for width in 3..50 {
check_no_overflow(skin, src, width);
}
check_line_lengths(skin, src, 25, vec![25, 19, 25, 7, 12, 25, 23]);
}
#[test]
fn check_space_removing() {
let skin = crate::get_default_skin();
let src = FmtComposite::from(Composite::from_inline("syntax coloring"), skin);
println!("input:\n{:?}", &src);
let wrapped = hard_wrap_composite(&src, 8, skin).unwrap();
println!("wrapped: {:?}", &wrapped);
assert_eq!(wrapped.len(), 2);
}
fn first_compound(line: FmtLine) -> Option<Compound> {
match line {
FmtLine::Normal(mut fc) => fc.compounds.drain(..).next(),
_ => None,
}
}
#[test]
fn check_issue_17() {
let skin = crate::get_default_skin();
let src = "*Now I'll describe this example with more words than necessary, in order to be sure to demonstrate scrolling (and **wrapping**, too, thanks to long sentences).*";
let text = skin.text(src, Some(120));
assert_eq!(text.lines.len(), 2);
assert_eq!(
first_compound(text.lines.into_iter().nth(1).unwrap()),
Some(Compound::raw_str("wrapping").bold().italic()),
);
}
#[test]
fn check_issue_23() {
let md: &str = "ZA\u{360}\u{321}\u{34a}\u{35d}LGΌ IS\u{36e}\u{302}\u{489}\u{32f}\u{348}\u{355}\u{339}\u{318}\u{331} T</b>O\u{345}\u{347}\u{339}\u{33a}Ɲ\u{334}ȳ\u{333} TH\u{318}<b>E\u{344}\u{309}\u{356} \u{360}P\u{32f}\u{34d}\u{32d}O\u{31a}\u{200b}N\u{310}Y\u{321} H\u{368}\u{34a}\u{33d}\u{305}\u{33e}\u{30e}\u{321}\u{338}\u{32a}\u{32f}E\u{33e}\u{35b}\u{36a}\u{344}\u{300}\u{301}\u{327}\u{358}\u{32c}\u{329} \u{367}\u{33e}\u{36c}\u{327}\u{336}\u{328}\u{331}\u{339}\u{32d}\u{32f}C\u{36d}\u{30f}\u{365}\u{36e}\u{35f}\u{337}\u{319}\u{332}\u{31d}\u{356}O\u{36e}\u{34f}\u{32e}\u{32a}\u{31d}\u{34d}";
let skin = MadSkin::default();
for w in 40..60 {
println!("wrapping on width {}", w);
let _text = FmtText::from(&skin, md, Some(w));
}
}
}