feat: a sketch text's height, place and angle can be expressions (#1453)
A sketch text's height, place (x and y) and angle can each be an expression over the design's parameters, and the text follows parameter changes, undo and reopening. Closes #1362. Co-authored-by: NOWN-DEV <NOWN-DEV@users.noreply.github.com>
This commit is contained in:
1 parent
e20cf1858a
commit
b6806b16cf
23 files changed
+751
-70
No files matched your search
@@ -223,7 +223,8 @@ fn text_in_a_font_closes_into_profiles_with_counters() {
|
||||
let folder = Folder::new("profiles");
|
||||
let (at, height) = (v(10.0, 20.0), 7.0);
|
||||
let font = folder.font();
|
||||
let source = LinkSource::Text { text: "O".into(), at, height, angle: 0.0, font: Some(font.clone()), style: Default::default() };
|
||||
let source =
|
||||
LinkSource::Text { text: "O".into(), at, height, angle: 0.0, font: Some(font.clone()), style: Default::default(), exprs: Default::default() };
|
||||
let mut sk = Sketch::new();
|
||||
sk.add_link(LinkKind::Text, source, &text_geometry_in(Some(&font), "O", at, height, 0.0).unwrap()).unwrap();
|
||||
// The block with the counter in it is 8 x 7 mm minus 4 x 3 mm; the counter is a region of its own.
|
||||
@@ -303,6 +304,7 @@ fn a_font_is_kept_in_the_design_and_old_designs_read_without_one() {
|
||||
angle: 0.0,
|
||||
font: font.map(str::to_string),
|
||||
style: Default::default(),
|
||||
exprs: Default::default(),
|
||||
};
|
||||
// No font: nothing is written, so the text of a design reads and writes as it did before fonts, and a design saved
|
||||
// before them reads as text in the built-in font.
|
||||
|
||||
@@ -46,7 +46,7 @@ pub use decompose::Budget;
|
||||
pub use dims::{DimFrame, DimLayout, chain_centres, default_text, dim_frame, dim_layout, dim_layout_reaching, dim_reach, encode_text};
|
||||
pub use fonts::{DEFAULT_FONT_FAMILY, FontData, families_in, find_style_in, font_data, font_data_styled, installed_families, set_font_file_check};
|
||||
pub use freeform_offset::{FreeformOffset, OFFSET_TOLERANCE};
|
||||
pub use link::{Link, LinkGeom, LinkKind, LinkSource, MAX_LINK_ENTITIES, SKETCH_AXIS_HALF, sketch_axis_geom};
|
||||
pub use link::{Link, LinkGeom, LinkKind, LinkSource, MAX_LINK_ENTITIES, SKETCH_AXIS_HALF, TextExprs, sketch_axis_geom};
|
||||
pub use model::{
|
||||
Constraint, ConstraintKind, Curve, CurveKind, MAX_CONSTRAINTS, MAX_COORD, MAX_CURVES, MAX_POINTS, SPoint, Sketch, SketchError, SketchView,
|
||||
};
|
||||
|
||||
@@ -84,9 +84,47 @@ pub enum LinkSource {
|
||||
/// Bold or italic, alignment, spacing and the curve the text follows.
|
||||
#[serde(default, skip_serializing_if = "crate::text::TextStyle::is_plain")]
|
||||
style: crate::text::TextStyle,
|
||||
/// The expressions behind the numbers above that were typed as expressions.
|
||||
#[serde(default, skip_serializing_if = "TextExprs::is_empty")]
|
||||
exprs: TextExprs,
|
||||
},
|
||||
}
|
||||
|
||||
/// The expressions behind a text's numbers that were typed as expressions: its place (`x`, `y`),
|
||||
/// its height and its angle. A number without an expression is the number typed. The numbers
|
||||
/// of `LinkSource::Text` are what the expressions gave last: the document evaluates them against
|
||||
/// its parameters before the text's outlines are rebuilt (`Document::apply_text_values`), and
|
||||
/// the sketch crate, which knows no parameters, only keeps them.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TextExprs {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub x: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub y: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub height: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub angle: Option<String>,
|
||||
}
|
||||
|
||||
impl TextExprs {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
*self == TextExprs::default()
|
||||
}
|
||||
|
||||
/// The expressions set, each with what it sets.
|
||||
pub fn list(&self) -> Vec<(&'static str, &String)> {
|
||||
[("Text X", &self.x), ("Text Y", &self.y), ("Text height", &self.height), ("Text angle", &self.angle)]
|
||||
.into_iter()
|
||||
.filter_map(|(what, e)| e.as_ref().map(|e| (what, e)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn list_mut(&mut self) -> Vec<&mut String> {
|
||||
[&mut self.x, &mut self.y, &mut self.height, &mut self.angle].into_iter().filter_map(Option::as_mut).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl LinkSource {
|
||||
/// Short description for messages and the browser.
|
||||
pub fn describe(&self) -> String {
|
||||
@@ -482,6 +520,30 @@ impl Sketch {
|
||||
self.links.iter().find(|l| l.id == id)
|
||||
}
|
||||
|
||||
/// The expressions of the sketch's texts, each with what it sets (`Text height`…).
|
||||
pub fn text_exprs(&self) -> Vec<(&'static str, &String)> {
|
||||
self.links
|
||||
.iter()
|
||||
.filter_map(|l| match &l.source {
|
||||
LinkSource::Text { exprs, .. } => Some(exprs.list()),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Mutable view of [`Sketch::text_exprs`], for renaming the parameters they refer to.
|
||||
pub fn text_exprs_mut(&mut self) -> Vec<&mut String> {
|
||||
self.links
|
||||
.iter_mut()
|
||||
.filter_map(|l| match &mut l.source {
|
||||
LinkSource::Text { exprs, .. } => Some(exprs.list_mut()),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Curves owned by a link, in creation order.
|
||||
pub fn link_curves(&self, id: &str) -> Vec<usize> {
|
||||
self.curves.iter().enumerate().filter(|(_, c)| c.link.as_deref() == Some(id)).map(|(i, _)| i).collect()
|
||||
|
||||
@@ -650,7 +650,7 @@ fn merging_adjacent_regions() {
|
||||
/// outlines.
|
||||
fn text_sketch(text: &str, at: Vec2, height: f64) -> Sketch {
|
||||
let mut sk = Sketch::new();
|
||||
let source = LinkSource::Text { text: text.into(), at, height, angle: 0.0, font: None, style: Default::default() };
|
||||
let source = LinkSource::Text { text: text.into(), at, height, angle: 0.0, font: None, style: Default::default(), exprs: Default::default() };
|
||||
sk.add_link(LinkKind::Text, source, &text_geometry(text, at, height, 0.0).unwrap()).unwrap();
|
||||
sk
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ impl Sketch {
|
||||
/// The outline geometry of a text source of this sketch: its path, when it has one, is a
|
||||
/// curve of this sketch as it is now.
|
||||
pub fn text_source_geometry(&self, src: &LinkSource) -> Result<Vec<LinkGeom>> {
|
||||
let LinkSource::Text { text, at, height, angle, font, style } = src else {
|
||||
let LinkSource::Text { text, at, height, angle, font, style, .. } = src else {
|
||||
return Err(SketchError::Invalid("not a text".into()));
|
||||
};
|
||||
let path = match &style.path {
|
||||
|
||||
@@ -134,6 +134,7 @@ fn a_sketch_sets_its_text_on_one_of_its_curves() {
|
||||
angle: 0.7,
|
||||
font: None,
|
||||
style: TextStyle { path: Some(path.into()), ..TextStyle::default() },
|
||||
exprs: Default::default(),
|
||||
};
|
||||
// On the line: `at` and the angle do not place it.
|
||||
let on = sk.text_source_geometry(&source(&id)).unwrap();
|
||||
@@ -149,7 +150,15 @@ fn a_sketch_sets_its_text_on_one_of_its_curves() {
|
||||
|
||||
#[test]
|
||||
fn a_style_is_saved_only_when_it_is_not_plain_and_reads_back() {
|
||||
let plain = LinkSource::Text { text: "A".into(), at: Vec2::ZERO, height: 5.0, angle: 0.0, font: None, style: TextStyle::default() };
|
||||
let plain = LinkSource::Text {
|
||||
text: "A".into(),
|
||||
at: Vec2::ZERO,
|
||||
height: 5.0,
|
||||
angle: 0.0,
|
||||
font: None,
|
||||
style: TextStyle::default(),
|
||||
exprs: Default::default(),
|
||||
};
|
||||
let json = serde_json::to_value(&plain).unwrap();
|
||||
assert!(json.get("style").is_none(), "{json}");
|
||||
let styled = LinkSource::Text {
|
||||
@@ -167,6 +176,7 @@ fn a_style_is_saved_only_when_it_is_not_plain_and_reads_back() {
|
||||
path: Some("l3".into()),
|
||||
reverse: true,
|
||||
},
|
||||
exprs: Default::default(),
|
||||
};
|
||||
let json = serde_json::to_value(&styled).unwrap();
|
||||
assert_eq!(json["style"]["align"], "right");
|
||||
|
||||
Reference in new issue
Block a user