MTEXT: annotative scaling and SCALE on multileader text scale the text width and columns too (fixes #708, fixes #709) (#743)

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-13 05:10:24 -04:00
1 parent cddcbe7a2a
commit 79efd97515
4 files changed
+110 -1

No files matched your search

+6
View File
@@ -159,6 +159,9 @@ pub fn anno_scaled(e: &Entity, f: f64) -> Entity {
EntityKind::MText(t) => {
t.height *= f;
t.width *= f;
if let Some(c) = &mut t.columns {
c.scale(f);
}
}
EntityKind::Dimension(dm) => {
dm.overrides.insert("scale".into(), serde_json::json!(f));
@@ -172,6 +175,9 @@ pub fn anno_scaled(e: &Entity, f: f64) -> Entity {
t.insert = land + (t.insert - land) * f;
t.height *= f;
t.width *= f;
if let Some(c) = &mut t.columns {
c.scale(f);
}
}
if let Some(ins) = &mut m.block {
ins.insert = land + (ins.insert - land) * f;
+4
View File
@@ -1212,6 +1212,10 @@ impl EntityKind {
if let Some(t) = &mut ml.text {
t3(&mut t.insert);
t.height *= s;
t.width *= s;
if let Some(c) = &mut t.columns {
c.scale(s);
}
}
if let Some(b) = &mut ml.block {
// Transformed exactly like a block reference.
+53
View File
@@ -283,3 +283,56 @@ fn hostile_parameters_are_refused() {
assert!(s.execute("scalelistedit", &p).is_err(), "{p}");
}
}
#[test]
fn annotative_mtext_columns_follow_the_scale() {
// #708: at 1:2, two columns 10 wide with a gutter of 1 and a height of 20 on paper draw 20
// wide, 2 apart and 40 tall: the box is 42 × 40, not 41 × 20.
let mut s = Session::new();
setvar(&mut s, "CANNOSCALE", json!("1:2"));
let r = s
.execute(
"mtext",
&json!({ "at": [0, 0], "height": 1, "width": 10, "text": "a\\Pb\\Pc", "columns": { "type": "static", "count": 2, "gutter": 1, "height": 20 } }),
)
.unwrap();
let h = Handle::parse_hex(r["handle"].as_str().unwrap()).unwrap();
s.execute("properties.set", &json!({ "handles": [h.hex()], "annotative": true })).unwrap();
assert_eq!(scales(&s, h), ["1:2"]);
let v = |s: &mut Session, name: &str| s.execute("getvar", &json!({ "name": name })).unwrap()["value"].clone();
assert_eq!(v(&mut s, "EXTMIN"), json!([0.0, -40.0, 0.0]));
assert_eq!(v(&mut s, "EXTMAX"), json!([42.0, 0.0, 0.0]));
// Dynamic columns with manual heights, and the text of a multileader, scale the same way.
let mut e = entity(&s, h);
if let EntityKind::MText(t) = &mut e.kind
&& let Some(c) = &mut t.columns
{
c.heights = vec![3.0, 5.0];
}
let mtext = match &e.kind {
EntityKind::MText(t) => t.clone(),
k => panic!("{k:?}"),
};
let ml = Entity {
kind: EntityKind::MLeader(cadcraft_engine::doc::MLeader {
leaders: Vec::new(),
landing: Default::default(),
dogleg: 1.0,
text: Some(mtext),
style: "Standard".into(),
arrow_size: 1.0,
spline: false,
block: None,
}),
..e.clone()
};
for e in [e, ml] {
let t = match cadcraft_engine::doc::anno_scaled(&e, 2.0).kind {
EntityKind::MText(t) => t,
EntityKind::MLeader(m) => m.text.unwrap(),
k => panic!("{k:?}"),
};
let c = t.columns.unwrap();
assert_eq!((t.height, t.width, c.count, c.gutter, c.height, c.heights), (2.0, 20.0, 2, 2.0, 40.0, vec![6.0, 10.0]));
}
}
+47 -1
View File
@@ -1,7 +1,7 @@
//! MTEXT columns and paragraph formatting from the command line, scripts and the JSON form
//! (#532).
use cadcraft_doc::{EntityKind, MText, MTextColumnType};
use cadcraft_doc::{EntityKind, MLeader, MText, MTextColumnType, MTextColumns};
use cadcraft_engine::Session;
use serde_json::json;
@@ -79,3 +79,49 @@ fn columns_shape_the_drawn_text_and_its_extents() {
let b = cadcraft_doc::entity_bounds(d, e, 0);
assert!((b.width() - 34.0).abs() < 1e-9 && (b.height() - 6.0).abs() < 1e-9, "{b:?}");
}
#[test]
fn scale_scales_multileader_text_width_and_columns() {
// #709: SCALE 2 on a multileader's text doubles its width and column lengths like it does
// for a standalone MTEXT, not only its height.
let text = MText {
insert: Default::default(),
height: 2.0,
width: 10.0,
attach: 1,
rotation: 0.0,
style: "Standard".into(),
contents: "hello world".into(),
line_spacing: 1.0,
line_spacing_exact: false,
columns: Some(MTextColumns {
kind: MTextColumnType::Static,
count: 2,
gutter: 1.0,
height: 5.0,
auto_height: true,
heights: vec![3.0, 4.0],
flow_reversed: false,
}),
};
let mut s = Session::new();
let ml = MLeader {
leaders: Vec::new(),
landing: Default::default(),
dogleg: 1.0,
text: Some(text),
style: "Standard".into(),
arrow_size: 1.0,
spline: false,
block: None,
};
let h = s.add_entity(EntityKind::MLeader(ml)).unwrap();
s.execute("scale", &json!({ "handles": [h.hex()], "base": [0, 0], "factor": 2 })).unwrap();
let t = match &s.doc().unwrap().entity(h).unwrap().kind {
EntityKind::MLeader(m) => m.text.clone().unwrap(),
k => panic!("{k:?}"),
};
let c = t.columns.unwrap();
assert_eq!((t.height, t.width), (4.0, 20.0));
assert_eq!((c.kind, c.count, c.gutter, c.height, c.heights), (MTextColumnType::Static, 2, 2.0, 10.0, vec![6.0, 8.0]));
}