fix(text): correct scaled text placement
Derive two-point layout and continuation spacing from the effective annotation scale. Keep style flags out of per-entity mirror overrides.
This commit is contained in:
parent
0f5f47b37d
commit
0603aae286
8 changed files with 109 additions and 47 deletions
|
|
@ -1268,10 +1268,16 @@ impl OpenCADStudio {
|
||||||
// ── Annotate commands ──────────────────────────────────────────
|
// ── Annotate commands ──────────────────────────────────────────
|
||||||
"TEXT" => {
|
"TEXT" => {
|
||||||
use crate::modules::annotate::text::TextCommand;
|
use crate::modules::annotate::text::TextCommand;
|
||||||
let document = &self.tabs[i].scene.document;
|
let (defaults, styles, annotation_multiplier) = {
|
||||||
let defaults = crate::scene::creation_style::current_text_defaults(document);
|
let scene = &self.tabs[i].scene;
|
||||||
let styles = document.text_styles.iter().cloned().collect();
|
let annotation_multiplier = scene.creation_annotation_multiplier();
|
||||||
let new_cmd = TextCommand::with_defaults(defaults, styles);
|
let defaults =
|
||||||
|
crate::scene::creation_style::current_text_defaults(&scene.document);
|
||||||
|
let styles = scene.document.text_styles.iter().cloned().collect();
|
||||||
|
(defaults, styles, annotation_multiplier)
|
||||||
|
};
|
||||||
|
let new_cmd =
|
||||||
|
TextCommand::with_defaults(defaults, styles, annotation_multiplier);
|
||||||
self.command_line.push_info(&new_cmd.prompt());
|
self.command_line.push_info(&new_cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1809,12 +1809,30 @@ impl OpenCADStudio {
|
||||||
text.horizontal_alignment,
|
text.horizontal_alignment,
|
||||||
acadrust::entities::TextHorizontalAlignment::Aligned
|
acadrust::entities::TextHorizontalAlignment::Aligned
|
||||||
);
|
);
|
||||||
|
let annotative = crate::scene::annotative::is_annotative(doc, entity);
|
||||||
|
let model_factor = if annotative {
|
||||||
|
annotation_scale_handle
|
||||||
|
.and_then(|handle| match doc.objects.get(&handle) {
|
||||||
|
Some(acadrust::objects::ObjectType::Scale(scale)) => Some(
|
||||||
|
scale.inverse_factor()
|
||||||
|
/ self.tabs[i].scene.annotation_scale_unit_factor(),
|
||||||
|
),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or(self.tabs[i].scene.annotation_scale as f64)
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
let paper_height = if aligned {
|
let paper_height = if aligned {
|
||||||
crate::entities::text::text_run_placement(text, doc).height as f64
|
crate::entities::text::text_run_placement_at_scale(
|
||||||
|
text,
|
||||||
|
doc,
|
||||||
|
model_factor as f32,
|
||||||
|
)
|
||||||
|
.height as f64
|
||||||
} else {
|
} else {
|
||||||
text.height
|
text.height
|
||||||
};
|
};
|
||||||
let annotative = crate::scene::annotative::is_annotative(doc, entity);
|
|
||||||
for section in sections.iter_mut() {
|
for section in sections.iter_mut() {
|
||||||
if let Some(row) =
|
if let Some(row) =
|
||||||
section.props.iter_mut().find(|row| row.field == "height")
|
section.props.iter_mut().find(|row| row.field == "height")
|
||||||
|
|
@ -1830,15 +1848,6 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if annotative {
|
if annotative {
|
||||||
let model_factor = annotation_scale_handle
|
|
||||||
.and_then(|handle| match doc.objects.get(&handle) {
|
|
||||||
Some(acadrust::objects::ObjectType::Scale(scale)) => Some(
|
|
||||||
scale.inverse_factor()
|
|
||||||
/ self.tabs[i].scene.annotation_scale_unit_factor(),
|
|
||||||
),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.unwrap_or(self.tabs[i].scene.annotation_scale as f64);
|
|
||||||
insert_row_after(
|
insert_row_after(
|
||||||
&mut sections,
|
&mut sections,
|
||||||
"height",
|
"height",
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,7 @@ impl super::OpenCADStudio {
|
||||||
} else {
|
} else {
|
||||||
crate::command::WorkingPlane::default()
|
crate::command::WorkingPlane::default()
|
||||||
};
|
};
|
||||||
|
let command_creation = ed.creation.is_some();
|
||||||
let mut t = if let Some(mut prepared) = ed.creation {
|
let mut t = if let Some(mut prepared) = ed.creation {
|
||||||
prepared.value = ed.value.clone();
|
prepared.value = ed.value.clone();
|
||||||
prepared
|
prepared
|
||||||
|
|
@ -258,10 +259,26 @@ impl super::OpenCADStudio {
|
||||||
t.style = cur_style;
|
t.style = cur_style;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let annotative = crate::scene::annotative::text_style_is_annotative(
|
if command_creation {
|
||||||
|
let annotation_multiplier = if crate::scene::annotative::text_style_is_annotative(
|
||||||
&self.tabs[i].scene.document,
|
&self.tabs[i].scene.document,
|
||||||
&t.style,
|
&t.style,
|
||||||
);
|
) {
|
||||||
|
self.tabs[i].scene.creation_annotation_multiplier()
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
let display_height = crate::entities::text::text_run_placement_at_scale(
|
||||||
|
&t,
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
annotation_multiplier as f32,
|
||||||
|
)
|
||||||
|
.height as f64
|
||||||
|
* annotation_multiplier;
|
||||||
|
if let Some(command) = self.tabs[i].suspended_cmd.as_mut() {
|
||||||
|
command.on_editor_display_height(display_height);
|
||||||
|
}
|
||||||
|
}
|
||||||
self.push_undo_snapshot(i, "TEXT");
|
self.push_undo_snapshot(i, "TEXT");
|
||||||
self.tabs[i].scene.document.header.current_text_style_name = t.style.clone();
|
self.tabs[i].scene.document.header.current_text_style_name = t.style.clone();
|
||||||
let variable_height = self.tabs[i]
|
let variable_height = self.tabs[i]
|
||||||
|
|
@ -279,17 +296,7 @@ impl super::OpenCADStudio {
|
||||||
{
|
{
|
||||||
self.tabs[i].scene.document.header.text_height = t.height;
|
self.tabs[i].scene.document.header.text_height = t.height;
|
||||||
}
|
}
|
||||||
let handle = self.commit_entity_handle(plane.place_entity(EntityType::Text(t)));
|
let _ = self.commit_entity_handle(plane.place_entity(EntityType::Text(t)));
|
||||||
if annotative {
|
|
||||||
let scale = self.tabs[i].scene.current_annotation_scale_handle();
|
|
||||||
if let (Some(handle), Some(scale)) = (handle, scale) {
|
|
||||||
crate::scene::annotative::create_annotation_context(
|
|
||||||
&mut self.tabs[i].scene.document,
|
|
||||||
handle,
|
|
||||||
scale,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
}
|
}
|
||||||
self.refresh_properties();
|
self.refresh_properties();
|
||||||
|
|
|
||||||
|
|
@ -1869,6 +1869,8 @@ pub trait CadCommand: Send {
|
||||||
/// Resume the command with collected rich text.
|
/// Resume the command with collected rich text.
|
||||||
fn on_editor_text(&mut self, _value: String) {}
|
fn on_editor_text(&mut self, _value: String) {}
|
||||||
|
|
||||||
|
fn on_editor_display_height(&mut self, _height: f64) {}
|
||||||
|
|
||||||
/// Called when the user clicks and `needs_entity_pick()` is true.
|
/// Called when the user clicks and `needs_entity_pick()` is true.
|
||||||
/// `handle` is the nearest wire's entity handle (Handle::NULL if nothing found).
|
/// `handle` is the nearest wire's entity handle (Handle::NULL if nothing found).
|
||||||
fn on_entity_pick(&mut self, _handle: Handle, _pt: DVec3) -> CmdResult {
|
fn on_entity_pick(&mut self, _handle: Handle, _pt: DVec3) -> CmdResult {
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,12 @@ pub(crate) fn acad_text_encode(value: &str) -> String {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_render(t: &Text, document: &acadrust::CadDocument) -> RenderEntity {
|
pub(crate) fn to_render_at_scale(
|
||||||
let p = text_run_placement(t, document);
|
t: &Text,
|
||||||
|
document: &acadrust::CadDocument,
|
||||||
|
annotation_scale: f32,
|
||||||
|
) -> RenderEntity {
|
||||||
|
let p = text_run_placement_at_scale(t, document, annotation_scale);
|
||||||
let snap_pt = glam::DVec3::new(p.wcs_insertion[0], p.wcs_insertion[1], p.wcs_insertion[2]);
|
let snap_pt = glam::DVec3::new(p.wcs_insertion[0], p.wcs_insertion[1], p.wcs_insertion[2]);
|
||||||
// Parse `%%` codes via acadrust, re-encoded for the stroke tessellator.
|
// Parse `%%` codes via acadrust, re-encoded for the stroke tessellator.
|
||||||
let value = acad_text_encode(&p.value);
|
let value = acad_text_encode(&p.value);
|
||||||
|
|
@ -172,7 +176,16 @@ fn to_render(t: &Text, document: &acadrust::CadDocument) -> RenderEntity {
|
||||||
|
|
||||||
/// Compute a TEXT entity's run placement (origin + layout params). Extracted
|
/// Compute a TEXT entity's run placement (origin + layout params). Extracted
|
||||||
/// from `to_render` verbatim so the stroke and SDF-quad paths agree exactly.
|
/// from `to_render` verbatim so the stroke and SDF-quad paths agree exactly.
|
||||||
pub fn text_run_placement(t: &Text, document: &acadrust::CadDocument) -> TextPlacement {
|
pub fn text_run_placement_at_scale(
|
||||||
|
t: &Text,
|
||||||
|
document: &acadrust::CadDocument,
|
||||||
|
annotation_scale: f32,
|
||||||
|
) -> TextPlacement {
|
||||||
|
let annotation_scale = if annotation_scale.is_finite() && annotation_scale > 1.0e-9 {
|
||||||
|
annotation_scale
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
let normal = (t.normal.x, t.normal.y, t.normal.z);
|
let normal = (t.normal.x, t.normal.y, t.normal.z);
|
||||||
let (wsx, wsy, wsz) = crate::scene::view::transform::ocs_point_to_wcs(
|
let (wsx, wsy, wsz) = crate::scene::view::transform::ocs_point_to_wcs(
|
||||||
(
|
(
|
||||||
|
|
@ -222,7 +235,8 @@ pub fn text_run_placement(t: &Text, document: &acadrust::CadDocument) -> TextPla
|
||||||
oblique_angle,
|
oblique_angle,
|
||||||
) {
|
) {
|
||||||
if base_bounds.advance > 1.0e-6 {
|
if base_bounds.advance > 1.0e-6 {
|
||||||
let scale = (span as f32 / base_bounds.advance).max(1.0e-6);
|
let scale =
|
||||||
|
(span as f32 / annotation_scale / base_bounds.advance).max(1.0e-6);
|
||||||
if matches!(t.horizontal_alignment, HA::Aligned) {
|
if matches!(t.horizontal_alignment, HA::Aligned) {
|
||||||
height *= scale;
|
height *= scale;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -605,7 +619,7 @@ fn apply_transform(t: &mut Text, tr: &EntityTransform) {
|
||||||
|
|
||||||
impl RenderConvertible for Text {
|
impl RenderConvertible for Text {
|
||||||
fn to_render(&self, document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
fn to_render(&self, document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
||||||
Some(to_render(self, document))
|
Some(to_render_at_scale(self, document, 1.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,17 @@ pub struct TextCommand {
|
||||||
oblique_angle: f64,
|
oblique_angle: f64,
|
||||||
fixed_height: bool,
|
fixed_height: bool,
|
||||||
annotative: bool,
|
annotative: bool,
|
||||||
generation_flags: i16,
|
annotation_multiplier: f64,
|
||||||
|
last_display_height: Option<f64>,
|
||||||
last_entity: Option<Text>,
|
last_entity: Option<Text>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextCommand {
|
impl TextCommand {
|
||||||
pub fn with_defaults(defaults: TextCreationDefaults, styles: Vec<TextStyle>) -> Self {
|
pub fn with_defaults(
|
||||||
|
defaults: TextCreationDefaults,
|
||||||
|
styles: Vec<TextStyle>,
|
||||||
|
annotation_multiplier: f64,
|
||||||
|
) -> Self {
|
||||||
let current_height = defaults.height;
|
let current_height = defaults.height;
|
||||||
let mut command = Self {
|
let mut command = Self {
|
||||||
step: Step::Start,
|
step: Step::Start,
|
||||||
|
|
@ -69,7 +74,8 @@ impl TextCommand {
|
||||||
oblique_angle: defaults.oblique_angle,
|
oblique_angle: defaults.oblique_angle,
|
||||||
fixed_height: false,
|
fixed_height: false,
|
||||||
annotative: false,
|
annotative: false,
|
||||||
generation_flags: 0,
|
annotation_multiplier: annotation_multiplier.max(1.0e-9),
|
||||||
|
last_display_height: None,
|
||||||
last_entity: None,
|
last_entity: None,
|
||||||
};
|
};
|
||||||
let style = command.style_name.clone();
|
let style = command.style_name.clone();
|
||||||
|
|
@ -102,8 +108,6 @@ impl TextCommand {
|
||||||
85.0_f64.to_radians(),
|
85.0_f64.to_radians(),
|
||||||
);
|
);
|
||||||
self.annotative = style.annotative;
|
self.annotative = style.annotative;
|
||||||
self.generation_flags = (if style.flags.backward { 2 } else { 0 })
|
|
||||||
| (if style.flags.upside_down { 4 } else { 0 });
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,7 +170,6 @@ impl TextCommand {
|
||||||
text.rotation = self.rotation;
|
text.rotation = self.rotation;
|
||||||
text.horizontal_alignment = self.horizontal;
|
text.horizontal_alignment = self.horizontal;
|
||||||
text.vertical_alignment = self.vertical;
|
text.vertical_alignment = self.vertical;
|
||||||
text.generation_flags = self.generation_flags;
|
|
||||||
text.alignment_point = if self.is_two_point() {
|
text.alignment_point = if self.is_two_point() {
|
||||||
let second = self.plane.to_local(self.second_point?);
|
let second = self.plane.to_local(self.second_point?);
|
||||||
Some(Vector3::new(second.x, second.y, second.z))
|
Some(Vector3::new(second.x, second.y, second.z))
|
||||||
|
|
@ -183,6 +186,7 @@ impl TextCommand {
|
||||||
return CmdResult::NeedPoint;
|
return CmdResult::NeedPoint;
|
||||||
};
|
};
|
||||||
let pos = self.first_point.unwrap_or(DVec3::ZERO);
|
let pos = self.first_point.unwrap_or(DVec3::ZERO);
|
||||||
|
self.last_display_height = None;
|
||||||
self.last_entity = Some(entity.clone());
|
self.last_entity = Some(entity.clone());
|
||||||
CmdResult::SuspendForTextInput { pos, entity }
|
CmdResult::SuspendForTextInput { pos, entity }
|
||||||
}
|
}
|
||||||
|
|
@ -199,7 +203,18 @@ impl TextCommand {
|
||||||
} else {
|
} else {
|
||||||
entity.rotation
|
entity.rotation
|
||||||
};
|
};
|
||||||
let spacing = entity.height.max(1.0e-9) * 1.666_666_666_7;
|
let fallback_height = entity.height.max(1.0e-9)
|
||||||
|
* if self.annotative {
|
||||||
|
self.annotation_multiplier
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
let spacing = self
|
||||||
|
.last_display_height
|
||||||
|
.take()
|
||||||
|
.unwrap_or(fallback_height)
|
||||||
|
.max(1.0e-9)
|
||||||
|
* 1.666_666_666_7;
|
||||||
let delta = Vector3::new(angle.sin() * spacing, -angle.cos() * spacing, 0.0);
|
let delta = Vector3::new(angle.sin() * spacing, -angle.cos() * spacing, 0.0);
|
||||||
entity.insertion_point = entity.insertion_point + delta;
|
entity.insertion_point = entity.insertion_point + delta;
|
||||||
if let Some(point) = entity.alignment_point.as_mut() {
|
if let Some(point) = entity.alignment_point.as_mut() {
|
||||||
|
|
@ -422,6 +437,12 @@ impl CadCommand for TextCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_editor_display_height(&mut self, height: f64) {
|
||||||
|
if height.is_finite() && height > 1.0e-9 {
|
||||||
|
self.last_display_height = Some(height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_escape(&mut self) -> CmdResult {
|
fn on_escape(&mut self) -> CmdResult {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -633,7 +633,14 @@ pub fn tessellate(
|
||||||
// stay a roughly constant on-screen size; otherwise the header-driven path.
|
// stay a roughly constant on-screen size; otherwise the header-driven path.
|
||||||
let te = crate::entities::point::relative_render(entity, document, world_per_pixel)
|
let te = crate::entities::point::relative_render(entity, document, world_per_pixel)
|
||||||
.or_else(|| crate::entities::light::relative_render(entity, document, world_per_pixel))
|
.or_else(|| crate::entities::light::relative_render(entity, document, world_per_pixel))
|
||||||
.or_else(|| convert(entity, document));
|
.or_else(|| match entity {
|
||||||
|
EntityType::Text(text) => Some(crate::entities::text::to_render_at_scale(
|
||||||
|
text,
|
||||||
|
document,
|
||||||
|
anno_scale,
|
||||||
|
)),
|
||||||
|
_ => convert(entity, document),
|
||||||
|
});
|
||||||
if let Some(te) = te {
|
if let Some(te) = te {
|
||||||
match te.object {
|
match te.object {
|
||||||
// ── Text / MText: pre-tessellated glyph strokes ───────────────
|
// ── Text / MText: pre-tessellated glyph strokes ───────────────
|
||||||
|
|
|
||||||
|
|
@ -134,10 +134,6 @@ fn apply_text_defaults(doc: &CadDocument, entity: &mut EntityType) {
|
||||||
if text.oblique_angle.abs() <= 1.0e-9 {
|
if text.oblique_angle.abs() <= 1.0e-9 {
|
||||||
text.oblique_angle = resolved.oblique_angle;
|
text.oblique_angle = resolved.oblique_angle;
|
||||||
}
|
}
|
||||||
if text.generation_flags == 0 {
|
|
||||||
text.generation_flags = (if resolved.flags.backward { 2 } else { 0 })
|
|
||||||
| (if resolved.flags.upside_down { 4 } else { 0 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
EntityType::MText(text) => {
|
EntityType::MText(text) => {
|
||||||
if resolved.height > 1.0e-9 {
|
if resolved.height > 1.0e-9 {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue