From: Ben Pfaff Date: Sun, 23 Feb 2025 18:45:17 +0000 (-0800) Subject: work? X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ab21add6e43cd55f15ec932c45c639e9a094b4c4;p=pspp work? --- diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 33edf04474..10d655058b 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -922,6 +922,7 @@ dependencies = [ "smallvec", "thiserror", "unicase", + "unicode-linebreak", "unicode-width", "utf8-decode", "windows-sys 0.48.0", @@ -1342,6 +1343,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.23" diff --git a/rust/pspp/Cargo.toml b/rust/pspp/Cargo.toml index 0536ee4ec0..526073e6bf 100644 --- a/rust/pspp/Cargo.toml +++ b/rust/pspp/Cargo.toml @@ -35,6 +35,7 @@ smallvec = { version = "1.13.2", features = ["const_generics", "write"] } libm = "0.2.11" smallstr = "0.3.0" itertools = "0.14.0" +unicode-linebreak = "0.1.5" [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.48.0", features = ["Win32_Globalization"] } diff --git a/rust/pspp/src/output/render.rs b/rust/pspp/src/output/render.rs index b4937f707e..0e19ccef08 100644 --- a/rust/pspp/src/output/render.rs +++ b/rust/pspp/src/output/render.rs @@ -71,6 +71,13 @@ pub struct Params { pub can_scale: bool, } +impl Params { + /// Returns a small but visible width. + fn em(&self) -> usize { + self.font_size[Axis2::X] + } +} + pub trait Device { fn params(&self) -> &Params; @@ -79,10 +86,10 @@ pub trait Device { /// single word across multiple lines (normally, this is the width of the /// longest word in the cell) and `max_width` is the minimum width required /// to avoid line breaks other than at new-lines. - fn measure_cell_width(&self, cell: &CellInner) -> [usize; 2]; + fn measure_cell_width(&self, cell: &DrawCell) -> [usize; 2]; /// Returns the height required to render `cell` given a width of `width`. - fn measure_cell_height(&self, cell: &CellInner, width: usize) -> usize; + fn measure_cell_height(&self, cell: &DrawCell, width: usize) -> usize; /// Given that there is space measuring `size` to render `cell`, where /// `size.y()` is insufficient to render the entire height of the cell, @@ -95,9 +102,7 @@ pub trait Device { /// Optional. If [RenderParams::can_adjust_break] is false, the rendering /// engine assumes that all breakpoints are acceptable. fn adjust_break(&self, cell: &Content, size: Coord2) -> usize; -} -pub trait Draw { /// Draws a generalized intersection of lines in `bb`. /// /// `styles` is interpreted this way: @@ -146,6 +151,27 @@ pub struct DrawCell<'a> { pub footnotes: &'a [Arc], } +impl<'a> DrawCell<'a> { + fn new(inner: &'a CellInner, table: &'a Table) -> Self { + let (style, subscripts, footnotes) = if let Some(styling) = inner.value.styling.as_ref() { + ( + &styling.style, + styling.subscripts.as_slice(), + styling.footnotes.as_slice(), + ) + } else { + (&table.areas[inner.area], [].as_slice(), [].as_slice()) + }; + Self { + rotate: inner.rotate, + inner: &inner.value.inner, + style, + subscripts, + footnotes, + } + } +} + /// A layout for rendering a specific table on a specific device. /// /// May represent the layout of an entire table presented to [Pager::new], or a @@ -162,7 +188,6 @@ pub struct DrawCell<'a> { /// The vertical cells rendered are the topmost `h[Y]`, then `r[Y]`. /// `n[i]` is the sum of `h[i]` and `r[i].len()`. struct Page { - device: Arc, table: Arc, /// Size of the table in cells. @@ -342,7 +367,7 @@ impl Page { /// The new [Page] will be suitable for rendering on a device whose page /// size is `params.size`, but the caller is responsible for actually /// breaking it up to fit on such a device, using the [Break] abstraction. - fn new(table: Arc
, device: Arc, min_width: usize, look: &Look) -> Self { + fn new(table: Arc
, device: &dyn Device, min_width: usize, look: &Look) -> Self { use Axis2::*; let n = table.n.clone(); @@ -350,7 +375,7 @@ impl Page { // Figure out rule widths. let rules = EnumMap::from_fn(|axis| { (0..n[axis]) - .map(|z| measure_rule(&*table, &*device, axis, z)) + .map(|z| measure_rule(&*device, &*table, axis, z)) .collect::>() }); @@ -364,7 +389,7 @@ impl Page { // multiple columns. let mut unspanned_columns = [vec![0; n.x()], vec![0; n.x()]]; for cell in table.cells().filter(|cell| cell.col_span() == 1) { - let mut w = device.measure_cell_width(cell.inner()); + let mut w = device.measure_cell_width(&DrawCell::new(cell.inner(), &*table)); if device.params().px_size.is_some() { if let Some(region) = table.heading_region(cell.coord) { let wr = &heading_widths[region]; @@ -395,7 +420,7 @@ impl Page { for cell in table.cells().filter(|cell| cell.col_span() > 1) { let rect = cell.rect(); - let w = device.measure_cell_width(cell.inner()); + let w = device.measure_cell_width(&DrawCell::new(cell.inner(), &*table)); for i in 0..2 { distribute_spanned_width( w[i], @@ -458,7 +483,7 @@ impl Page { let rect = cell.rect(); let w = joined_width(&cp_x, rect[X].clone()); - let h = device.measure_cell_height(cell.inner(), w); + let h = device.measure_cell_height(&DrawCell::new(cell.inner(), &*table), w); let row = &mut unspanned_rows[cell.coord.y()]; if h > *row { @@ -471,7 +496,7 @@ impl Page { for cell in table.cells().filter(|cell| cell.row_span() > 1) { let rect = cell.rect(); let w = joined_width(&cp_x, rect[X].clone()); - let h = device.measure_cell_height(cell.inner(), w); + let h = device.measure_cell_height(&DrawCell::new(cell.inner(), &*table), w); distribute_spanned_width( h, &unspanned_rows[rect[Y].clone()], @@ -499,7 +524,6 @@ impl Page { let r = Rect2::new(h[X]..n[X], h[Y]..n[Y]); let maps = Self::new_mappings(h, &r); Self { - device, table, n, h, @@ -799,7 +823,6 @@ impl Page { let maps = Self::new_mappings(h, &r); Arc::new(Self { - device: self.device.clone(), table: self.table.clone(), n, h, @@ -815,23 +838,23 @@ impl Page { self.cp[axis].last().copied().unwrap() } - fn draw(&self, draw: &mut dyn Draw, ofs: Coord2) { + fn draw(&self, device: &mut dyn Device, ofs: Coord2) { use Axis2::*; self.draw_cells( + device, ofs, - draw, Rect2::new(0..self.n[X] * 2 + 1, 0..self.n[Y] * 2 + 1), ); } - fn draw_cells(&self, ofs: Coord2, draw: &mut dyn Draw, cells: Rect2) { + fn draw_cells(&self, device: &mut dyn Device, ofs: Coord2, cells: Rect2) { use Axis2::*; for y in cells[Y].clone() { let mut x = cells[X].start; while x < cells[X].end { if !is_rule(x) && !is_rule(y) { let cell = self.get_cell(Coord2::new(x, y)); - self.draw_cell(ofs, draw, &cell); + self.draw_cell(device, ofs, &cell); x = rule_ofs(cell.rect[X].end); } else { x += 1; @@ -839,17 +862,16 @@ impl Page { } } - /* - for y in cells[Y] { - for x in cells[X] { + for y in cells[Y].clone() { + for x in cells[X].clone() { if is_rule(x) && is_rule(y) { - self.draw_rule(ofs, draw, Coord2::new(x, y)); + self.draw_rule(device, ofs, Coord2::new(x, y)); } } - }*/ + } } - fn draw_rule(&self, ofs: Coord2, draw: &mut dyn Draw, coord: Coord2) { + fn draw_rule(&self, device: &mut dyn Device, ofs: Coord2, coord: Coord2) { const NO_BORDER: BorderStyle = BorderStyle::none(); let styles = EnumMap::from_fn(|a: Axis2| { let b = !a; @@ -886,7 +908,7 @@ impl Page { { let bb = Rect2::from_fn(|a| self.cp[a][coord[a]]..self.cp[a][coord[a] + 1]).translate(ofs); - draw.draw_line(bb, styles); + device.draw_line(bb, styles); } } @@ -905,31 +927,14 @@ impl Page { } } - fn extra_height(&self, bb: &Rect2, inner: &CellInner) -> usize { + fn extra_height(&self, device: &dyn Device, bb: &Rect2, cell: &DrawCell) -> usize { use Axis2::*; - let height = self.device.measure_cell_height(inner, bb[X].len()); + let height = device.measure_cell_height(cell, bb[X].len()); usize::saturating_sub(bb[Y].len(), height) } - fn draw_cell(&self, ofs: Coord2, draw: &mut dyn Draw, cell: &RenderCell) { + fn draw_cell(&self, device: &mut dyn Device, ofs: Coord2, cell: &RenderCell) { use Axis2::*; - let inner = cell.content.inner(); - let (style, subscripts, footnotes) = if let Some(styling) = inner.value.styling.as_ref() { - ( - &styling.style, - styling.subscripts.as_slice(), - styling.footnotes.as_slice(), - ) - } else { - (&self.table.areas[inner.area], [].as_slice(), [].as_slice()) - }; - let draw_cell = DrawCell { - rotate: inner.rotate, - inner: &inner.value.inner, - style, - subscripts, - footnotes, - }; let mut bb = Rect2::from_fn(|a| { self.cp[a][cell.rect[a].start * 2 + 1]..self.cp[a][cell.rect[a].end * 2] }) @@ -964,17 +969,17 @@ impl Page { bb.clone() }; - let valign_offset = match style.cell_style.vert_align { - VertAlign::Top => 0, - VertAlign::Middle => self.extra_height(&bb, inner) / 2, - VertAlign::Bottom => self.extra_height(&bb, inner), - }; - // Header rows are never alternate rows. let alternate_row = usize::checked_sub(cell.rect[Y].start, self.h[Y]).is_some_and(|row| row % 2 == 1); - draw.draw_cell(&draw_cell, alternate_row, &bb, valign_offset, spill, &clip) + let draw_cell = DrawCell::new(cell.content.inner(), &*self.table); + let valign_offset = match draw_cell.style.cell_style.vert_align { + VertAlign::Top => 0, + VertAlign::Middle => self.extra_height(device, &bb, &draw_cell) / 2, + VertAlign::Bottom => self.extra_height(device, &bb, &draw_cell), + }; + device.draw_cell(&draw_cell, alternate_row, &bb, valign_offset, spill, &clip) } } @@ -1097,7 +1102,7 @@ fn distribute_spanned_width( /// Returns the width of the rule in `table` that is at offset `z` along axis /// `a`, if rendered on `device`. -fn measure_rule(table: &Table, device: &dyn Device, a: Axis2, z: usize) -> usize { +fn measure_rule(device: &dyn Device, table: &Table, a: Axis2, z: usize) -> usize { let b = !a; // Determine the types of rules that are present. @@ -1213,29 +1218,25 @@ impl Break { /// completely broken up, or if `size` is too small to reasonably render any /// cells. The latter will never happen if `size` is at least as large as /// the page size passed to [Page::new] along the axis using for breaking. - fn next(&mut self, size: usize) -> Option> { + fn next(&mut self, device: &dyn Device, size: usize) -> Option> { if !self.has_next() { return None; } - self.find_breakpoint(size).map(|(z, pixel)| match pixel { - 0 => self.page.select(self.axis, self.z..z, self.pixel, 0), - pixel => self.page.select( - self.axis, - self.z..z + 1, - pixel, - self.page.cell_width(self.axis, z) - pixel, - ), - }) - } - - /// Returns a small but visible width. - fn em(&self) -> usize { - self.page.device.params().font_size[Axis2::X] + self.find_breakpoint(device, size) + .map(|(z, pixel)| match pixel { + 0 => self.page.select(self.axis, self.z..z, self.pixel, 0), + pixel => self.page.select( + self.axis, + self.z..z + 1, + pixel, + self.page.cell_width(self.axis, z) - pixel, + ), + }) } - fn break_cell(&self, z: usize, overflow: usize) -> usize { - if self.cell_is_breakable(z) { + fn break_cell(&self, device: &dyn Device, z: usize, overflow: usize) -> usize { + if self.cell_is_breakable(device, z) { // If there is no right header and we render a partial cell // on the right side of the body, then we omit the rightmost // rule of the body. Otherwise the rendering is deceptive @@ -1270,7 +1271,7 @@ impl Break { // If there would be only a tiny amount of the cell left // after rendering it partially, reduce the amount rendered // slightly to make the output look a little better. - let em = self.em(); + let em = device.params().em(); if pixel + em > cell_size { pixel = pixel.saturating_sub(em); } @@ -1280,11 +1281,11 @@ impl Break { // the exact number of pixels available, which might look // bad e.g. because it breaks in the middle of a line of // text. - if self.axis == Axis2::Y && self.page.device.params().can_adjust_break { + if self.axis == Axis2::Y && device.params().can_adjust_break { let mut x = 0; while x < self.page.n[Axis2::X] { let cell = self.page.get_cell(Coord2::new(x, z)); - let better_pixel = self.page.device.adjust_break( + let better_pixel = device.adjust_break( cell.content, Coord2::new( self.page @@ -1313,11 +1314,11 @@ impl Break { } } - fn find_breakpoint(&mut self, size: usize) -> Option<(usize, usize)> { + fn find_breakpoint(&mut self, device: &dyn Device, size: usize) -> Option<(usize, usize)> { for z in self.z..self.page.n[self.axis] { let needed = self.needed_size(z + 1); if needed > size { - let pixel = self.break_cell(z, needed - size); + let pixel = self.break_cell(device, z, needed - size); if z == self.z && pixel == 0 { return None; } else { @@ -1333,13 +1334,12 @@ impl Break { /// /// This is just a heuristic. Breaking cells across page boundaries can /// save space, but it looks ugly. - fn cell_is_breakable(&self, cell: usize) -> bool { - self.page.cell_width(self.axis, cell) >= self.page.device.params().min_break[self.axis] + fn cell_is_breakable(&self, device: &dyn Device, cell: usize) -> bool { + self.page.cell_width(self.axis, cell) >= device.params().min_break[self.axis] } } pub struct Pager { - device: Arc, scale: f64, /// [Page]s to be rendered, in order, vertically. There may be up to 5 @@ -1353,7 +1353,7 @@ pub struct Pager { impl Pager { pub fn new( - device: Arc, + device: &dyn Device, pivot_table: &PivotTable, layer_indexes: Option<&[usize]>, ) -> Self { @@ -1364,7 +1364,7 @@ impl Pager { // Figure out the width of the body of the table. Use this to determine // the base scale. - let body_page = Page::new(Arc::new(output.body), device.clone(), 0, &pivot_table.look); + let body_page = Page::new(Arc::new(output.body), device, 0, &pivot_table.look); let body_width = body_page.width(Axis2::X); let mut scale = if body_width > device.params().size[Axis2::X] && pivot_table.look.shrink_to_fit[Axis2::X] @@ -1379,7 +1379,7 @@ impl Pager { for table in [output.title, output.layers].into_iter().flatten() { pages.push(Arc::new(Page::new( Arc::new(table), - device.clone(), + device, body_width, &pivot_table.look, ))); @@ -1388,7 +1388,7 @@ impl Pager { for table in [output.caption, output.footnotes].into_iter().flatten() { pages.push(Arc::new(Page::new( Arc::new(table), - device.clone(), + device, 0, &pivot_table.look, ))); @@ -1416,7 +1416,6 @@ impl Pager { } Self { - device, scale, pages, x_break: None, @@ -1425,7 +1424,7 @@ impl Pager { } /// True if there's content left to rnder. - pub fn has_next(&mut self) -> bool { + pub fn has_next(&mut self, device: &dyn Device) -> bool { while self .y_break .as_mut() @@ -1436,7 +1435,8 @@ impl Pager { .as_mut() .and_then(|x_break| { x_break.next( - (self.device.params().size[Axis2::X] as f64 / self.scale as f64) as usize, + device, + (device.params().size[Axis2::X] as f64 / self.scale as f64) as usize, ) }) .map(|page| Break::new(page, Axis2::Y)); @@ -1455,27 +1455,27 @@ impl Pager { /// Returns the amount of vertical space actually used by the rendered /// chunk, which will be 0 if `space` is too small to render anything or if /// no content remains (use [Self::has_next] to distinguish these cases). - pub fn draw_next(&mut self, mut space: usize, draw: &mut dyn Draw) -> usize { + pub fn draw_next(&mut self, device: &mut dyn Device, mut space: usize) -> usize { use Axis2::*; if self.scale != 1.0 { - draw.scale(self.scale); + device.scale(self.scale); space = (space as f64 / self.scale) as usize; } let mut ofs = Coord2::new(0, 0); let mut n_pages = None; - while self.has_next() && n_pages == Some(self.pages.len()) { + while self.has_next(device) && n_pages == Some(self.pages.len()) { n_pages = Some(self.pages.len()); let Some(page) = self .y_break .as_mut() - .and_then(|y_break| y_break.next(space - ofs[Y])) + .and_then(|y_break| y_break.next(device, space - ofs[Y])) else { break; }; - page.draw(draw, ofs); + page.draw(device, ofs); ofs[Y] += page.total_size(Y); } diff --git a/rust/pspp/src/output/text.rs b/rust/pspp/src/output/text.rs index 256dc8bdb3..50e4eaa9d6 100644 --- a/rust/pspp/src/output/text.rs +++ b/rust/pspp/src/output/text.rs @@ -2,15 +2,20 @@ use std::{ borrow::Cow, fs::File, io::{BufWriter, Write}, + ops::Range, sync::{Arc, LazyLock}, }; use enum_map::{Enum, EnumMap}; +use unicode_linebreak::{linebreaks, BreakOpportunity}; +use unicode_width::UnicodeWidthStr; + +use crate::output::pivot::DisplayValue; use super::{ driver::Driver, pivot::{Axis2, BorderStyle, Coord2, PivotTable, Rect2, Stroke}, - render::{Device, Draw, DrawCell, Pager, Params}, + render::{Device, DrawCell, Pager, Params}, table::{CellInner, Content}, text_line::TextLine, Details, Item, @@ -252,16 +257,176 @@ impl TextDriver { 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() { + let mut pager = Pager::new(self, table, Some(layer_indexes.as_slice())); + while pager.has_next(self) { if self.n_objects > 0 { writeln!(&mut self.file).unwrap(); } self.n_objects += 1; - pager.draw_next(usize::MAX, self); + pager.draw_next(self, usize::MAX); + } + } + } + + fn layout_cell(&self, cell: &DrawCell, mut text: &str, bb: Rect2) -> Coord2 { + if text.is_empty() { + return Coord2::default(); + } + + /* + let mut breaks = linebreaks(text); + let bb_w = bb[Axis2::X].len(); + let bb_h = bb[Axis2::Y].len(); + let mut pos = 0; + for _ in 0..bb_h { + let mut w = 0; + loop { + let (index, opportunity) = breaks.next().unwrap(); + match opportunity { + BreakOpportunity::Mandatory => break index, + BreakOpportunity::Allowed => { + let segment_width = text[pos..index].width(); + if w > 0 && w + segment_width > bb_w { + break index; + } + pos = index; + w += segment_width; + } + } + } + todo!() + } + */ + todo!() + } +} + +struct LineBreaks<'a, B> +where + B: Iterator + Clone + 'a, +{ + text: &'a str, + max_width: usize, + indexes: Range, + width: usize, + saved: Option<(usize, BreakOpportunity)>, + breaks: B, +} + +impl<'a, B> Iterator for LineBreaks<'a, B> +where + B: Iterator + Clone + 'a, +{ + type Item = &'a str; + + fn next(&mut self) -> Option { + while let Some((postindex, opportunity)) = self.saved.take().or_else(|| self.breaks.next()) + { + let index = if postindex != self.text.len() { + self.text[..postindex].char_indices().next_back().unwrap().0 + } else { + postindex + }; + println!("index={index} {:?}", &self.text[index..]); + if index <= self.indexes.end { + dbg!(); + continue; + } + + let segment_width = self.text[self.indexes.end..index].width(); + if self.width == 0 || self.width + segment_width <= self.max_width { + dbg!(); + // Add this segment to the current line. + self.width += segment_width; + self.indexes.end = index; + + // If this was a new-line, we're done. + if opportunity == BreakOpportunity::Mandatory { + dbg!(); + let segment = self.text[self.indexes.clone()].trim_end_matches('\n'); + self.indexes = postindex..postindex; + self.width = 0; + return Some(segment); + } + } else { + // Won't fit. Return what we've got and save this segment for next time. + // + // We trim trailing spaces from the line we return, and leading + // spaces from the position where we resume. + dbg!(); + let segment = self.text[self.indexes.clone()].trim_end(); + + let start = self.text[self.indexes.end..].trim_start_matches([' ', '\t']); + let start_index = self.text.len() - start.len(); + self.indexes = start_index..start_index; + self.width = 0; + self.saved = Some((postindex, opportunity)); + return Some(segment); } } + None + } +} + +fn new_line_breaks<'a>( + text: &'a str, + width: usize, +) -> LineBreaks<'a, impl Iterator + Clone + 'a> { + LineBreaks { + text, + max_width: width, + indexes: 0..0, + width: 0, + saved: None, + breaks: linebreaks(text), + } +} + +#[cfg(test)] +mod test { + use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; + + use crate::output::text::new_line_breaks; + + #[test] + fn unicode_width() { + // `\n` is a control character, so [UnicodeWidthChar] considers it to + // have no width. + assert_eq!('\n'.width(), None); + + // But [UnicodeWidthStr] has a different idea. + assert_eq!("\n".width(), 1); + assert_eq!("\r\n".width(), 1); + } + + #[track_caller] + fn test_line_breaks(input: &str, width: usize, expected: Vec<&str>) { + let actual = new_line_breaks(input, width).collect::>(); + if expected != actual { + panic!("filling {input:?} to {width} columns:\nexpected: {expected:?}\nactual: {actual:?}"); + } + } + #[test] + fn line_breaks() { + for width in 0..=6 { + test_line_breaks("abc def ghi", width, vec!["abc", "def", "ghi"]); + } + for width in 7..=10 { + test_line_breaks("abc def ghi", width, vec!["abc def", "ghi"]); + } + test_line_breaks("abc def ghi", 11, vec!["abc def ghi"]); + + for width in 0..=6 { + test_line_breaks("abc def ghi", width, vec!["abc", "def", "ghi"]); + } + test_line_breaks("abc def ghi", 7, vec!["abc", "def ghi"]); + for width in 8..=11 { + test_line_breaks("abc def ghi", width, vec!["abc def", "ghi"]); + } + test_line_breaks("abc def ghi", 12, vec!["abc def ghi"]); + + test_line_breaks("abc\ndef\nghi", 2, vec!["abc", "def", "ghi"]); } } @@ -283,7 +448,32 @@ impl Driver for TextDriver { } } -impl Draw for TextDriver { +impl Device for TextDriver { + fn params(&self) -> &Params { + &self.params + } + + fn measure_cell_width(&self, cell: &DrawCell) -> [usize; 2] { + let text = cell + .inner + .display(() /* XXX */) + .with_font_style(&cell.style.font_style) + .with_subscripts(cell.subscripts) + .with_footnotes(cell.footnotes) + .to_string(); + let max_width = self.layout_cell(cell, &text, Rect2::new(0..usize::MAX, 0..usize::MAX)); + let min_width = self.layout_cell(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 { + todo!() + } + + fn adjust_break(&self, cell: &Content, size: Coord2) -> usize { + unreachable!() + } + fn draw_line(&mut self, bb: Rect2, styles: EnumMap) { todo!() } @@ -301,24 +491,6 @@ impl Draw for TextDriver { } 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!() } }