fix(mtext): show preview glyphs and make Enter break the line
Two defects in the in-app MTEXT editor, both surfaced once the preview text became visible: 1. Invisible preview text. After the SDF text migration, tessellate emits glyphs as SDF quads on `text_verts` and leaves the polyline `points` empty, but the editor preview draws on a 2D canvas that can't run the SDF shader — so only the selection box showed. Add a `force_text_strokes` parameter to tessellate: when set, TEXT/MTEXT run-groups also emit their glyph outline strokes as points. The preview passes true (real stroke font, aligned with glyph_boxes); every other caller passes false and keeps SDF-only text. 2. Enter did not start a new line. mtext_type inserted a literal newline, but the MTEXT layout only breaks on the `\P` code (a raw 0x0A is a break only to the caret logic). Normalise every literal newline — from Enter or a multi-line paste — to `\P`, so the line breaks and the saved value stays standard. Closes #308 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
cf4074b0f2
commit
d37dabb0fb
6 changed files with 33 additions and 2 deletions
|
|
@ -409,6 +409,9 @@ impl super::OpenCADStudio {
|
||||||
anno,
|
anno,
|
||||||
None,
|
None,
|
||||||
bg,
|
bg,
|
||||||
|
// Editor preview draws on a 2D canvas with no SDF shader — force the
|
||||||
|
// glyph outline strokes so the text is visible (#308).
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
// Per-character boxes share the preview wires' absolute coordinate frame.
|
// Per-character boxes share the preview wires' absolute coordinate frame.
|
||||||
let boxes = crate::entities::mtext::glyph_boxes(&mt, &self.tabs[i].scene.document);
|
let boxes = crate::entities::mtext::glyph_boxes(&mt, &self.tabs[i].scene.document);
|
||||||
|
|
@ -509,6 +512,21 @@ impl super::OpenCADStudio {
|
||||||
|
|
||||||
/// Insert text (or replace the current selection) at the preview caret.
|
/// Insert text (or replace the current selection) at the preview caret.
|
||||||
pub(super) fn mtext_type(&mut self, s: &str) {
|
pub(super) fn mtext_type(&mut self, s: &str) {
|
||||||
|
// MTEXT stores a hard line break as the `\P` code. A literal newline —
|
||||||
|
// from Enter or a multi-line paste — is not a break to the layout (only
|
||||||
|
// the caret logic honours it), so the preview would stay on one line.
|
||||||
|
// Normalise every literal newline to `\P` so it breaks and the saved
|
||||||
|
// value stays standard (#308).
|
||||||
|
let normalized;
|
||||||
|
let s: &str = if s.contains(['\n', '\r']) {
|
||||||
|
normalized = s
|
||||||
|
.replace("\r\n", "\\P")
|
||||||
|
.replace('\n', "\\P")
|
||||||
|
.replace('\r', "\\P");
|
||||||
|
&normalized
|
||||||
|
} else {
|
||||||
|
s
|
||||||
|
};
|
||||||
if let Some(ed) = self.mtext_editor.as_mut() {
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
let raw0 = ed.content.text();
|
let raw0 = ed.content.text();
|
||||||
let raw = raw0.clone();
|
let raw = raw0.clone();
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,7 @@ pub(crate) fn append_insert_attribute_wires(
|
||||||
anno_scale,
|
anno_scale,
|
||||||
None,
|
None,
|
||||||
bg_color,
|
bg_color,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
// Use the INSERT's handle so selection / picking groups attribute
|
// Use the INSERT's handle so selection / picking groups attribute
|
||||||
// text with the parent insert instead of treating it as a stray text.
|
// text with the parent insert instead of treating it as a stray text.
|
||||||
|
|
|
||||||
|
|
@ -1357,6 +1357,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
1.0,
|
1.0,
|
||||||
None,
|
None,
|
||||||
bg_color,
|
bg_color,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
for w in &mut sub_wires {
|
for w in &mut sub_wires {
|
||||||
w.name = name.clone();
|
w.name = name.clone();
|
||||||
|
|
|
||||||
2
src/scene/cache/block_cache.rs
vendored
2
src/scene/cache/block_cache.rs
vendored
|
|
@ -419,7 +419,7 @@ fn tessellate_sub_local(
|
||||||
// before casting to f32 — same precision-preservation trick used for
|
// before casting to f32 — same precision-preservation trick used for
|
||||||
// top-level entities, applied per-defn.
|
// top-level entities, applied per-defn.
|
||||||
let wires_out = tessellate::tessellate(
|
let wires_out = tessellate::tessellate(
|
||||||
doc, h, sub, false, sub_color, pat_len, pat, lw_px, anno_scale, None, bg_color,
|
doc, h, sub, false, sub_color, pat_len, pat, lw_px, anno_scale, None, bg_color, false,
|
||||||
);
|
);
|
||||||
if wires_out.is_empty() {
|
if wires_out.is_empty() {
|
||||||
return vec![];
|
return vec![];
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,7 @@ pub(crate) fn tessellate_entity(
|
||||||
1.0,
|
1.0,
|
||||||
world_per_pixel,
|
world_per_pixel,
|
||||||
bg_color,
|
bg_color,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
let ab = entity_aabb(e);
|
let ab = entity_aabb(e);
|
||||||
for w in &mut wires {
|
for w in &mut wires {
|
||||||
|
|
@ -594,6 +595,7 @@ pub(crate) fn tessellate_entity(
|
||||||
anno_scale,
|
anno_scale,
|
||||||
world_per_pixel,
|
world_per_pixel,
|
||||||
bg_color,
|
bg_color,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
for w in &mut wires {
|
for w in &mut wires {
|
||||||
w.name = h.value().to_string();
|
w.name = h.value().to_string();
|
||||||
|
|
@ -654,6 +656,7 @@ pub(crate) fn tessellate_entity(
|
||||||
anno_scale,
|
anno_scale,
|
||||||
world_per_pixel,
|
world_per_pixel,
|
||||||
bg_color,
|
bg_color,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
for b in &mut bases {
|
for b in &mut bases {
|
||||||
b.aci = aci;
|
b.aci = aci;
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,11 @@ pub fn tessellate(
|
||||||
// (flag 0x02, "use drawing window colour") so the mask erases geometry
|
// (flag 0x02, "use drawing window colour") so the mask erases geometry
|
||||||
// behind the text the way a wipeout does.
|
// behind the text the way a wipeout does.
|
||||||
bg_color: [f32; 4],
|
bg_color: [f32; 4],
|
||||||
|
// When true, TEXT/MTEXT run-groups ALSO emit their glyph outline strokes as
|
||||||
|
// polyline points (not just SDF quads). The in-app MTEXT editor preview
|
||||||
|
// draws those strokes on a 2D canvas that can't run the SDF shader; every
|
||||||
|
// other caller passes false and gets the normal SDF-only text.
|
||||||
|
force_text_strokes: bool,
|
||||||
) -> Vec<WireModel> {
|
) -> Vec<WireModel> {
|
||||||
let color = if selected {
|
let color = if selected {
|
||||||
WireModel::SELECTED
|
WireModel::SELECTED
|
||||||
|
|
@ -210,7 +215,10 @@ pub fn tessellate(
|
||||||
// and text into one Text object (a tolerance frame: box lines =
|
// and text into one Text object (a tolerance frame: box lines =
|
||||||
// run-less, cell text = run-groups) keeps the geometry as
|
// run-less, cell text = run-groups) keeps the geometry as
|
||||||
// strokes and draws only the text as SDF.
|
// strokes and draws only the text as SDF.
|
||||||
for group in stroke_groups.iter().filter(|g| g.run.is_none()) {
|
for group in stroke_groups
|
||||||
|
.iter()
|
||||||
|
.filter(|g| force_text_strokes || g.run.is_none())
|
||||||
|
{
|
||||||
let lx_v = group.origin[0];
|
let lx_v = group.origin[0];
|
||||||
let ly_v = group.origin[1];
|
let ly_v = group.origin[1];
|
||||||
let slx_v = (lx_v - ref_lx_v) * anno + ref_lx_v;
|
let slx_v = (lx_v - ref_lx_v) * anno + ref_lx_v;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue