format_tools 0.6.0

Collection of mechanisms for formatting and serialization into string.
Documentation
//!
//! Text wrapping function.
//!

/// Define a private namespace for all its items.
mod private
{

  use std::borrow::Cow;

  use crate::*;

  /// Struct that represents a wrapped tabular data. It is similar to `InputExtract`,
  /// but we cannot use it as it does not wrap the text and it contains wrong column
  /// widthes and heights (as they are dependent on wrapping too).
  #[ derive( Debug ) ]
  pub struct WrappedInputExtract< 'data >
  {
    /// Tabular data of rows and columns.
    /// Note: these cells does not represent the actual information cells in the 
    /// original table. These cells are wrapped and used only for displaying. This also
    /// means that one row in original table can be represented here with one or more
    /// rows.
    pub data: Vec< Vec< WrappedCell<  'data  > > >,

    /// New widthes of columns that include wrapping.
    pub column_widthes : Vec<  usize  >,

    /// Size of the first row of the table.
    /// This parameter is used in case header of the table should be displayed.
    pub first_row_height : usize,
  }

  /// Struct that represents a content of a wrapped cell.
  /// It contains the slice of the cell as well as its original width.
  ///
  /// Parameter `wrap_width` is needed as text in `output_format::Table` is centered.
  /// However it is centered according to whole cell size and not the size of wrapped
  /// text slice.
  /// 
  /// Example that depicts the importance of `wrap_width` parameter:
  /// 
  /// 1) | [        |  2) |    [     |
  ///    |   line1, |     |   line1, |
  ///    |   line2  |     |   line2  |
  ///    | ]        |     |    ]     |
  ///
  /// The first case seems to be properly formatted, while the second case took centering
  /// too literally. That is why `wrap_width` is introduced, and additional spaces to the 
  /// right side should be included by the output formatter.
  #[ derive( Debug ) ]
  pub struct WrappedCell<  'data  >
  {
    /// Width of the cell. In calculations use this width instead of slice length in order
    /// to properly center the text. See example in the doc string of the parent struct.
    pub wrap_width : usize,

    /// Actual content of the cell.
    pub content : Cow< 'data, str >
  }

  /// Wrap table cells.
  ///
  /// `InputExtract` contains cells with full content, so it represents the logical
  /// structure of the table. `WrappedInputExtract` wraps original cells to smaller 
  /// cells. The resulting data is more low-level and corresponds to the table that
  /// will be actually printed to the console (or other output type).
  ///
  /// `InputExtract` is not directly passed to this function, as it made to be general.
  /// Instead you pass table cells in `data` argument and pass a vector of column widthes
  /// in `columns_width_list` generated by `InputExtract`.
  ///
  /// `columns_width_list` is a slice, this is more effective and general than just a `Vec`.
  /// In table style, there could be many columns, but in records style there will be
  /// always 2 columns - this number is known at compile time, so we can use a slice object.
  ///
  /// Notice: 
  /// 1. Data passed to this function should contain only visible rows and columns.
  ///    It does not perform additional filtering.
  /// 2. `data` parameters is **vector of rows of columns** (like and ordinary table).
  ///    This means that in styles like `Records` where headers and rows turned into columns
  ///    You have to transpose your data before passing it to this function.
  ///
  /// Wrapping is controlled by `columns_max_width` and `columns_nowrap_width` parameters.
  ///
  /// - `columns_max_width` is the size that is allowed to be occupied by columns.
  /// It equals to maximum table width minus lengthes of visual elements (prefixes,
  /// postfixes, separators, etc.).
  ///
  /// - `columns_nowrap_width` is the sum of column widthes of cells without wrapping (basically,
  /// the sum of widthes of column descriptors in `InputExtract`).
  ///
  /// The function will perform wrapping and shrink the columns so that they occupy not
  /// more than `columns_max_width`.
  ///
  /// If `columns_max_width` is equal to 0, then no wrapping will be performed. 
  pub fn text_wrap< 'data >
  (
    data : impl Iterator< Item = &'data Vec< ( Cow< 'data, str >, [ usize; 2 ] ) > >,
    columns_width_list : impl AsRef< [ usize ] >,
    columns_max_width : usize,
    columns_nowrap_width : usize,
  ) 
  -> WrappedInputExtract< 'data >
  {
    let columns_width_list = columns_width_list.as_ref();

    let mut first_row_height = 0;
    let mut new_data = Vec::new();
    let mut column_widthes = Vec::new();

    if columns_max_width == 0 || columns_max_width >= columns_nowrap_width
    {
      column_widthes.extend( columns_width_list.iter() );
    }
    else
    {
      let shrink_factor : f32 = ( columns_max_width as f32 ) / ( columns_nowrap_width as f32 );

      for ( icol, col_width ) in columns_width_list.iter().enumerate()
      {
        let col_limit_float = ( *col_width as f32 ) * shrink_factor;
        let col_limit = col_limit_float.floor() as usize;

        let col_width_to_put = if icol == columns_width_list.len() - 1
        {
          columns_max_width - column_widthes.iter().sum::<usize>()
        }
        else
        {
          col_limit.max(1)
        };

        column_widthes.push( col_width_to_put );
      }
    }

    for ( irow, row ) in data.enumerate()
    {
      let mut wrapped_rows : Vec< Vec< Cow< 'data, str > > > = vec![];

      for ( icol, col ) in row.iter().enumerate()
      {
        let col_limit = column_widthes[ icol ];
        let wrapped_col = string::lines_with_limit( col.0.as_ref(), col_limit ).map( Cow::from ).collect();
        wrapped_rows.push( wrapped_col );
      }

      let max_rows = wrapped_rows.iter().map( Vec::len ).max().unwrap_or(0);

      let mut transposed : Vec< Vec< WrappedCell<  'data  > > > = Vec::new();

      if max_rows == 0 
      {
        transposed.push( vec![] );
      }
      
      for i in 0..max_rows
      {
        let mut row_vec : Vec< WrappedCell<  'data  > > = Vec::new();

        for col_lines in &wrapped_rows
        {
          if col_lines.len() > i
          {
            let wrap_width = col_lines.iter().map( |c| c.len() ).max().unwrap_or(0);
            row_vec.push( WrappedCell { wrap_width , content : col_lines[ i ].clone() } );
          }
          else
          {
            row_vec.push( WrappedCell { wrap_width : 0, content : Cow::from( "" ) } );
          }
        }

        transposed.push( row_vec );
      }

      if irow == 0
      {
        first_row_height += transposed.len();
      }

      new_data.extend( transposed );
    }

    WrappedInputExtract
    {
      data: new_data,
      first_row_height,
      column_widthes
    }
  }

  /// Calculate width of the column without wrapping.
  pub fn width_calculate< 'data >
  ( 
    column : &'data Vec< ( Cow< 'data, str >, [ usize; 2 ] ) >
  )
  -> usize
  {
    column.iter().map( |k| 
    {
      string::lines( k.0.as_ref() ).map( |l| l.chars().count() ).max().unwrap_or( 0 )
    } ).max().unwrap_or( 0 )
  }

}

#[ allow( unused_imports ) ]
pub use own::*;

/// Own namespace of the module.
#[ allow( unused_imports ) ]
pub mod own
{
  use super::*;
  #[ doc( inline ) ]
  pub use orphan::*;

  #[ doc( inline ) ]
  pub use
  {
  };

  #[ doc( inline ) ]
  pub use private::
  {
    text_wrap,
    width_calculate,
  };

}

/// Orphan namespace of the module.
#[ allow( unused_imports ) ]
pub mod orphan
{
  use super::*;
  #[ doc( inline ) ]
  pub use exposed::*;
}

/// Exposed namespace of the module.
#[ allow( unused_imports ) ]
pub mod exposed
{
  use super::*;
  pub use super::super::output_format;
}

/// Prelude to use essentials: `use my_module::prelude::*`.
#[ allow( unused_imports ) ]
pub mod prelude
{
  use super::*;
}