text driver is finished?
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 8 Mar 2025 20:52:24 +0000 (12:52 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 8 Mar 2025 20:52:24 +0000 (12:52 -0800)
rust/pspp/src/output/text.rs
rust/pspp/src/output/text_line.rs

index 778f49292dd2abd50dc522623de7e14320e50376..1a70a9769e7e869ffa485327834e89de037d0f6b 100644 (file)
@@ -2,7 +2,7 @@ use std::{
     borrow::Cow,
     fs::File,
     io::{BufWriter, Write},
-    ops::Range,
+    ops::{Index, Range},
     sync::{Arc, LazyLock},
 };
 
@@ -78,8 +78,13 @@ impl BoxChars {
             self.0[Lines { r, b, l, t }] = c;
         }
     }
-    fn get(&self, lines: Lines) -> char {
-        self.0[lines]
+}
+
+impl Index<Lines> for BoxChars {
+    type Output = char;
+
+    fn index(&self, lines: Lines) -> &Self::Output {
+        &self.0[lines]
     }
 }
 
@@ -275,15 +280,15 @@ impl TextDriver {
             .with_footnotes(cell.footnotes)
     }
 
-    fn layout_cell(&self, cell: &DrawCell, mut text: &str, bb: Rect2, clip: Rect2) -> Coord2 {
+    fn layout_cell(&self, text: &str, bb: Rect2) -> Coord2 {
         if text.is_empty() {
             return Coord2::default();
         }
 
         use Axis2::*;
-        let mut breaks = new_line_breaks(text, bb[X].len());
+        let breaks = new_line_breaks(text, bb[X].len());
         let mut size = Coord2::new(0, 0);
-        for (text, _y) in breaks.zip(bb[Y].clone()) {
+        for text in breaks.take(bb[Y].len()) {
             let width = text.width();
             if width > size[X] {
                 size[X] = width;
@@ -293,10 +298,11 @@ impl TextDriver {
         size
     }
 
-    fn reserve(&mut self, y: usize) {
+    fn get_line(&mut self, y: usize) -> &mut TextLine {
         if y >= self.lines.len() {
             self.lines.resize(y + 1, TextLine::new());
         }
+        &mut self.lines[y]
     }
 }
 
@@ -438,10 +444,10 @@ impl Driver for TextDriver {
             Details::Chart => todo!(),
             Details::Image => todo!(),
             Details::Group(_) => todo!(),
-            Details::Message(diagnostic) => todo!(),
+            Details::Message(_diagnostic) => todo!(),
             Details::PageBreak => (),
             Details::Table(pivot_table) => self.output_table(pivot_table),
-            Details::Text(text) => todo!(),
+            Details::Text(_text) => todo!(),
         }
     }
 }
@@ -453,38 +459,39 @@ impl Device for TextDriver {
 
     fn measure_cell_width(&self, cell: &DrawCell) -> [usize; 2] {
         let text = Self::display_cell(cell).to_string();
-        let max_width = self.layout_cell(
-            cell,
-            &text,
-            Rect2::new(0..usize::MAX, 0..usize::MAX),
-            Rect2::default(),
-        );
-        let min_width = self.layout_cell(
-            cell,
-            &text,
-            Rect2::new(0..1, 0..usize::MAX),
-            Rect2::default(),
-        );
+        let max_width = self.layout_cell(&text, Rect2::new(0..usize::MAX, 0..usize::MAX));
+        let min_width = self.layout_cell(&text, Rect2::new(0..1, 0..usize::MAX));
         [min_width.x(), max_width.x()]
     }
 
     fn measure_cell_height(&self, cell: &DrawCell, width: usize) -> usize {
         let text = Self::display_cell(cell).to_string();
-        self.layout_cell(
-            cell,
-            &text,
-            Rect2::new(0..width, 0..usize::MAX),
-            Rect2::default(),
-        )
-        .y()
+        self.layout_cell(&text, Rect2::new(0..width, 0..usize::MAX))
+            .y()
     }
 
-    fn adjust_break(&self, cell: &Content, size: Coord2) -> usize {
+    fn adjust_break(&self, _cell: &Content, _size: Coord2) -> usize {
         unreachable!()
     }
 
     fn draw_line(&mut self, bb: Rect2, styles: EnumMap<Axis2, [BorderStyle; 2]>) {
-        todo!()
+        use Axis2::*;
+        let x = bb[X].start.max(0)..bb[X].end.min(self.width);
+        let y = bb[Y].start.max(0)..bb[Y].end;
+        if x.is_empty() || x.end >= self.width {
+            return;
+        }
+
+        let lines = Lines {
+            l: styles[X][0].stroke.into(),
+            r: styles[X][1].stroke.into(),
+            t: styles[Y][0].stroke.into(),
+            b: styles[Y][1].stroke.into(),
+        };
+        let c = self.box_chars[lines];
+        for y in y {
+            self.get_line(y).put_multiple(x.start, c, x.len());
+        }
     }
 
     fn draw_cell(
@@ -493,7 +500,7 @@ impl Device for TextDriver {
         _alternate_row: bool,
         bb: &Rect2,
         valign_offset: usize,
-        spill: EnumMap<Axis2, [usize; 2]>,
+        _spill: EnumMap<Axis2, [usize; 2]>,
         clip: &Rect2,
     ) {
         let display = Self::display_cell(cell);
@@ -505,7 +512,7 @@ impl Device for TextDriver {
             .unwrap_or_else(|| HorzAlign::for_mixed(display.var_type()));
 
         use Axis2::*;
-        let mut breaks = new_line_breaks(&text, bb[X].len());
+        let breaks = new_line_breaks(&text, bb[X].len());
         for (text, y) in breaks.zip(bb[Y].start + valign_offset..bb[Y].end) {
             let width = text.width();
             if !clip[Y].contains(&y) {
@@ -521,12 +528,11 @@ impl Device for TextDriver {
                 continue;
             };
 
-            self.reserve(y);
-            self.lines[y].put(x, text);
+            self.get_line(y).put(x, text);
         }
     }
 
-    fn scale(&mut self, factor: f64) {
-        todo!()
+    fn scale(&mut self, _factor: f64) {
+        unimplemented!()
     }
 }
index 21e9e9a395399afc546886d0e9d19a5a5b013e9e..b861da2df3823184148d3662a6cbe62bc1cf7e70 100644 (file)
@@ -43,15 +43,17 @@ impl TextLine {
         self.width = x;
     }
 
-    pub fn put(&mut self, x0: usize, s: &str) {
-        let w = Widths::new(s).sum::<usize>();
+    fn put_closure<F>(&mut self, x0: usize, w: usize, push_str: F)
+    where
+        F: FnOnce(&mut String),
+    {
         let x1 = x0 + w;
         if w == 0 {
             // Nothing to do.
         } else if x0 >= self.width {
             // The common case: adding new characters at the end of a line.
             self.string.extend((self.width..x0).map(|_| ' '));
-            self.string.push_str(s);
+            push_str(&mut self.string);
             self.width = x1;
         } else if x1 >= self.width {
             let p0 = self.find_pos(x0);
@@ -60,23 +62,31 @@ impl TextLine {
             // replace its first character width by `?`.
             self.string.truncate(p0.offsets.start);
             self.string.extend((p0.columns.start..x0).map(|_| '?'));
-            self.string.push_str(s);
+            push_str(&mut self.string);
             self.width = x1;
         } else {
             let span = self.find_span(x0, x1);
-            if span.columns.start < x0 || span.columns.end > x1 {
-                let tail = self.string.split_off(span.offsets.end);
-                self.string.truncate(span.offsets.start);
-                self.string.extend((span.columns.start..x0).map(|_| '?'));
-                self.string.push_str(s);
-                self.string.extend((x1..span.columns.end).map(|_| '?'));
-                self.string.push_str(&tail);
-            } else {
-                self.string.replace_range(span.offsets, s);
-            }
+            let tail = self.string.split_off(span.offsets.end);
+            self.string.truncate(span.offsets.start);
+            self.string.extend((span.columns.start..x0).map(|_| '?'));
+            push_str(&mut self.string);
+            self.string.extend((x1..span.columns.end).map(|_| '?'));
+            self.string.push_str(&tail);
         }
     }
 
+    pub fn put(&mut self, x0: usize, s: &str) {
+        self.string.reserve(s.len());
+        self.put_closure(x0, Widths::new(s).sum(), |dst| dst.push_str(s));
+    }
+
+    pub fn put_multiple(&mut self, x0: usize, c: char, n: usize) {
+        self.string.reserve(c.len_utf8() * n);
+        self.put_closure(x0, c.width().unwrap() * n, |dst| {
+            (0..n).for_each(|_| dst.push(c))
+        });
+    }
+
     fn find_span(&self, x0: usize, x1: usize) -> Position {
         debug_assert!(x1 > x0);
         let p0 = self.find_pos(x0);