Type: Release Type on a Path keeps the size Dynamic Text drew the type at

Release Type on a Path copied the stored runs into point type with the
Dynamic Text flag still set, so the text dropped back to its stored size
and the file saved the flag on point type. The point type now gets the
drawn sizes and the flag is turned off.

The legacy .ai writer writes Dynamic Text type at its drawn sizes; it
now also warns that the setting itself is not written, as the editing
data writer does, whether the type is written as text or as outlines.

docs/target-app-parity.md lists reshaping or scaling the path among
VectorCraft's own rules, and a task bar comment marks leaving the item
out for threaded type as VectorCraft's own rule.
This commit is contained in:
dotCooCoo
2026-11-11 13:06:51 -08:00
parent 6e873b806c
commit b426822a9c
8 changed files with 74 additions and 13 deletions
+7 -4
View File
@@ -21,7 +21,7 @@ use super::super::menucmds::whole_or;
use super::super::object::choice;
use super::super::textedit::{CharChange, text_mut, text_ref, type_ranges};
use super::super::typecmd::refresh_bounds;
use super::super::typemenu::text_ids;
use super::super::typemenu::{bake_dynamic_text, text_ids};
use super::super::{CommandSpec, bad, cmd, has_selection, number_or, switch_or, targets};
use crate::{EngineError, Result, Session};
@@ -61,7 +61,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Release Type on a Path…",
["Object", "Plug-ins", "Type Tools"],
None,
"{mode?: \"both\" (point type and the path) | \"path\" (the path only) | \"text\" (point type only) (\"both\"), keepOriginal?: false (true: the type on a path stays and the released objects go just above it), ids?: [id…], id? (instead of ids; neither: the selection)} split each type on a path among the objects (inside groups too) into point type and the path it was set on. The point type holds the same text with the same character and paragraph attributes, on one straight line whose baseline starts where the first character sat on the path; Type on a Path Options (effect, align to path, spacing, flip, brackets) don't apply to point type and are dropped. The path keeps its exact shape (open or closed, anchors and handles, the object's transform) and comes out with no fill and no stroke, in the type's layer and place in the stacking order. Other objects are left alone, and with no type on a path among them nothing changes and no undo step is made. One undo step; the released objects are selected → {ids (released, bottom to top), paths, texts}",
"{mode?: \"both\" (point type and the path) | \"path\" (the path only) | \"text\" (point type only) (\"both\"), keepOriginal?: false (true: the type on a path stays and the released objects go just above it), ids?: [id…], id? (instead of ids; neither: the selection)} split each type on a path among the objects (inside groups too) into point type and the path it was set on. The point type holds the same text with the same character and paragraph attributes, on one straight line whose baseline starts where the first character sat on the path; Type on a Path Options (effect, align to path, spacing, flip, brackets) don't apply to point type and are dropped; type with Dynamic Text on (type.dynamicText) comes out with it off, at the sizes it was drawn at. The path keeps its exact shape (open or closed, anchors and handles, the object's transform) and comes out with no fill and no stroke, in the type's layer and place in the stacking order. Other objects are left alone, and with no type on a path among them nothing changes and no undo step is made. One undo step; the released objects are selected → {ids (released, bottom to top), paths, texts}",
has_selection,
release_path
),
@@ -230,8 +230,9 @@ fn first_pen(lay: &vectorcraft_text::TextLayout) -> Option<Point> {
/// `t`, type on a path, as point type on one straight line: the same runs and character and
/// paragraph attributes, its baseline starting where its first character sat on the path (in the
/// object's own text space, so its transform stays). Type on a Path Options are dropped. None for
/// other type, or when it can't be placed.
/// object's own text space, so its transform stays). Type on a Path Options are dropped, and
/// Dynamic Text is turned off at the sizes it draws the text at. None for other type, or when it
/// can't be placed.
fn released_text(t: &TextObject) -> Option<TextObject> {
let TextKind::OnPath { path, .. } = &t.kind else { return None };
let db = vectorcraft_text::FontDb::global();
@@ -244,6 +245,8 @@ fn released_text(t: &TextObject) -> Option<TextObject> {
(p.x.is_finite() && p.y.is_finite()).then_some(p)
})?;
let mut out = t.clone();
// Point type has no brackets to fit: it keeps the sizes the path drew it at.
bake_dynamic_text(&mut out);
out.kind = TextKind::Point;
out.path_effect = Default::default();
out.path_align = Default::default();
+20
View File
@@ -238,6 +238,26 @@ fn svg_export_writes_the_drawn_size() {
assert!(!sizes.iter().any(|v| (v - 12.0).abs() < 1e-3), "the stored size is not written: {sizes:?}");
}
#[test]
fn release_type_on_a_path_keeps_the_drawn_size() {
let mut s = session();
let id = on_line(&mut s, "Release");
s.execute("type.dynamicText", &json!({"on": true})).unwrap();
let size = 12.0 * fit(&s, id);
assert!(size > 24.0, "the text grows on the long line: {size}");
let r = s.execute("addon.type.releasePath", &json!({"ids": [id.0], "mode": "text"})).unwrap();
let released = NodeId(r["texts"][0].as_u64().unwrap());
let t = text(&s, released);
assert!(!t.dynamic_text, "point type does not keep Dynamic Text");
assert!((t.runs[0].style.size - size).abs() < 1e-9, "{} vs the drawn {size}", t.runs[0].style.size);
assert_eq!(fit(&s, released), 1.0);
// Type on a path without Dynamic Text keeps its stored size.
let plain = on_line(&mut s, "Plain");
let r = s.execute("addon.type.releasePath", &json!({"ids": [plain.0], "mode": "text"})).unwrap();
let t = text(&s, NodeId(r["texts"][0].as_u64().unwrap()));
assert!(!t.dynamic_text && t.runs[0].style.size == 12.0);
}
/// The id of the first text object in `s`'s document.
fn first_text(s: &Session) -> NodeId {
let mut found = None;