--- /dev/null
+use std::{
+ borrow::Cow,
+ fs::File,
+ io::{BufWriter, Write},
+ sync::{Arc, LazyLock},
+};
+
+use enum_map::{Enum, EnumMap};
+
+use super::{
+ driver::Driver,
+ pivot::{Axis2, BorderStyle, Coord2, PivotTable, Rect2, Stroke},
+ render::{Device, Draw, DrawCell, Pager, Params},
+ table::{CellInner, Content},
+ text_line::TextLine,
+ Details, Item,
+};
+
+pub struct TextDriver {
+ file: BufWriter<File>,
+
+ /// Enable bold and underline in output?
+ emphasis: bool,
+
+ /// Page width.
+ width: usize,
+
+ /// Minimum cell size to break across pages.
+ min_hbreak: usize,
+
+ box_chars: &'static BoxChars,
+
+ params: Params,
+ n_objects: usize,
+ lines: Vec<TextLine>,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Enum)]
+enum Line {
+ None,
+ Dashed,
+ Single,
+ Double,
+}
+
+impl From<Stroke> for Line {
+ fn from(stroke: Stroke) -> Self {
+ match stroke {
+ Stroke::None => Self::None,
+ Stroke::Solid | Stroke::Thick | Stroke::Thin => Self::Single,
+ Stroke::Dashed => Self::Dashed,
+ Stroke::Double => Self::Double,
+ }
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Enum)]
+struct Lines {
+ r: Line,
+ b: Line,
+ l: Line,
+ t: Line,
+}
+
+#[derive(Default)]
+struct BoxChars(EnumMap<Lines, char>);
+
+impl BoxChars {
+ fn put(&mut self, r: Line, b: Line, l: Line, chars: [char; 4]) {
+ use Line::*;
+ for (t, c) in [None, Dashed, Single, Double]
+ .into_iter()
+ .zip(chars.into_iter())
+ {
+ self.0[Lines { r, b, l, t }] = c;
+ }
+ }
+ fn get(&self, lines: Lines) -> char {
+ self.0[lines]
+ }
+}
+
+static ASCII_BOX: LazyLock<BoxChars> = LazyLock::new(|| {
+ let mut ascii_box = BoxChars::default();
+ let n = Line::None;
+ let d = Line::Dashed;
+ use Line::{Double as D, Single as S};
+ ascii_box.put(n, n, n, [' ', '|', '|', '#']);
+ ascii_box.put(n, n, d, ['-', '+', '+', '#']);
+ ascii_box.put(n, n, S, ['-', '+', '+', '#']);
+ ascii_box.put(n, n, D, ['=', '#', '#', '#']);
+ ascii_box.put(n, d, n, ['|', '|', '|', '#']);
+ ascii_box.put(n, d, d, ['+', '+', '+', '#']);
+ ascii_box.put(n, d, S, ['+', '+', '+', '#']);
+ ascii_box.put(n, d, D, ['#', '#', '#', '#']);
+ ascii_box.put(n, S, n, ['|', '|', '|', '#']);
+ ascii_box.put(n, S, d, ['+', '+', '+', '#']);
+ ascii_box.put(n, S, S, ['+', '+', '+', '#']);
+ ascii_box.put(n, S, D, ['#', '#', '#', '#']);
+ ascii_box.put(n, D, n, ['#', '#', '#', '#']);
+ ascii_box.put(n, D, d, ['#', '#', '#', '#']);
+ ascii_box.put(n, D, S, ['#', '#', '#', '#']);
+ ascii_box.put(n, D, D, ['#', '#', '#', '#']);
+ ascii_box.put(d, n, n, ['-', '+', '+', '#']);
+ ascii_box.put(d, n, d, ['-', '+', '+', '#']);
+ ascii_box.put(d, n, S, ['-', '+', '+', '#']);
+ ascii_box.put(d, n, D, ['#', '#', '#', '#']);
+ ascii_box.put(d, d, n, ['+', '+', '+', '#']);
+ ascii_box.put(d, d, d, ['+', '+', '+', '#']);
+ ascii_box.put(d, d, S, ['+', '+', '+', '#']);
+ ascii_box.put(d, d, D, ['#', '#', '#', '#']);
+ ascii_box.put(d, S, n, ['+', '+', '+', '#']);
+ ascii_box.put(d, S, d, ['+', '+', '+', '#']);
+ ascii_box.put(d, S, S, ['+', '+', '+', '#']);
+ ascii_box.put(d, S, D, ['#', '#', '#', '#']);
+ ascii_box.put(d, D, n, ['#', '#', '#', '#']);
+ ascii_box.put(d, D, d, ['#', '#', '#', '#']);
+ ascii_box.put(d, D, S, ['#', '#', '#', '#']);
+ ascii_box.put(d, D, D, ['#', '#', '#', '#']);
+ ascii_box.put(S, n, n, ['-', '+', '+', '#']);
+ ascii_box.put(S, n, d, ['-', '+', '+', '#']);
+ ascii_box.put(S, n, S, ['-', '+', '+', '#']);
+ ascii_box.put(S, n, D, ['#', '#', '#', '#']);
+ ascii_box.put(S, d, n, ['+', '+', '+', '#']);
+ ascii_box.put(S, d, d, ['+', '+', '+', '#']);
+ ascii_box.put(S, d, S, ['+', '+', '+', '#']);
+ ascii_box.put(S, d, D, ['#', '#', '#', '#']);
+ ascii_box.put(S, S, n, ['+', '+', '+', '#']);
+ ascii_box.put(S, S, d, ['+', '+', '+', '#']);
+ ascii_box.put(S, S, S, ['+', '+', '+', '#']);
+ ascii_box.put(S, S, D, ['#', '#', '#', '#']);
+ ascii_box.put(S, D, n, ['#', '#', '#', '#']);
+ ascii_box.put(S, D, d, ['#', '#', '#', '#']);
+ ascii_box.put(S, D, S, ['#', '#', '#', '#']);
+ ascii_box.put(S, D, D, ['#', '#', '#', '#']);
+ ascii_box.put(D, n, n, ['=', '#', '#', '#']);
+ ascii_box.put(D, n, d, ['#', '#', '#', '#']);
+ ascii_box.put(D, n, S, ['#', '#', '#', '#']);
+ ascii_box.put(D, n, D, ['=', '#', '#', '#']);
+ ascii_box.put(D, d, n, ['#', '#', '#', '#']);
+ ascii_box.put(D, d, d, ['#', '#', '#', '#']);
+ ascii_box.put(D, d, S, ['#', '#', '#', '#']);
+ ascii_box.put(D, d, D, ['#', '#', '#', '#']);
+ ascii_box.put(D, S, n, ['#', '#', '#', '#']);
+ ascii_box.put(D, S, d, ['#', '#', '#', '#']);
+ ascii_box.put(D, S, S, ['#', '#', '#', '#']);
+ ascii_box.put(D, S, D, ['#', '#', '#', '#']);
+ ascii_box.put(D, D, n, ['#', '#', '#', '#']);
+ ascii_box.put(D, D, d, ['#', '#', '#', '#']);
+ ascii_box.put(D, D, S, ['#', '#', '#', '#']);
+ ascii_box.put(D, D, D, ['#', '#', '#', '#']);
+ ascii_box
+});
+
+static UNICODE_BOX: LazyLock<BoxChars> = LazyLock::new(|| {
+ let mut unicode_box = BoxChars::default();
+ let n = Line::None;
+ let d = Line::Dashed;
+ use Line::{Double as D, Single as S};
+ unicode_box.put(n, n, n, [' ', '╵', '╵', '║']);
+ unicode_box.put(n, n, d, ['╴', '╯', '╯', '╜']);
+ unicode_box.put(n, n, S, ['╴', '╯', '╯', '╜']);
+ unicode_box.put(n, n, D, ['═', '╛', '╛', '╝']);
+ unicode_box.put(n, S, n, ['╷', '│', '│', '║']);
+ unicode_box.put(n, S, d, ['╮', '┤', '┤', '╢']);
+ unicode_box.put(n, S, S, ['╮', '┤', '┤', '╢']);
+ unicode_box.put(n, S, D, ['╕', '╡', '╡', '╣']);
+ unicode_box.put(n, d, n, ['╷', '┊', '│', '║']);
+ unicode_box.put(n, d, d, ['╮', '┤', '┤', '╢']);
+ unicode_box.put(n, d, S, ['╮', '┤', '┤', '╢']);
+ unicode_box.put(n, d, D, ['╕', '╡', '╡', '╣']);
+ unicode_box.put(n, D, n, ['║', '║', '║', '║']);
+ unicode_box.put(n, D, d, ['╖', '╢', '╢', '╢']);
+ unicode_box.put(n, D, S, ['╖', '╢', '╢', '╢']);
+ unicode_box.put(n, D, D, ['╗', '╣', '╣', '╣']);
+ unicode_box.put(d, n, n, ['╶', '╰', '╰', '╙']);
+ unicode_box.put(d, n, d, ['╌', '┴', '┴', '╨']);
+ unicode_box.put(d, n, S, ['─', '┴', '┴', '╨']);
+ unicode_box.put(d, n, D, ['═', '╧', '╧', '╩']);
+ unicode_box.put(d, d, n, ['╭', '├', '├', '╟']);
+ unicode_box.put(d, d, d, ['┬', '+', '┼', '╪']);
+ unicode_box.put(d, d, S, ['┬', '┼', '┼', '╪']);
+ unicode_box.put(d, d, D, ['╤', '╪', '╪', '╬']);
+ unicode_box.put(d, S, n, ['╭', '├', '├', '╟']);
+ unicode_box.put(d, S, d, ['┬', '┼', '┼', '╪']);
+ unicode_box.put(d, S, S, ['┬', '┼', '┼', '╪']);
+ unicode_box.put(d, S, D, ['╤', '╪', '╪', '╬']);
+ unicode_box.put(d, D, n, ['╓', '╟', '╟', '╟']);
+ unicode_box.put(d, D, d, ['╥', '╫', '╫', '╫']);
+ unicode_box.put(d, D, S, ['╥', '╫', '╫', '╫']);
+ unicode_box.put(d, D, D, ['╦', '╬', '╬', '╬']);
+ unicode_box.put(S, n, n, ['╶', '╰', '╰', '╙']);
+ unicode_box.put(S, n, d, ['─', '┴', '┴', '╨']);
+ unicode_box.put(S, n, S, ['─', '┴', '┴', '╨']);
+ unicode_box.put(S, n, D, ['═', '╧', '╧', '╩']);
+ unicode_box.put(S, d, n, ['╭', '├', '├', '╟']);
+ unicode_box.put(S, d, d, ['┬', '┼', '┼', '╪']);
+ unicode_box.put(S, d, S, ['┬', '┼', '┼', '╪']);
+ unicode_box.put(S, d, D, ['╤', '╪', '╪', '╬']);
+ unicode_box.put(S, S, n, ['╭', '├', '├', '╟']);
+ unicode_box.put(S, S, d, ['┬', '┼', '┼', '╪']);
+ unicode_box.put(S, S, S, ['┬', '┼', '┼', '╪']);
+ unicode_box.put(S, S, D, ['╤', '╪', '╪', '╬']);
+ unicode_box.put(S, D, n, ['╓', '╟', '╟', '╟']);
+ unicode_box.put(S, D, d, ['╥', '╫', '╫', '╫']);
+ unicode_box.put(S, D, S, ['╥', '╫', '╫', '╫']);
+ unicode_box.put(S, D, D, ['╦', '╬', '╬', '╬']);
+ unicode_box.put(D, n, n, ['═', '╘', '╘', '╚']);
+ unicode_box.put(D, n, d, ['═', '╧', '╧', '╩']);
+ unicode_box.put(D, n, S, ['═', '╧', '╧', '╩']);
+ unicode_box.put(D, n, D, ['═', '╧', '╧', '╩']);
+ unicode_box.put(D, d, n, ['╒', '╞', '╞', '╠']);
+ unicode_box.put(D, d, d, ['╤', '╪', '╪', '╬']);
+ unicode_box.put(D, d, S, ['╤', '╪', '╪', '╬']);
+ unicode_box.put(D, d, D, ['╤', '╪', '╪', '╬']);
+ unicode_box.put(D, S, n, ['╒', '╞', '╞', '╠']);
+ unicode_box.put(D, S, d, ['╤', '╪', '╪', '╬']);
+ unicode_box.put(D, S, S, ['╤', '╪', '╪', '╬']);
+ unicode_box.put(D, S, D, ['╤', '╪', '╪', '╬']);
+ unicode_box.put(D, D, n, ['╔', '╠', '╠', '╠']);
+ unicode_box.put(D, D, d, ['╠', '╬', '╬', '╬']);
+ unicode_box.put(D, D, S, ['╠', '╬', '╬', '╬']);
+ unicode_box.put(D, D, D, ['╦', '╬', '╬', '╬']);
+ unicode_box
+});
+
+impl TextDriver {
+ fn new(file: File) -> TextDriver {
+ let width = 80;
+ Self {
+ file: BufWriter::new(file),
+ emphasis: false,
+ width,
+ min_hbreak: 20,
+ box_chars: &*ASCII_BOX,
+ n_objects: 0,
+ params: Params {
+ size: Coord2::new(width, usize::MAX),
+ font_size: EnumMap::from_fn(|_| 1),
+ line_widths: EnumMap::from_fn(|stroke| if stroke == Stroke::None { 0 } else { 1 }),
+ px_size: None,
+ min_break: EnumMap::default(),
+ supports_margins: false,
+ rtl: false,
+ printing: true,
+ can_adjust_break: false,
+ can_scale: false,
+ },
+ lines: Vec::new(),
+ }
+ }
+
+ fn output_table(&mut self, table: &PivotTable) {
+ for layer_indexes in table.layers(true) {
+ let pager = Pager::new(todo!(), table, Some(layer_indexes.as_slice()));
+ while pager.has_next() {
+ if self.n_objects > 0 {
+ writeln!(&mut self.file).unwrap();
+ }
+ self.n_objects += 1;
+
+ pager.draw_next(usize::MAX, self);
+ }
+ }
+ }
+}
+
+impl Driver for TextDriver {
+ fn name(&self) -> Cow<'static, str> {
+ Cow::from("text")
+ }
+
+ fn write(&mut self, item: &Arc<Item>) {
+ match &item.details {
+ Details::Chart => todo!(),
+ Details::Image => todo!(),
+ Details::Group(vec) => todo!(),
+ Details::Message(diagnostic) => todo!(),
+ Details::PageBreak => todo!(),
+ Details::Table(pivot_table) => self.output_table(pivot_table),
+ Details::Text(text) => todo!(),
+ }
+ }
+}
+
+impl Draw for TextDriver {
+ fn draw_line(&mut self, bb: Rect2, styles: EnumMap<Axis2, [BorderStyle; 2]>) {
+ todo!()
+ }
+
+ fn draw_cell(
+ &mut self,
+ draw_cell: &DrawCell,
+ alternate_row: bool,
+ bb: &Rect2,
+ valign_offset: usize,
+ spill: EnumMap<Axis2, [usize; 2]>,
+ clip: &Rect2,
+ ) {
+ todo!()
+ }
+
+ fn scale(&mut self, factor: f64) {
+ unreachable!()
+ }
+}
+
+impl Device for TextDriver {
+ fn params(&self) -> &Params {
+ todo!()
+ }
+
+ fn measure_cell_width(&self, cell: &CellInner) -> [usize; 2] {
+ todo!()
+ }
+
+ fn measure_cell_height(&self, cell: &CellInner, width: usize) -> usize {
+ todo!()
+ }
+
+ fn adjust_break(&self, cell: &Content, size: Coord2) -> usize {
+ todo!()
+ }
+}