forked from 1b-agent/opencadstudio-upstream
feat(mtext): align tabbed fields on center/right/decimal tab stops
A `\t` reaching a center, right, or decimal (`D`) tab stop now aligns the field that follows it — the run of words up to the next space/tab — instead of just advancing the pen: - decimal: the field's first `.` lands on the stop (a column of numbers aligns on the decimal point; a field with no `.` aligns its end), - right: the field's end lands on the stop, - center: its middle. The stop wins even when the field is wider than the room before it, so the dot/edge stays put and the field overruns to the left, keeping the column aligned. Left stops and the default 4-em grid are unchanged. Picks up acadrust 162f194, which carries the tab-stop kinds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
0de2f76921
commit
3da678b2ca
2 files changed
+99
-13
No files matched your search
Generated
+1
-1
@@ -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#504281a468f0218ede6a5437e2f0d90d9c7c243f"
|
source = "git+https://github.com/OpenAEC-Foundation/acadifc?branch=main#162f1940ef9f7faaa9b6597e4b190be913b70d37"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash 0.8.12",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|||||||
@@ -244,6 +244,8 @@ pub enum TabKind {
|
|||||||
Left,
|
Left,
|
||||||
Center,
|
Center,
|
||||||
Right,
|
Right,
|
||||||
|
/// Dot-aligned: the text after the tab places its decimal point on the stop.
|
||||||
|
Decimal,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
@@ -482,9 +484,18 @@ pub fn adapt_mtext_paragraphs(
|
|||||||
props
|
props
|
||||||
.tab_stops
|
.tab_stops
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&p| TabStop {
|
.map(|ts| {
|
||||||
position: p as f32,
|
use acadrust::entities::mtext_format::TabStop as ATab;
|
||||||
kind: TabKind::Left,
|
let kind = match ts {
|
||||||
|
ATab::Left(_) => TabKind::Left,
|
||||||
|
ATab::Center(_) => TabKind::Center,
|
||||||
|
ATab::Right(_) => TabKind::Right,
|
||||||
|
ATab::Decimal(_) => TabKind::Decimal,
|
||||||
|
};
|
||||||
|
TabStop {
|
||||||
|
position: ts.position() as f32,
|
||||||
|
kind,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
);
|
);
|
||||||
@@ -1047,15 +1058,22 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
|||||||
MTextRunKind::Glyphs(text) => {
|
MTextRunKind::Glyphs(text) => {
|
||||||
let mut word = String::new();
|
let mut word = String::new();
|
||||||
for ch in text.chars() {
|
for ch in text.chars() {
|
||||||
if ch == ' ' || ch == '\u{00A0}' {
|
if ch == ' ' || ch == '\u{00A0}' || ch == '\t' {
|
||||||
if !word.is_empty() {
|
if !word.is_empty() {
|
||||||
atoms.push(LayoutAtom {
|
atoms.push(LayoutAtom {
|
||||||
kind: AtomKind::Word(std::mem::take(&mut word)),
|
kind: AtomKind::Word(std::mem::take(&mut word)),
|
||||||
state: run.state.clone(),
|
state: run.state.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// A literal tab (acadrust keeps `^I` / `\t` as a tab
|
||||||
|
// char in the span) advances to the paragraph's next
|
||||||
|
// tab stop, aligning the field that follows it.
|
||||||
atoms.push(LayoutAtom {
|
atoms.push(LayoutAtom {
|
||||||
kind: AtomKind::Space,
|
kind: if ch == '\t' {
|
||||||
|
AtomKind::Tab
|
||||||
|
} else {
|
||||||
|
AtomKind::Space
|
||||||
|
},
|
||||||
state: run.state.clone(),
|
state: run.state.clone(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -1446,7 +1464,7 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut cursor_x = cursor_start;
|
let mut cursor_x = cursor_start;
|
||||||
for atom in &sub.atoms {
|
for (ai, atom) in sub.atoms.iter().enumerate() {
|
||||||
match &atom.kind {
|
match &atom.kind {
|
||||||
AtomKind::Word(text) => {
|
AtomKind::Word(text) => {
|
||||||
let run_h = atom.state.height_mul * entity_h;
|
let run_h = atom.state.height_mul * entity_h;
|
||||||
@@ -1737,12 +1755,80 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
|||||||
cursor_x += adv;
|
cursor_x += adv;
|
||||||
}
|
}
|
||||||
AtomKind::Tab => {
|
AtomKind::Tab => {
|
||||||
cursor_x = next_tab_position(
|
// A center/right/decimal stop aligns the field that follows
|
||||||
cursor_x,
|
// the tab (the run of words up to the next space/tab) rather
|
||||||
&sub.tab_stops,
|
// than just advancing the pen. Decimal places the first `.`
|
||||||
sub.indent_left,
|
// on the stop; right the field's end; center its middle.
|
||||||
entity_h,
|
let local = cursor_x - sub.indent_left;
|
||||||
);
|
let stop = sub
|
||||||
|
.tab_stops
|
||||||
|
.iter()
|
||||||
|
.find(|ts| ts.position > local + 1e-4)
|
||||||
|
.copied();
|
||||||
|
match stop.map(|s| s.kind) {
|
||||||
|
Some(TabKind::Center | TabKind::Right | TabKind::Decimal) => {
|
||||||
|
let sx = sub.indent_left + stop.unwrap().position;
|
||||||
|
let mut field_w = 0.0_f32;
|
||||||
|
let mut before_dot: Option<f32> = None;
|
||||||
|
for next in &sub.atoms[ai + 1..] {
|
||||||
|
match &next.kind {
|
||||||
|
AtomKind::Word(t) => {
|
||||||
|
if before_dot.is_none() {
|
||||||
|
if let Some(dp) = t.find('.') {
|
||||||
|
let pre: String = t[..dp].to_string();
|
||||||
|
before_dot = Some(
|
||||||
|
field_w
|
||||||
|
+ measure_word(
|
||||||
|
&pre,
|
||||||
|
&next.state,
|
||||||
|
entity_h,
|
||||||
|
base_wf,
|
||||||
|
&base_font_name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field_w += measure_word(
|
||||||
|
t,
|
||||||
|
&next.state,
|
||||||
|
entity_h,
|
||||||
|
base_wf,
|
||||||
|
&base_font_name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
AtomKind::Stack { .. } => {
|
||||||
|
field_w += atom_width(
|
||||||
|
next,
|
||||||
|
entity_h,
|
||||||
|
base_wf,
|
||||||
|
&base_font_name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let offset = match stop.unwrap().kind {
|
||||||
|
TabKind::Right => field_w,
|
||||||
|
TabKind::Center => field_w * 0.5,
|
||||||
|
// No dot in the field → align its end, as AutoCAD
|
||||||
|
// does for a decimal tab with no decimal point.
|
||||||
|
TabKind::Decimal => before_dot.unwrap_or(field_w),
|
||||||
|
TabKind::Left => 0.0,
|
||||||
|
};
|
||||||
|
// The stop wins even when the field is wider than the
|
||||||
|
// room before it — the dot/edge lands on the stop and
|
||||||
|
// the field overruns to the left, matching AutoCAD.
|
||||||
|
cursor_x = sx - offset;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
cursor_x = next_tab_position(
|
||||||
|
cursor_x,
|
||||||
|
&sub.tab_stops,
|
||||||
|
sub.indent_left,
|
||||||
|
entity_h,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user