diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 09d6ab1..4e84051 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -1376,7 +1376,7 @@ fn blur(sigma: f64) -> Filter { /// Stroke `path` with `line`. A gradient line is laid out over `extent` (the untrimmed outline the /// line belongs to; `path` itself when `None`). #[allow(clippy::too_many_arguments)] -fn stroke( +pub(crate) fn stroke( ctx: &mut RenderContext, rctx: &Ctx, line: &Line, diff --git a/crates/render/src/table.rs b/crates/render/src/table.rs index 685df6f..c84e117 100644 --- a/crates/render/src/table.rs +++ b/crates/render/src/table.rs @@ -348,6 +348,17 @@ pub fn draw( }, } } + // Diagonal borders, under the edges: drawn like any line (dashes, compound, caps). + for (r, c, rect) in &rects { + let Some(cell) = t.cell(*r, *c) else { continue }; + for (line, a, b) in [(&cell.diag_down, (rect.x0, rect.y0), (rect.x1, rect.y1)), (&cell.diag_up, (rect.x0, rect.y1), (rect.x1, rect.y0))] { + let Some(l) = line.as_ref().filter(|l| l.fill.as_ref().is_some_and(|f| !f.is_none())) else { continue }; + let mut p = BezPath::new(); + p.move_to(a); + p.line_to(b); + crate::stroke(ctx, rctx, l, None, &p, None, m, l.width.unwrap_or(1.0).max(0.0), 1.0); + } + } // Borders: explicit cell borders, else the style's grid. for (r, c, rect) in &rects { let Some(cell) = t.cell(*r, *c) else { continue }; @@ -361,11 +372,16 @@ pub fn draw( for (i, e) in edges.iter().enumerate() { let line = cell.borders.get(i).and_then(|b| b.as_ref()); let (col, wd) = match line { - Some(l) => match &l.fill { - Some(Fill::Solid { color: cc }) => (rctx.color(cc, None), l.width.unwrap_or(1.0)), - Some(Fill::None) => continue, - _ => continue, - }, + // An explicit border is drawn like any line: dashed, compound, gradient. + Some(l) => { + if l.fill.as_ref().is_some_and(|f| !f.is_none()) { + let mut p = BezPath::new(); + p.move_to((e.0, e.1)); + p.line_to((e.2, e.3)); + crate::stroke(ctx, rctx, l, None, &p, None, m, l.width.unwrap_or(1.0).max(0.25), 1.0); + } + continue; + } None => match lk.sides.get(i).copied().flatten().or(lk.border) { Some(b) if b.0.a > 0 && b.1 > 0.0 => b, _ => continue, diff --git a/crates/render/src/tests_wild.rs b/crates/render/src/tests_wild.rs index 0581fb2..9a885c9 100644 --- a/crates/render/src/tests_wild.rs +++ b/crates/render/src/tests_wild.rs @@ -580,3 +580,21 @@ fn a_bevelled_shape_is_lit_from_the_upper_left() { let img = render(vec![bevelled(r#""#)]); assert!(close(img.pixel(165, 104), base, 2) && close(img.pixel(165, 165), base, 2)); } + +#[test] +fn table_borders_take_their_dashes_and_diagonals_are_drawn() { + let mut t = deckcraft_model::Table::new(1, 1, 200.0, 100.0); + t.style = "none".into(); + let mut dashed = Line::solid(ColorRef::rgb(RED), 4.0); + dashed.dash = Some(deckcraft_model::style::Dash::Preset { name: "dash".into() }); + if let Some(c) = t.rows.first_mut().and_then(|r| r.cells.first_mut()) { + c.borders[2] = Some(dashed); + c.diag_down = Some(Line::solid(ColorRef::rgb(Rgba::rgb(0, 0, 255)), 4.0)); + } + let sh = Shape { id: ShapeId(27), xfrm: Some(Xfrm::new(100.0, 100.0, 200.0, 100.0)), kind: ShapeKind::Table(t), ..Default::default() }; + let img = render(vec![sh]); + // Dashes of 4 widths (16 pt) on, 3 (12 pt) off along the top edge, from its start. + assert!(close(img.pixel(108, 100), RED, 40) && close(img.pixel(122, 100), WHITE, 30) && close(img.pixel(135, 100), RED, 40)); + // The diagonal from top-left to bottom-right passes through the middle. + assert!(close(img.pixel(200, 150), Rgba::rgb(0, 0, 255), 40), "{:?}", img.pixel(200, 150)); +}