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 <noreply@anthropic.com>
This commit is contained in:
parent
977701b47e
commit
57ff14d3f3
2 changed files with 51 additions and 7 deletions
|
|
@ -707,8 +707,21 @@ impl super::OpenCADStudio {
|
||||||
each(&mut cells[a..b], |p| p.set_strikethrough(on));
|
each(&mut cells[a..b], |p| p.set_strikethrough(on));
|
||||||
}
|
}
|
||||||
MTextFmt::Italic => {
|
MTextFmt::Italic => {
|
||||||
let on = !all_have(&cells[a..b], |p| p.oblique_angle == Some(15.0));
|
// Italic is the editor's canonical 15° oblique. An imported
|
||||||
each(&mut cells[a..b], |p| p.oblique_angle = on.then_some(15.0));
|
// 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 => {
|
MTextFmt::Bold => {
|
||||||
// Real bold: set the font's bold flag (the SDF renderer bakes
|
// Real bold: set the font's bold flag (the SDF renderer bakes
|
||||||
|
|
|
||||||
|
|
@ -527,10 +527,16 @@ pub fn adapt_mtext_paragraphs(
|
||||||
None => 1.0,
|
None => 1.0,
|
||||||
},
|
},
|
||||||
width_mul: p.width_factor.map(|w| w as f32).unwrap_or(1.0),
|
width_mul: p.width_factor.map(|w| w as f32).unwrap_or(1.0),
|
||||||
oblique_rad: p
|
// Explicit `\Q` oblique wins; otherwise the font italic flag
|
||||||
.oblique_angle
|
// (`\f…|i1;`) renders as the same ~15° slant the editor's Italic
|
||||||
.map(|q| (q as f32).to_radians())
|
// button applies, so imported italic text isn't shown upright.
|
||||||
.unwrap_or(0.0),
|
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),
|
tracking: p.tracking.map(|t| t as f32).unwrap_or(1.0),
|
||||||
valign: match p.line_align {
|
valign: match p.line_align {
|
||||||
Some(MTextLineAlignment::Middle) => 1,
|
Some(MTextLineAlignment::Middle) => 1,
|
||||||
|
|
@ -1071,7 +1077,13 @@ 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}' || 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() {
|
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)),
|
||||||
|
|
@ -2009,6 +2021,25 @@ mod adapter_tests {
|
||||||
assert!(d.underline);
|
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]
|
#[test]
|
||||||
fn stacking_keeps_its_halves_apart() {
|
fn stacking_keeps_its_halves_apart() {
|
||||||
// `\S` reaches layout as a stack, not as flattened text: the halves
|
// `\S` reaches layout as a stack, not as flattened text: the halves
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue