forked from 1b-agent/opencadstudio-upstream
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:
1 parent
3da678b2ca
commit
f808030eb6
2 files changed
+24
-5
No files matched your search
Generated
+1
-1
@@ -73,7 +73,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||
[[package]]
|
||||
name = "acadrust"
|
||||
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 = [
|
||||
"ahash 0.8.12",
|
||||
"anyhow",
|
||||
|
||||
@@ -774,6 +774,10 @@ pub fn wrap_paragraph(
|
||||
let mut cur: Vec<LayoutAtom> = Vec::new();
|
||||
let mut cur_w = 0.0_f32;
|
||||
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_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 { .. } => {
|
||||
let w = atom_width(&atom, entity_h, base_wf, base_font);
|
||||
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)) {
|
||||
cur.pop();
|
||||
}
|
||||
@@ -791,10 +795,12 @@ pub fn wrap_paragraph(
|
||||
cur_w = 0.0;
|
||||
subline_idx += 1;
|
||||
}
|
||||
after_align_tab = false;
|
||||
cur.push(atom);
|
||||
cur_w += w;
|
||||
}
|
||||
AtomKind::Space => {
|
||||
after_align_tab = false;
|
||||
if cur.is_empty() {
|
||||
continue;
|
||||
}
|
||||
@@ -803,6 +809,12 @@ pub fn wrap_paragraph(
|
||||
}
|
||||
AtomKind::Tab => {
|
||||
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)
|
||||
- start_x;
|
||||
let max_w = line_max_w(subline_idx);
|
||||
@@ -814,6 +826,7 @@ pub fn wrap_paragraph(
|
||||
cur.push(atom);
|
||||
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
|
||||
// than just advancing the pen. Decimal places the first `.`
|
||||
// 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
|
||||
.tab_stops
|
||||
.iter()
|
||||
@@ -1767,7 +1786,7 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
||||
.copied();
|
||||
match stop.map(|s| s.kind) {
|
||||
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 before_dot: Option<f32> = None;
|
||||
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,
|
||||
&sub.tab_stops,
|
||||
sub.indent_left,
|
||||
tab_base,
|
||||
entity_h,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user