mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-31 22:07:06 -06:00
layout: preserve subpoint table grid widths (#2589)
* 356: preserve subpoint table grid widths * 356: normalize tiny positive grid totals --------- Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
Brandon Thomas
parent
bdb7672c0f
commit
a7abd346e7
@@ -357,7 +357,15 @@ pub fn layout_table(ctx: &mut Ctx, story: StoryRef, t: &Table, path: &[u32], ava
|
||||
let ncols = t.cols().max(1);
|
||||
// Column widths.
|
||||
let mut grid: Vec<f32> = if t.grid.len() == ncols {
|
||||
t.grid.iter().map(|w| if w.is_finite() { w.max(4.0) } else { 72.0 }).collect()
|
||||
// Grid units can be narrower than a text box, including omitted row-end columns.
|
||||
t.grid
|
||||
.iter()
|
||||
.map(|&w| match w {
|
||||
w if !w.is_finite() => 72.0,
|
||||
w if w <= 0.0 => 4.0,
|
||||
w => w,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![avail / ncols as f32; ncols]
|
||||
};
|
||||
@@ -373,8 +381,13 @@ pub fn layout_table(ctx: &mut Ctx, story: StoryRef, t: &Table, path: &[u32], ava
|
||||
_ => None,
|
||||
};
|
||||
if let Some(tw) = target {
|
||||
let k = tw / total.max(1.0);
|
||||
grid.iter_mut().for_each(|w| *w *= k);
|
||||
if total > 0.0 && total < 1.0 {
|
||||
// Divide first: a tiny positive total can overflow the scale factor.
|
||||
grid.iter_mut().for_each(|w| *w = (*w / total) * tw);
|
||||
} else {
|
||||
let k = tw / total.max(1.0);
|
||||
grid.iter_mut().for_each(|w| *w *= k);
|
||||
}
|
||||
total = tw;
|
||||
}
|
||||
if total > avail * 1.5 && !t.props.fixed {
|
||||
|
||||
@@ -8528,3 +8528,87 @@ fn cell_break_only_is_marker() {
|
||||
.unwrap();
|
||||
assert!(para.clusters.iter().all(|c| c.kind == para::ClKind::Marker));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subpoint_grid_keeps_widths() {
|
||||
for gap in [0.05, 0.5, 1.0, 3.0] {
|
||||
let mut d = Document::new();
|
||||
let mut table = Table::new(1, 3, 300.0);
|
||||
table.props.fixed = true;
|
||||
table.props.width = Some(300.0);
|
||||
table.grid = vec![100.0, 200.0 - gap, gap];
|
||||
let row = &mut table.rows[0];
|
||||
row.props.grid_after = 1;
|
||||
row.props.w_after = Some(gap);
|
||||
row.cells.truncate(1);
|
||||
row.cells[0].props.span = 2;
|
||||
row.cells[0].props.width = Some(300.0 - gap);
|
||||
d.body = vec![Arc::new(Block::Table(table))];
|
||||
let l = lay(&d);
|
||||
let width = l.pages[0]
|
||||
.items
|
||||
.iter()
|
||||
.find_map(|item| match item {
|
||||
Placed::Cell { rect, .. } => Some(rect.w),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap();
|
||||
assert!((width - (300.0 - gap)).abs() < 0.001, "gap {gap}: cell width {width}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_grid_keeps_fallback() {
|
||||
for width in [0.0, -1.0, f32::NAN, f32::INFINITY] {
|
||||
let mut d = Document::new();
|
||||
let mut table = Table::new(1, 2, 300.0);
|
||||
table.props.fixed = true;
|
||||
table.props.width = Some(300.0);
|
||||
table.grid = vec![width; 2];
|
||||
for cell in &mut table.rows[0].cells {
|
||||
cell.props.width = None;
|
||||
}
|
||||
d.body = vec![Arc::new(Block::Table(table))];
|
||||
let l = lay(&d);
|
||||
let widths: Vec<_> = l.pages[0]
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
Placed::Cell { rect, .. } => Some(rect.w),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(widths, [150.0, 150.0], "grid {width}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tiny_grid_scales_to_width() {
|
||||
for unit in [0.05, 0.25, 0.5, f32::MIN_POSITIVE, f32::from_bits(1)] {
|
||||
for ratio in [1.0, 3.0] {
|
||||
let mut d = Document::new();
|
||||
let mut table = Table::new(1, 2, 300.0);
|
||||
table.props.fixed = true;
|
||||
table.props.width = Some(300.0);
|
||||
table.grid = vec![unit, unit * ratio];
|
||||
for cell in &mut table.rows[0].cells {
|
||||
cell.props.width = None;
|
||||
}
|
||||
d.body = vec![Arc::new(Block::Table(table))];
|
||||
let l = lay(&d);
|
||||
let widths: Vec<_> = l.pages[0]
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
Placed::Cell { rect, .. } => Some(rect.w),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let first = 300.0 / (1.0 + ratio);
|
||||
assert_eq!(widths.len(), 2);
|
||||
for (width, want) in widths.iter().zip([first, 300.0 - first]) {
|
||||
assert!(width.is_finite() && (width - want).abs() < 0.001, "unit {unit}, ratio {ratio}: {widths:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ Exact line spacing keeps the run font baseline around tall equations; automatic
|
||||
|---|---|---|---|
|
||||
| 2026-10-16 | trivial | Split-cell content retains its resolved top margin and border band on continuation pages, matching original Word Mac16.113.2 controls. Synthetic regressions cover margins, borders, line spacing and image frames. |
|
||||
| Hint-reliant CJK glyph outlines (DFKai-SB / 標楷體) | 🟡 | Required TrueType interpreter runs at units-per-em size; local Windows glyph-bounds regression and synthetic DOCX page/window comparison pass. Other platforms and hint-reliant families remain unmeasured; no native Word comparison. | — |
|
||||
| Subpoint table grid columns | 🟡 | Preserve positive grid widths rather than imposing a text-box minimum; ECMA-376 §17.4.16. Native Word for Mac 16.113.2 build26092012 confirms narrow trailing grid units. `layout::tests::subpoint_grid_keeps_widths`, `tiny_grid_scales_to_width`. Tiny totals normalize without overflowing the scale factor. Zero and invalid widths retain their fallback. Border painting remains separate. | n/a |
|
||||
| Line breaking (UAX #14 with Word's exceptions: no break after `/`) | ✅ | `layout::tests::no_line_break_right_after_a_slash` | — |
|
||||
| Narrow table cells | ✅ | `layout::tests::narrow_cells_keep_their_width`: usable widths below 12 pt wrap without expansion; oversized glyphs still make progress | 0 |
|
||||
| Justification, Word 2013+ space shrinking (compat mode 15) | ✅ | #109 | — |
|
||||
@@ -227,6 +228,7 @@ Footer flow-table strokes reserve their drawn top edge, including the half-width
|
||||
| 2026-10-16 | trivial | Preserve direct TOC number tabs during regeneration |
|
||||
| 2026-10-16 | trivial | Explicit cell page breaks take no line height; original native Word controls in modes 14 and 15 and synthetic cluster/cache regressions. |
|
||||
| 2026-10-16 | trivial | Flow-table top border strokes in footers reserve body clearance; original mode-15 Word controls and a synthetic layout regression. |
|
||||
| 2026-10-16 | trivial | Subpoint grid units retain their declared widths without shrinking adjacent cells; tiny totals normalize without scale-factor overflow |
|
||||
| 2026-10-14 | trivial | Row table border exceptions draw without leaking into other rows |
|
||||
| 2026-10-14 | trivial | Consistent cell widths override a stale table grid |
|
||||
| 2026-10-14 | trivial | Paragraphs inside complex-field instructions take no flow height; empty result paragraphs keep their line. |
|
||||
|
||||
Reference in New Issue
Block a user