#[cfg(feature = "images")]
mod images;
use std::collections;
use std::iter;
use std::mem;
use crate::error::{Error, ErrorKind};
use crate::render;
use crate::style::{Style, StyledString};
use crate::wrap;
use crate::{Alignment, Context, Element, Margins, Mm, Position, RenderResult, Size};
#[cfg(feature = "images")]
pub use images::Image;
pub struct LinearLayout {
elements: Vec<Box<dyn Element>>,
render_idx: usize,
}
impl LinearLayout {
fn new() -> LinearLayout {
LinearLayout {
elements: Vec::new(),
render_idx: 0,
}
}
pub fn vertical() -> LinearLayout {
LinearLayout::new()
}
pub fn push<E: Element + 'static>(&mut self, element: E) {
self.elements.push(Box::new(element));
}
pub fn element<E: Element + 'static>(mut self, element: E) -> Self {
self.push(element);
self
}
fn render_vertical(
&mut self,
context: &Context,
mut area: render::Area<'_>,
style: Style,
) -> Result<RenderResult, Error> {
let mut result = RenderResult::default();
while area.size().height > Mm(0.0) && self.render_idx < self.elements.len() {
let element_result =
self.elements[self.render_idx].render(context, area.clone(), style)?;
area.add_offset(Position::new(0, element_result.size.height));
result.size = result.size.stack_vertical(element_result.size);
if element_result.has_more {
result.has_more = true;
return Ok(result);
}
self.render_idx += 1;
}
result.has_more = self.render_idx < self.elements.len();
Ok(result)
}
}
impl Element for LinearLayout {
fn render(
&mut self,
context: &Context,
area: render::Area<'_>,
style: Style,
) -> Result<RenderResult, Error> {
self.render_vertical(context, area, style)
}
}
#[derive(Clone, Debug, Default)]
pub struct Text {
text: StyledString,
}
impl Text {
pub fn new(text: impl Into<StyledString>) -> Text {
Text { text: text.into() }
}
}
impl Element for Text {
fn render(
&mut self,
context: &Context,
area: render::Area<'_>,
mut style: Style,
) -> Result<RenderResult, Error> {
let mut result = RenderResult::default();
style.merge(self.text.style);
if area.print_str(
&context.font_cache,
Position::default(),
style,
&self.text.s,
)? {
result.size = Size::new(
style.str_width(&context.font_cache, &self.text.s),
style.line_height(&context.font_cache),
);
} else {
result.has_more = true;
}
Ok(result)
}
}
#[derive(Clone, Debug, Default)]
pub struct Paragraph {
text: Vec<StyledString>,
words: collections::VecDeque<StyledString>,
style_applied: bool,
alignment: Alignment,
}
impl Paragraph {
pub fn new(text: impl Into<StyledString>) -> Paragraph {
Paragraph {
text: vec![text.into()],
..Default::default()
}
}
pub fn set_alignment(&mut self, alignment: Alignment) {
self.alignment = alignment;
}
pub fn aligned(mut self, alignment: Alignment) -> Self {
self.set_alignment(alignment);
self
}
pub fn push(&mut self, s: impl Into<StyledString>) {
self.text.push(s.into());
}
pub fn string(mut self, s: impl Into<StyledString>) -> Self {
self.push(s);
self
}
pub fn push_styled(&mut self, s: impl Into<String>, style: impl Into<Style>) {
self.text.push(StyledString::new(s, style))
}
pub fn styled_string(mut self, s: impl Into<String>, style: impl Into<Style>) -> Self {
self.push_styled(s, style);
self
}
fn get_offset(&self, width: Mm, max_width: Mm) -> Mm {
match self.alignment {
Alignment::Left => Mm::default(),
Alignment::Center => (max_width - width) / 2.0,
Alignment::Right => max_width - width,
}
}
fn apply_style(&mut self, style: Style) {
if !self.style_applied {
for s in &mut self.text {
s.style = style.and(s.style);
}
self.style_applied = true;
}
}
}
impl Element for Paragraph {
fn render(
&mut self,
context: &Context,
mut area: render::Area<'_>,
style: Style,
) -> Result<RenderResult, Error> {
let mut result = RenderResult::default();
self.apply_style(style);
if self.words.is_empty() {
if self.text.is_empty() {
return Ok(result);
}
self.words = wrap::Words::new(mem::take(&mut self.text)).collect();
}
let height = style.line_height(&context.font_cache);
let words = self.words.iter().map(Into::into);
let mut rendered_len = 0;
for (line, delta) in wrap::Wrapper::new(words, context, area.size().width) {
let width = line.iter().map(|s| s.width(&context.font_cache)).sum();
let position = Position::new(self.get_offset(width, area.size().width), 0);
if let Some(mut section) = area.text_section(&context.font_cache, position, style) {
for s in line {
section.print_str(&s.s, s.style)?;
rendered_len += s.s.len();
}
rendered_len -= delta;
} else {
result.has_more = true;
break;
}
result.size = result.size.stack_vertical(Size::new(width, height));
area.add_offset(Position::new(0, height));
}
while rendered_len > 0 && !self.words.is_empty() {
if self.words[0].s.len() <= rendered_len {
rendered_len -= self.words[0].s.len();
self.words.pop_front();
} else {
self.words[0].s.replace_range(..rendered_len, "");
rendered_len = 0;
}
}
Ok(result)
}
}
impl From<Vec<StyledString>> for Paragraph {
fn from(text: Vec<StyledString>) -> Paragraph {
Paragraph {
text,
..Default::default()
}
}
}
impl<T: Into<StyledString>> iter::Extend<T> for Paragraph {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for s in iter {
self.push(s);
}
}
}
impl<T: Into<StyledString>> iter::FromIterator<T> for Paragraph {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Paragraph {
let mut paragraph = Paragraph::default();
paragraph.extend(iter);
paragraph
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Break {
lines: f64,
}
impl Break {
pub fn new(lines: impl Into<f64>) -> Break {
Break {
lines: lines.into(),
}
}
}
impl Element for Break {
fn render(
&mut self,
context: &Context,
area: render::Area<'_>,
style: Style,
) -> Result<RenderResult, Error> {
let mut result = RenderResult::default();
if self.lines <= 0.0 {
return Ok(result);
}
let line_height = style.line_height(&context.font_cache);
let break_height = line_height * self.lines;
if break_height < area.size().height {
result.size.height = break_height;
self.lines = 0.0;
} else {
result.size.height = area.size().height;
self.lines -= result.size.height.0 / line_height.0;
}
Ok(result)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct PageBreak {
cont: bool,
}
impl PageBreak {
pub fn new() -> PageBreak {
PageBreak::default()
}
}
impl Element for PageBreak {
fn render(
&mut self,
_context: &Context,
_area: render::Area<'_>,
_style: Style,
) -> Result<RenderResult, Error> {
if self.cont {
Ok(RenderResult::default())
} else {
self.cont = true;
Ok(RenderResult {
size: Size::new(1, 0),
has_more: true,
})
}
}
}
#[derive(Clone, Debug, Default)]
pub struct PaddedElement<E: Element> {
element: E,
padding: Margins,
}
impl<E: Element> PaddedElement<E> {
pub fn new(element: E, padding: impl Into<Margins>) -> PaddedElement<E> {
PaddedElement {
element,
padding: padding.into(),
}
}
}
impl<E: Element> Element for PaddedElement<E> {
fn render(
&mut self,
context: &Context,
mut area: render::Area<'_>,
style: Style,
) -> Result<RenderResult, Error> {
area.add_margins(Margins {
bottom: Mm(0.0),
..self.padding
});
let mut result = self.element.render(context, area, style)?;
result.size.width += self.padding.left + self.padding.right;
result.size.height += self.padding.top + self.padding.bottom;
Ok(result)
}
}
#[derive(Clone, Debug, Default)]
pub struct StyledElement<E: Element> {
element: E,
style: Style,
}
impl<E: Element> StyledElement<E> {
pub fn new(element: E, style: impl Into<Style>) -> StyledElement<E> {
StyledElement {
element,
style: style.into(),
}
}
}
impl<E: Element> Element for StyledElement<E> {
fn render(
&mut self,
context: &Context,
area: render::Area<'_>,
mut style: Style,
) -> Result<RenderResult, Error> {
style.merge(self.style);
self.element.render(context, area, style)
}
}
#[derive(Clone, Debug, Default)]
pub struct FramedElement<E: Element> {
element: E,
is_first: bool,
}
impl<E: Element> FramedElement<E> {
pub fn new(element: E) -> FramedElement<E> {
FramedElement {
element,
is_first: true,
}
}
}
impl<E: Element> Element for FramedElement<E> {
fn render(
&mut self,
context: &Context,
area: render::Area<'_>,
style: Style,
) -> Result<RenderResult, Error> {
let result = self.element.render(context, area.clone(), style)?;
area.draw_line(
vec![Position::default(), Position::new(0, result.size.height)],
style,
);
area.draw_line(
vec![
Position::new(area.size().width, 0),
Position::new(area.size().width, result.size.height),
],
style,
);
if self.is_first {
area.draw_line(
vec![Position::default(), Position::new(area.size().width, 0)],
style,
);
}
if !result.has_more {
area.draw_line(
vec![
Position::new(0, result.size.height),
Position::new(area.size().width, result.size.height),
],
style,
);
}
self.is_first = false;
Ok(result)
}
}
pub struct UnorderedList {
layout: LinearLayout,
bullet: Option<String>,
}
impl UnorderedList {
pub fn new() -> UnorderedList {
UnorderedList {
layout: LinearLayout::vertical(),
bullet: None,
}
}
pub fn with_bullet(bullet: impl Into<String>) -> UnorderedList {
UnorderedList {
layout: LinearLayout::vertical(),
bullet: Some(bullet.into()),
}
}
pub fn push<E: Element + 'static>(&mut self, element: E) {
let mut point = BulletPoint::new(element);
if let Some(bullet) = &self.bullet {
point.set_bullet(bullet.clone());
}
self.layout.push(point);
}
pub fn element<E: Element + 'static>(mut self, element: E) -> Self {
self.push(element);
self
}
}
impl Element for UnorderedList {
fn render(
&mut self,
context: &Context,
area: render::Area<'_>,
style: Style,
) -> Result<RenderResult, Error> {
self.layout.render(context, area, style)
}
}
impl Default for UnorderedList {
fn default() -> UnorderedList {
UnorderedList::new()
}
}
pub struct OrderedList {
layout: LinearLayout,
number: usize,
}
impl OrderedList {
pub fn new() -> OrderedList {
OrderedList::with_start(1)
}
pub fn with_start(start: usize) -> OrderedList {
OrderedList {
layout: LinearLayout::vertical(),
number: start,
}
}
pub fn push<E: Element + 'static>(&mut self, element: E) {
let mut point = BulletPoint::new(element);
point.set_bullet(format!("{}.", self.number));
self.layout.push(point);
self.number += 1;
}
pub fn element<E: Element + 'static>(mut self, element: E) -> Self {
self.push(element);
self
}
}
impl Element for OrderedList {
fn render(
&mut self,
context: &Context,
area: render::Area<'_>,
style: Style,
) -> Result<RenderResult, Error> {
self.layout.render(context, area, style)
}
}
impl Default for OrderedList {
fn default() -> OrderedList {
OrderedList::new()
}
}
pub struct BulletPoint<E: Element> {
element: E,
indent: Mm,
bullet_space: Mm,
bullet: String,
bullet_rendered: bool,
}
impl<E: Element> BulletPoint<E> {
pub fn new(element: E) -> BulletPoint<E> {
BulletPoint {
element,
indent: Mm::from(10),
bullet_space: Mm::from(2),
bullet: String::from("–"),
bullet_rendered: false,
}
}
pub fn set_bullet(&mut self, bullet: impl Into<String>) {
self.bullet = bullet.into();
}
pub fn with_bullet(mut self, bullet: impl Into<String>) -> Self {
self.set_bullet(bullet);
self
}
}
impl<E: Element> Element for BulletPoint<E> {
fn render(
&mut self,
context: &Context,
area: render::Area<'_>,
style: Style,
) -> Result<RenderResult, Error> {
let mut element_area = area.clone();
element_area.add_offset(Position::new(self.indent, 0));
let mut result = self.element.render(context, element_area, style)?;
result.size.width += self.indent;
if !self.bullet_rendered {
let bullet_width = style.str_width(&context.font_cache, &self.bullet);
area.print_str(
&context.font_cache,
Position::new(self.indent - bullet_width - self.bullet_space, 0),
style,
self.bullet.as_str(),
)?;
self.bullet_rendered = true;
}
Ok(result)
}
}
pub trait CellDecorator {
fn set_table_size(&mut self, num_columns: usize, num_rows: usize) {
let _ = (num_columns, num_rows);
}
fn decorate_cell(
&mut self,
column: usize,
row: usize,
has_more: bool,
area: render::Area<'_>,
style: Style,
);
}
#[derive(Clone, Debug, Default)]
pub struct FrameCellDecorator {
inner: bool,
outer: bool,
cont: bool,
num_columns: usize,
num_rows: usize,
last_row: Option<usize>,
}
impl FrameCellDecorator {
pub fn new(inner: bool, outer: bool, cont: bool) -> FrameCellDecorator {
FrameCellDecorator {
inner,
outer,
cont,
..Default::default()
}
}
fn print_left(&self, column: usize) -> bool {
if column == 0 {
self.outer
} else {
self.inner
}
}
fn print_right(&self, column: usize) -> bool {
if column + 1 == self.num_columns {
self.outer
} else {
self.inner
}
}
fn print_top(&self, row: usize) -> bool {
if self.last_row.map(|last_row| row > last_row).unwrap_or(true) {
if row == 0 {
self.outer
} else {
self.inner
}
} else {
self.cont
}
}
fn print_bottom(&self, row: usize, has_more: bool) -> bool {
if has_more {
self.cont
} else if row + 1 == self.num_rows {
self.outer
} else {
self.inner
}
}
}
impl CellDecorator for FrameCellDecorator {
fn set_table_size(&mut self, num_columns: usize, num_rows: usize) {
self.num_columns = num_columns;
self.num_rows = num_rows;
}
fn decorate_cell(
&mut self,
column: usize,
row: usize,
has_more: bool,
area: render::Area<'_>,
style: Style,
) {
let size = area.size();
if self.print_left(column) {
area.draw_line(
vec![Position::default(), Position::new(0, size.height)],
style,
);
}
if self.print_right(column) {
area.draw_line(
vec![
Position::new(size.width, 0),
Position::new(size.width, size.height),
],
style,
);
}
if self.print_top(row) {
area.draw_line(
vec![Position::default(), Position::new(size.width, 0)],
style,
);
}
if self.print_bottom(row, has_more) {
area.draw_line(
vec![
Position::new(0, size.height),
Position::new(size.width, size.height),
],
style,
);
}
if column + 1 == self.num_columns {
self.last_row = Some(row);
}
}
}
pub struct TableLayoutRow<'a> {
table_layout: &'a mut TableLayout,
elements: Vec<Box<dyn Element>>,
}
impl<'a> TableLayoutRow<'a> {
fn new(table_layout: &'a mut TableLayout) -> TableLayoutRow<'a> {
TableLayoutRow {
table_layout,
elements: Vec::new(),
}
}
pub fn push_element<E: Element + 'static>(&mut self, element: E) {
self.elements.push(Box::new(element));
}
#[must_use]
pub fn element<E: Element + 'static>(mut self, element: E) -> Self {
self.push_element(element);
self
}
pub fn push(self) -> Result<(), Error> {
self.table_layout.push_row(self.elements)
}
}
pub struct TableLayout {
column_weights: Vec<usize>,
rows: Vec<Vec<Box<dyn Element>>>,
render_idx: usize,
cell_decorator: Option<Box<dyn CellDecorator>>,
}
impl TableLayout {
pub fn new(column_weights: Vec<usize>) -> TableLayout {
TableLayout {
column_weights,
rows: Vec::new(),
render_idx: 0,
cell_decorator: None,
}
}
pub fn set_cell_decorator(&mut self, decorator: impl CellDecorator + 'static) {
self.cell_decorator = Some(Box::from(decorator));
}
pub fn row(&mut self) -> TableLayoutRow<'_> {
TableLayoutRow::new(self)
}
pub fn push_row(&mut self, row: Vec<Box<dyn Element>>) -> Result<(), Error> {
if row.len() == self.column_weights.len() {
self.rows.push(row);
Ok(())
} else {
Err(Error::new(
format!(
"Expected {} elements in table row, received {}",
self.column_weights.len(),
row.len()
),
ErrorKind::InvalidData,
))
}
}
fn render_row(
&mut self,
context: &Context,
area: render::Area<'_>,
style: Style,
) -> Result<RenderResult, Error> {
let mut result = RenderResult::default();
let areas = area.split_horizontally(&self.column_weights);
let mut row_height = Mm::from(0);
for (area, element) in areas.iter().zip(self.rows[self.render_idx].iter_mut()) {
let element_result = element.render(context, area.clone(), style)?;
result.has_more |= element_result.has_more;
row_height = row_height.max(element_result.size.height);
}
result.size.height = row_height;
if let Some(decorator) = &mut self.cell_decorator {
for (i, mut area) in areas.into_iter().enumerate() {
area.set_height(row_height);
decorator.decorate_cell(i, self.render_idx, result.has_more, area, style);
}
}
Ok(result)
}
}
impl Element for TableLayout {
fn render(
&mut self,
context: &Context,
mut area: render::Area<'_>,
style: Style,
) -> Result<RenderResult, Error> {
let mut result = RenderResult::default();
if self.column_weights.is_empty() {
return Ok(result);
}
if let Some(decorator) = &mut self.cell_decorator {
decorator.set_table_size(self.column_weights.len(), self.rows.len());
}
result.size.width = area.size().width;
while self.render_idx < self.rows.len() {
let row_result = self.render_row(context, area.clone(), style)?;
result.size.height += row_result.size.height;
area.add_offset(Position::new(0, row_result.size.height));
if row_result.has_more {
break;
}
self.render_idx += 1;
}
result.has_more = self.render_idx < self.rows.len();
Ok(result)
}
}