forked from 1b-agent/opencadstudio-upstream
fix(mtext): render font italic flag and non-breaking space
Complete the editor/layout support for two MTEXT inline codes that were parsed but not rendered: - `\f…|i1;` set the font italic flag but layout only fed oblique from `\Q`, so imported italic showed upright. Map font.italic to the same 15° slant the editor's Italic button uses (explicit `\Q` still wins), and make that button recognise + clear the flag so imported italic can be toggled off. - `\~` (non-breaking space) was a normal Space atom, so lines could wrap at it. Fold it into the word — it still renders as a space but no longer breaks. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
977701b47e
commit
57ff14d3f3
2 files changed
+51
-7
No files matched your search
+15
-2
@@ -707,8 +707,21 @@ impl super::OpenCADStudio {
|
||||
each(&mut cells[a..b], |p| p.set_strikethrough(on));
|
||||
}
|
||||
MTextFmt::Italic => {
|
||||
let on = !all_have(&cells[a..b], |p| p.oblique_angle == Some(15.0));
|
||||
each(&mut cells[a..b], |p| p.oblique_angle = on.then_some(15.0));
|
||||
// Italic is the editor's canonical 15° oblique. An imported
|
||||
// run may instead carry the font italic flag (`\f…|i1;`);
|
||||
// recognise that as italic too, and clear it on toggle so
|
||||
// the one canonical form is the oblique — otherwise an
|
||||
// imported italic run could never be switched off.
|
||||
let on = !all_have(&cells[a..b], |p| {
|
||||
p.oblique_angle == Some(15.0)
|
||||
|| p.font.as_ref().is_some_and(|f| f.italic)
|
||||
});
|
||||
each(&mut cells[a..b], |p| {
|
||||
p.oblique_angle = on.then_some(15.0);
|
||||
if let Some(f) = p.font.as_mut() {
|
||||
f.italic = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
MTextFmt::Bold => {
|
||||
// Real bold: set the font's bold flag (the SDF renderer bakes
|
||||
|
||||
@@ -527,10 +527,16 @@ pub fn adapt_mtext_paragraphs(
|
||||
None => 1.0,
|
||||
},
|
||||
width_mul: p.width_factor.map(|w| w as f32).unwrap_or(1.0),
|
||||
oblique_rad: p
|
||||
.oblique_angle
|
||||
.map(|q| (q as f32).to_radians())
|
||||
.unwrap_or(0.0),
|
||||
// Explicit `\Q` oblique wins; otherwise the font italic flag
|
||||
// (`\f…|i1;`) renders as the same ~15° slant the editor's Italic
|
||||
// button applies, so imported italic text isn't shown upright.
|
||||
oblique_rad: match p.oblique_angle {
|
||||
Some(q) => (q as f32).to_radians(),
|
||||
None if p.font.as_ref().is_some_and(|f| f.italic) => {
|
||||
15.0_f32.to_radians()
|
||||
}
|
||||
None => 0.0,
|
||||
},
|
||||
tracking: p.tracking.map(|t| t as f32).unwrap_or(1.0),
|
||||
valign: match p.line_align {
|
||||
Some(MTextLineAlignment::Middle) => 1,
|
||||
@@ -1071,7 +1077,13 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
||||
MTextRunKind::Glyphs(text) => {
|
||||
let mut word = String::new();
|
||||
for ch in text.chars() {
|
||||
if ch == ' ' || ch == '\u{00A0}' || ch == '\t' {
|
||||
if ch == '\u{00A0}' {
|
||||
// Non-breaking space (`\~`): keep it inside the word
|
||||
// so the line can't wrap here. It still renders as a
|
||||
// space — the font advances over the ' ' — but the
|
||||
// words it joins stay together.
|
||||
word.push(' ');
|
||||
} else if ch == ' ' || ch == '\t' {
|
||||
if !word.is_empty() {
|
||||
atoms.push(LayoutAtom {
|
||||
kind: AtomKind::Word(std::mem::take(&mut word)),
|
||||
@@ -2009,6 +2021,25 @@ mod adapter_tests {
|
||||
assert!(d.underline);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn font_italic_flag_renders_as_oblique() {
|
||||
// A `\f…|i1;` italic font has no true-italic glyphs in the stroke/SDF
|
||||
// faces, so it must render as the same slant the editor's Italic button
|
||||
// applies (~15°) — not upright.
|
||||
let (_, it) = first_run(&adapt_mtext_paragraphs("\\fArial|i1;x", 2.5, true));
|
||||
assert!(
|
||||
(it.oblique_rad - 15f32.to_radians()).abs() < 1e-4,
|
||||
"italic flag should slant, got {}",
|
||||
it.oblique_rad
|
||||
);
|
||||
// A non-italic font stays upright.
|
||||
let (_, up) = first_run(&adapt_mtext_paragraphs("\\fArial|i0;x", 2.5, true));
|
||||
assert!(up.oblique_rad.abs() < 1e-4, "got {}", up.oblique_rad);
|
||||
// An explicit `\Q` wins over the italic flag (no double slant).
|
||||
let (_, q) = first_run(&adapt_mtext_paragraphs("\\Q30;\\fArial|i1;x", 2.5, true));
|
||||
assert!((q.oblique_rad - 30f32.to_radians()).abs() < 1e-4, "got {}", q.oblique_rad);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stacking_keeps_its_halves_apart() {
|
||||
// `\S` reaches layout as a stack, not as flattened text: the halves
|
||||
|
||||
Reference in new issue
Block a user