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:
Hakan SevenandClaude Opus 4.8 committed 2026-07-19 17:16:41 +03:00
1 parent 977701b47e
commit 57ff14d3f3
2 files changed
+51 -7

No files matched your search

+15 -2
View File
@@ -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
+36 -5
View File
@@ -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