fix(mtext): tab stops are column-relative and align-aware when wrapping
Two defects in tabbed fields inside a multi-column MText: - Stop positions are relative to the paragraph's own left edge, but the tab logic compared them against the column-absolute `cursor_x`. In a second column `cursor_x` already carries the column offset, so no stop ever matched and the field shot off to the default 4-em grid, way past the page. Measure against the column's `box_left` instead. - A field after a center/right/decimal tab aligns on the stop, reaching only part of its width to the right, but word-wrap flushed it left and wrapped it off the line by its full width — dropping a wide decimal number onto a fresh, un-tabbed line. Don't wrap the field that follows an aligning tab. Picks up acadrust b16137f (decimal stop position parse). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
3da678b2ca
commit
f808030eb6
2 changed files with 24 additions and 5 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -73,7 +73,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "acadrust"
|
name = "acadrust"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/OpenAEC-Foundation/acadifc?branch=main#162f1940ef9f7faaa9b6597e4b190be913b70d37"
|
source = "git+https://github.com/OpenAEC-Foundation/acadifc?branch=main#b16137ff0143bf480edfbc8e12f89068a76316de"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash 0.8.12",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
|
||||||
|
|
@ -774,6 +774,10 @@ pub fn wrap_paragraph(
|
||||||
let mut cur: Vec<LayoutAtom> = Vec::new();
|
let mut cur: Vec<LayoutAtom> = Vec::new();
|
||||||
let mut cur_w = 0.0_f32;
|
let mut cur_w = 0.0_f32;
|
||||||
let mut subline_idx: usize = 0;
|
let mut subline_idx: usize = 0;
|
||||||
|
// The field after a center/right/decimal tab is aligned on the stop, so it
|
||||||
|
// reaches only part of its width to the right — don't wrap it off the line
|
||||||
|
// by its full width as a left-flushed word would be.
|
||||||
|
let mut after_align_tab = false;
|
||||||
let line_start_x = |idx: usize| if idx == 0 { indent_first } else { indent_left };
|
let line_start_x = |idx: usize| if idx == 0 { indent_first } else { indent_left };
|
||||||
let line_max_w = |idx: usize| (rect_w - indent_right - line_start_x(idx)).max(0.0);
|
let line_max_w = |idx: usize| (rect_w - indent_right - line_start_x(idx)).max(0.0);
|
||||||
|
|
||||||
|
|
@ -783,7 +787,7 @@ pub fn wrap_paragraph(
|
||||||
AtomKind::Word(_) | AtomKind::Stack { .. } => {
|
AtomKind::Word(_) | AtomKind::Stack { .. } => {
|
||||||
let w = atom_width(&atom, entity_h, base_wf, base_font);
|
let w = atom_width(&atom, entity_h, base_wf, base_font);
|
||||||
let max_w = line_max_w(subline_idx);
|
let max_w = line_max_w(subline_idx);
|
||||||
if !cur.is_empty() && cur_w + w > max_w {
|
if !cur.is_empty() && cur_w + w > max_w && !after_align_tab {
|
||||||
while matches!(cur.last().map(|a| &a.kind), Some(AtomKind::Space)) {
|
while matches!(cur.last().map(|a| &a.kind), Some(AtomKind::Space)) {
|
||||||
cur.pop();
|
cur.pop();
|
||||||
}
|
}
|
||||||
|
|
@ -791,10 +795,12 @@ pub fn wrap_paragraph(
|
||||||
cur_w = 0.0;
|
cur_w = 0.0;
|
||||||
subline_idx += 1;
|
subline_idx += 1;
|
||||||
}
|
}
|
||||||
|
after_align_tab = false;
|
||||||
cur.push(atom);
|
cur.push(atom);
|
||||||
cur_w += w;
|
cur_w += w;
|
||||||
}
|
}
|
||||||
AtomKind::Space => {
|
AtomKind::Space => {
|
||||||
|
after_align_tab = false;
|
||||||
if cur.is_empty() {
|
if cur.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -803,6 +809,12 @@ pub fn wrap_paragraph(
|
||||||
}
|
}
|
||||||
AtomKind::Tab => {
|
AtomKind::Tab => {
|
||||||
let start_x = line_start_x(subline_idx);
|
let start_x = line_start_x(subline_idx);
|
||||||
|
let local = cur_w + start_x - indent_left;
|
||||||
|
let is_align = tab_stops
|
||||||
|
.iter()
|
||||||
|
.find(|ts| ts.position > local + 1e-4)
|
||||||
|
.map(|ts| !matches!(ts.kind, TabKind::Left))
|
||||||
|
.unwrap_or(false);
|
||||||
let new_w = next_tab_position(cur_w + start_x, tab_stops, indent_left, entity_h)
|
let new_w = next_tab_position(cur_w + start_x, tab_stops, indent_left, entity_h)
|
||||||
- start_x;
|
- start_x;
|
||||||
let max_w = line_max_w(subline_idx);
|
let max_w = line_max_w(subline_idx);
|
||||||
|
|
@ -814,6 +826,7 @@ pub fn wrap_paragraph(
|
||||||
cur.push(atom);
|
cur.push(atom);
|
||||||
cur_w = new_w.min(max_w);
|
cur_w = new_w.min(max_w);
|
||||||
}
|
}
|
||||||
|
after_align_tab = is_align;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1759,7 +1772,13 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
||||||
// the tab (the run of words up to the next space/tab) rather
|
// the tab (the run of words up to the next space/tab) rather
|
||||||
// than just advancing the pen. Decimal places the first `.`
|
// than just advancing the pen. Decimal places the first `.`
|
||||||
// on the stop; right the field's end; center its middle.
|
// on the stop; right the field's end; center its middle.
|
||||||
let local = cursor_x - sub.indent_left;
|
//
|
||||||
|
// Stop positions are relative to the paragraph's own left
|
||||||
|
// edge, so measure against `box_left` (the column's left,
|
||||||
|
// which `cursor_x` already carries) — not the raw indent, or
|
||||||
|
// a second column's tabs miss every stop and shoot off right.
|
||||||
|
let tab_base = box_left + sub.indent_left;
|
||||||
|
let local = cursor_x - tab_base;
|
||||||
let stop = sub
|
let stop = sub
|
||||||
.tab_stops
|
.tab_stops
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -1767,7 +1786,7 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
||||||
.copied();
|
.copied();
|
||||||
match stop.map(|s| s.kind) {
|
match stop.map(|s| s.kind) {
|
||||||
Some(TabKind::Center | TabKind::Right | TabKind::Decimal) => {
|
Some(TabKind::Center | TabKind::Right | TabKind::Decimal) => {
|
||||||
let sx = sub.indent_left + stop.unwrap().position;
|
let sx = tab_base + stop.unwrap().position;
|
||||||
let mut field_w = 0.0_f32;
|
let mut field_w = 0.0_f32;
|
||||||
let mut before_dot: Option<f32> = None;
|
let mut before_dot: Option<f32> = None;
|
||||||
for next in &sub.atoms[ai + 1..] {
|
for next in &sub.atoms[ai + 1..] {
|
||||||
|
|
@ -1824,7 +1843,7 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
||||||
cursor_x = next_tab_position(
|
cursor_x = next_tab_position(
|
||||||
cursor_x,
|
cursor_x,
|
||||||
&sub.tab_stops,
|
&sub.tab_stops,
|
||||||
sub.indent_left,
|
tab_base,
|
||||||
entity_h,
|
entity_h,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue