fix(mtext): size \S fractions to fill and persist paragraph alignment
Two layout fixes on the same MTEXT (budweiser2018.dwg handle 2609 / 8782). Fraction size: `\H0.7x;\S1/2;\H1.429x;` is how a 70 %-scaled fraction is spelled, so the 0.7 already *is* the fraction's size — the earlier constants shrank each half again on top of it and buried the stack inside one line. A half is now near the run height and the stack stands about twice that: the numerator rises past the cap and the denominator sits on the baseline, the way a fraction is meant to grow past its line rather than be squeezed into it. Alignment persistence: a `\p...;` block stays in force until the next `\p`, so a paragraph opened by a bare `\P` / `\N` inherits it. The parser records only the literal codes, so the second line of a centred or right column (`\pxqc;col2\P≡`) fell back to the left edge while its heading stayed aligned. Layout now carries a paragraph's whole property block forward to any paragraph that sets none of its own — measured, the column symbols land on the same centre / right edge as their headings. Kept in the layout adapter rather than the parser: acadrust faithfully records which paragraphs carry an explicit `\p`, so persistence is a rendering interpretation, and the literal record stays available to an editor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7e1cd6842b
commit
292e23f44f
1 changed files with 62 additions and 21 deletions
|
|
@ -381,7 +381,7 @@ pub fn adapt_mtext_paragraphs(
|
||||||
) -> Vec<MTextLine> {
|
) -> Vec<MTextLine> {
|
||||||
use acadrust::entities::mtext_format::{
|
use acadrust::entities::mtext_format::{
|
||||||
parse_mtext, MTextColor, MTextLineAlignment, MTextParagraphAlignment, MTextScalar,
|
parse_mtext, MTextColor, MTextLineAlignment, MTextParagraphAlignment, MTextScalar,
|
||||||
SpanProperties, StackingType,
|
ParagraphProperties, SpanProperties, StackingType,
|
||||||
};
|
};
|
||||||
|
|
||||||
let entity_height = entity_height.max(1e-6);
|
let entity_height = entity_height.max(1e-6);
|
||||||
|
|
@ -411,23 +411,54 @@ pub fn adapt_mtext_paragraphs(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A `\p...;` block sets the paragraph's alignment/indents/tabs and stays in
|
||||||
|
// force until the next `\p` — a paragraph opened by a bare `\P` / `\N`
|
||||||
|
// inherits it. The parser records only the literal codes (a paragraph with
|
||||||
|
// no `\p` carries none), so persistence is applied here: without it, the
|
||||||
|
// second line of a centred or right column (`\pxqc;col2\P≡`) would fall back
|
||||||
|
// to the left edge while its heading stayed aligned.
|
||||||
|
let has_own_props = |p: &ParagraphProperties| {
|
||||||
|
p.alignment
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|a| !matches!(a, MTextParagraphAlignment::Default))
|
||||||
|
|| p.first_line_indent.is_some()
|
||||||
|
|| p.left_margin.is_some()
|
||||||
|
|| p.right_margin.is_some()
|
||||||
|
|| !p.tab_stops.is_empty()
|
||||||
|
};
|
||||||
|
|
||||||
let mut lines: Vec<MTextLine> = Vec::new();
|
let mut lines: Vec<MTextLine> = Vec::new();
|
||||||
|
let mut carried: Option<(Option<ParagraphAlign>, f32, f32, f32, Vec<TabStop>)> = None;
|
||||||
for para in &doc.paragraphs {
|
for para in &doc.paragraphs {
|
||||||
let props = ¶.properties;
|
let props = ¶.properties;
|
||||||
|
let (align, indent_first, indent_left, indent_right, tab_stops) =
|
||||||
|
if has_own_props(props) || carried.is_none() {
|
||||||
|
let v = (
|
||||||
|
props.alignment.and_then(map_align),
|
||||||
|
props.first_line_indent.unwrap_or(0.0) as f32,
|
||||||
|
props.left_margin.unwrap_or(0.0) as f32,
|
||||||
|
props.right_margin.unwrap_or(0.0) as f32,
|
||||||
|
props
|
||||||
|
.tab_stops
|
||||||
|
.iter()
|
||||||
|
.map(|&p| TabStop {
|
||||||
|
position: p as f32,
|
||||||
|
kind: TabKind::Left,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
carried = Some(v.clone());
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
carried.clone().unwrap()
|
||||||
|
};
|
||||||
let mut line = MTextLine {
|
let mut line = MTextLine {
|
||||||
align: props.alignment.and_then(map_align),
|
align,
|
||||||
starts_column: para.starts_column,
|
starts_column: para.starts_column,
|
||||||
indent_first: props.first_line_indent.unwrap_or(0.0) as f32,
|
indent_first,
|
||||||
indent_left: props.left_margin.unwrap_or(0.0) as f32,
|
indent_left,
|
||||||
indent_right: props.right_margin.unwrap_or(0.0) as f32,
|
indent_right,
|
||||||
tab_stops: props
|
tab_stops,
|
||||||
.tab_stops
|
|
||||||
.iter()
|
|
||||||
.map(|&p| TabStop {
|
|
||||||
position: p as f32,
|
|
||||||
kind: TabKind::Left,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
runs: Vec::new(),
|
runs: Vec::new(),
|
||||||
};
|
};
|
||||||
for span in ¶.spans {
|
for span in ¶.spans {
|
||||||
|
|
@ -558,13 +589,25 @@ pub enum AtomKind {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A `\S` stack is measured against the height of the run it sits in, and that
|
||||||
|
// run has usually already been dropped for the purpose — `\H0.7x;\S1/2;\H1.429x;`
|
||||||
|
// is how a 70 %-scaled fraction is spelled, so the 0.7 *is* the fraction's size.
|
||||||
|
// Shrinking again on top of it is what buries the halves.
|
||||||
|
//
|
||||||
|
// So a half is near the full run height and the stack ends up about twice that:
|
||||||
|
// it is meant to grow past the line, numerator above the cap and denominator on
|
||||||
|
// the baseline — not to be squeezed into one line's worth of height.
|
||||||
|
//
|
||||||
|
// The three must stay ordered — denominator cap, then rule, then numerator
|
||||||
|
// baseline — or the rule cuts through a half.
|
||||||
/// Each half of a `\S` stack, as a fraction of the run's own height.
|
/// Each half of a `\S` stack, as a fraction of the run's own height.
|
||||||
pub const STACK_HALF_SCALE: f32 = 0.55;
|
pub const STACK_HALF_SCALE: f32 = 0.85;
|
||||||
/// Baseline of the numerator, as a fraction of the run's height above the
|
/// Height of the fraction rule above the denominator's baseline, as a fraction
|
||||||
/// denominator's. Leaves a gap the fraction rule sits in.
|
/// of the run's height. Sits just clear of the denominator's cap.
|
||||||
pub const STACK_RAISE: f32 = 0.68;
|
pub const STACK_BAR_Y: f32 = 0.95;
|
||||||
/// Height of the fraction rule above the denominator's baseline.
|
/// Baseline of the numerator above the denominator's, as a fraction of the run's
|
||||||
pub const STACK_BAR_Y: f32 = 0.60;
|
/// height. Clears the rule.
|
||||||
|
pub const STACK_RAISE: f32 = 1.05;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LayoutAtom {
|
pub struct LayoutAtom {
|
||||||
|
|
@ -1692,5 +1735,3 @@ mod v_anchor_tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue