Type: text.create and text.createInPath return the new type's bounds (#2413)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
vangel76
2026-10-17 13:20:26 +07:00
committed by bflatastic
co-authored by Claude Sonnet 5.5
parent d3751cc07b
commit 4f30e52368
5 changed files with 43 additions and 5 deletions
+2 -1
View File
@@ -74,7 +74,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Create Text",
[],
None,
"{x, y, text, vertical?: bool = false, size?: pt, font?: family, style?, color?, area?: {width, height, fit?: none|autoHeight|shrinkText, fitMinPercent?}, placeholder?: bool (fill it with placeholder text instead, as the Type tools do with prefs placeholderText), justify?: \"auto\" (default: the start of each paragraph's direction)|\"left\"|\"center\"|\"right\"|\"justifyAll\" (point type: aligned on x, so \"center\" centres each line on the point and \"right\" ends it there; text.setStyle changes it later), leadingModel?: \"romanBaseline\"|\"emBoxTop\", charAlign?: \"romanBaseline\"|\"emBoxTop\"|\"emBoxCenter\"|\"emBoxBottom\"|\"icfTop\"|\"icfBottom\" (default: emBoxTop and emBoxCenter while the interface is in Japanese, else romanBaseline)} → {id, overflow?: [id] (area type whose text doesn't fit its frame: the lines past it aren't drawn; left out when it fits)}; new area type gets Auto Size (fit autoHeight) when the autoSizeAreaType preference is on",
"{x, y, text, vertical?: bool = false, size?: pt, font?: family, style?, color?, area?: {width, height, fit?: none|autoHeight|shrinkText, fitMinPercent?}, placeholder?: bool (fill it with placeholder text instead, as the Type tools do with prefs placeholderText), justify?: \"auto\" (default: the start of each paragraph's direction)|\"left\"|\"center\"|\"right\"|\"justifyAll\" (point type: aligned on x, so \"center\" centres each line on the point and \"right\" ends it there; text.setStyle changes it later), leadingModel?: \"romanBaseline\"|\"emBoxTop\", charAlign?: \"romanBaseline\"|\"emBoxTop\"|\"emBoxCenter\"|\"emBoxBottom\"|\"icfTop\"|\"icfBottom\" (default: emBoxTop and emBoxCenter while the interface is in Japanese, else romanBaseline)} → {id, bounds: {x, y, width, height} (the new type's box as document.inspect reports it, after justify), overflow?: [id] (area type whose text doesn't fit its frame: the lines past it aren't drawn; left out when it fits)}; new area type gets Auto Size (fit autoHeight) when the autoSizeAreaType preference is on",
has_doc,
text_create
),
@@ -512,6 +512,7 @@ fn text_create(s: &mut Session, p: &Value) -> Result<Value> {
}
let made = add_node(s, "Type", NodeKind::Text(Box::new(t)), Appearance::default(), None)?;
let id = made.get("id").and_then(Value::as_u64).map(NodeId);
let made = super::typecmd::with_bounds(s, id, made);
Ok(super::typecmd::with_overflow(s, id.as_slice(), made))
}
+3 -2
View File
@@ -45,7 +45,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Area / Path Type",
[],
None,
"{path: id, mode: \"area\"|\"onPath\", text?: \"\", vertical?: bool = false, at?: [x, y] (on-path start: nearest point), size?, font?, style?, color? (as text.create), fit?: none|autoHeight|shrinkText, fitMinPercent? (area: as text.areaOptions; default autoHeight when the autoSizeAreaType preference is on), placeholder?: bool (placeholder text instead, as text.create), justify?, leadingModel?, charAlign? (as text.create)} turn a path into an area-type frame or a type-on-a-path baseline (the path's paint is dropped) → {id, overflow?: [id] (the text doesn't fit; left out when it fits)}",
"{path: id, mode: \"area\"|\"onPath\", text?: \"\", vertical?: bool = false, at?: [x, y] (on-path start: nearest point), size?, font?, style?, color? (as text.create), fit?: none|autoHeight|shrinkText, fitMinPercent? (area: as text.areaOptions; default autoHeight when the autoSizeAreaType preference is on), placeholder?: bool (placeholder text instead, as text.create), justify?, leadingModel?, charAlign? (as text.create)} turn a path into an area-type frame or a type-on-a-path baseline (the path's paint is dropped) → {id, bounds: {x, y, width, height} (as text.create), overflow?: [id] (the text doesn't fit; left out when it fits)}",
has_doc,
create_in_path
),
@@ -609,7 +609,8 @@ fn create_in_path(s: &mut Session, p: &Value) -> Result<Value> {
sel.set([id]);
Ok(id)
})?;
Ok(super::typecmd::with_overflow(s, &[id], json!({"id": id.0})))
let made = super::typecmd::with_bounds(s, Some(id), json!({"id": id.0}));
Ok(super::typecmd::with_overflow(s, &[id], made))
}
/// What `type.step` changes.
+35
View File
@@ -269,6 +269,17 @@ pub(crate) fn with_overflow(s: &Session, ids: &[NodeId], mut result: Value) -> V
result
}
/// Adds `bounds` to the result of a command that made the type object `id`: the same
/// `{x, y, width, height}` box `document.inspect` reports for it (after `justify`, so a script can
/// place the new type without a helper object); `null` when it has none.
pub(crate) fn with_bounds(s: &Session, id: Option<NodeId>, mut result: Value) -> Value {
let Ok(st) = s.doc() else { return result };
let symbols = st.doc.symbol_boxes();
let b = id.and_then(|i| st.doc.node(i)).and_then(|n| n.geometric_bounds_with(&symbols));
result["bounds"] = crate::inspect::rect_json(b);
result
}
/// The warning that Print gives for `ids`, area type and type on a path whose text doesn't fit
/// (as [`with_overflow`] finds it): the text past the end of their frames doesn't print. It names
/// the first 20 ids and counts the rest; `None` when `ids` is empty.
@@ -783,6 +794,30 @@ fn reshape_area(s: &mut Session, p: &Value) -> Result<Value> {
mod area_tests {
use super::*;
/// #2413: `text.create` and `text.createInPath` return the new type's `bounds`, the box
/// `document.inspect` reports for it, so a script can place it without a helper object.
#[test]
fn text_create_returns_the_inspect_bounds() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 600, "height": 400})).unwrap();
let inspected = |s: &mut Session, id: &Value| {
let doc = s.execute("document.inspect", &json!({})).unwrap();
fn find(v: &Value, id: &Value) -> Option<Value> {
if &v["id"] == id {
return Some(v["bounds"].clone());
}
v.get("children").or_else(|| v.get("layers"))?.as_array()?.iter().find_map(|c| find(c, id))
}
find(&doc, id).unwrap()
};
let made = s.execute("text.create", &json!({"x": 300, "y": 100, "text": "Hello", "size": 24, "justify": "center"})).unwrap();
assert!(made["bounds"]["width"].as_f64().unwrap() > 10.0, "{made}");
assert_eq!(made["bounds"], inspected(&mut s, &made["id"]));
let rect = s.execute("shape.rectangle", &json!({"x": 10, "y": 200, "width": 100, "height": 50})).unwrap();
let area = s.execute("text.createInPath", &json!({"path": rect["id"], "mode": "area", "text": "Boxed"})).unwrap();
assert_eq!(area["bounds"], inspected(&mut s, &area["id"]));
}
/// #1748: `text.create` takes `justify`; point type then lines up on its point (centred on
/// it, or ending there), and `text.setStyle` moves it the same way afterwards.
#[test]
+1 -1
View File
@@ -6,7 +6,7 @@ use vectorcraft_doc::{ArrowAlign, Document, LineCap, LineJoin, Node, NodeKind, S
use crate::Session;
fn rect_json(r: Option<vectorcraft_geom::Rect>) -> Value {
pub(crate) fn rect_json(r: Option<vectorcraft_geom::Rect>) -> Value {
match r {
Some(r) => json!({ "x": r.x0, "y": r.y0, "width": r.width(), "height": r.height() }),
None => Value::Null,
+2 -1
View File
@@ -3165,7 +3165,8 @@ Text that doesn't fit is drawn up to the last line that does, so a one-line head
draws nothing. The commands that make type or change what it holds say so in their own reply: `text.create`,
`text.createInPath`, `text.setText` and `text.setStyle` add `overflow: [id…]` (the area type and type on a path among
the objects they made or changed whose text doesn't fit), and the `add_text` tool adds `overflow: true`. The key is
left out when everything fits. The warnings of `file.print` and `print.preview` give the ids of the objects that print
left out when everything fits. `text.create` and `text.createInPath` also return `bounds: {x, y, width, height}`, the new type's box
as `document.inspect` reports it (after `justify`), so a script can place the type without a helper object (#2413). The warnings of `file.print` and `print.preview` give the ids of the objects that print
and whose text doesn't fit: the first 20 ids, then how many more.
```json