Merge pull request #6717 from dotCooCoo/feat/type-path-center-on-top

Type: Center on Top and Center on Bottom for type on a closed path
This commit is contained in:
BFlat
2026-11-12 21:04:31 +07:00
committed by GitHub
37 changed files with 727 additions and 3 deletions
@@ -645,6 +645,8 @@ pub(super) const ALLOWED: &[&str] = &[
"type.opticalMarginAlignment",
"type.orientation.horizontal",
"type.orientation.vertical",
"type.pathCenter.bottom",
"type.pathCenter.top",
"type.pathOptions",
"type.resetHorizontalScale",
"type.resetTracking",
+148
View File
@@ -60,6 +60,24 @@ pub fn specs() -> Vec<CommandSpec> {
has_selection,
path_options
),
cmd!(
"type.pathCenter.top",
"Center on Top",
["Type", "Type on a Path"],
None,
"{ids?|id?} slide the selected type on a closed path along it so that the middle of the span between its start and end brackets lands on the path's topmost point as the object is drawn (after its transform, so a rotated, scaled or skewed path is measured as it appears; on a level stretch such as a rectangle's top edge, its middle; with several separate topmost points, the middle of the shortest part of the path that holds them all), keeping the span's length (a full turn stays a full turn), and set the first paragraph's alignment to Center unless it is Center or Justify Center, so the type is centered there. Point type and area type in the selection are left as they are. Type on an open or compound path, type in a thread, and type on a path with no length or no height are refused with an error that names them, and nothing changes. When every object is already centered there, nothing changes and no undo step is recorded → {ids, start, end} (the first object's brackets)",
has_closed_path_type,
path_center_top
),
cmd!(
"type.pathCenter.bottom",
"Center on Bottom",
["Type", "Type on a Path"],
None,
"{ids?|id?} as type.pathCenter.top, on the path's bottommost point as drawn. The type is not flipped: on the bottom of a path that runs clockwise it reads upside down until Type on a Path Options › Flip turns it → {ids, start, end} (the first object's brackets)",
has_closed_path_type,
path_center_bottom
),
cmd!(
"type.dynamicText",
"Dynamic Text",
@@ -430,6 +448,136 @@ fn to_point(s: &mut Session, p: &Value) -> Result<Value> {
Ok(ids_json(&ids))
}
/// Whether type on a path `t` runs along one closed path (no open or compound path).
fn on_one_closed_path(t: &TextObject) -> bool {
matches!(&t.kind, TextKind::OnPath { path, .. } if path.subpaths.len() == 1 && path.is_closed())
}
/// Why `type.pathCenter.top` and `.bottom` refuse type on a path `ids`: those on an open or
/// compound path, then those in a thread; None when they take every one.
fn path_center_refusal(d: &Document, ids: &[NodeId]) -> Option<String> {
let open = ids.iter().filter(|id| !path_text(d.node(**id)).is_some_and(on_one_closed_path)).map(|id| id.0.to_string()).collect::<Vec<_>>();
if !open.is_empty() {
let open = open.join(", ");
return Some(format!("type on an open or compound path cannot be centered on its top or bottom: {open}"));
}
let frames = threaded_ids(d, ids);
(!frames.is_empty()).then(|| format!("type on a path in a thread cannot be centered on its top or bottom: {frames}"))
}
/// `type.pathCenter.top` and `.bottom` are enabled for a selection holding type on a path, all of
/// it on one closed path and none of it in a thread.
fn has_closed_path_type(s: &Session) -> std::result::Result<(), String> {
has_selection(s)?;
let st = s.active().ok_or("no document open")?;
let d = &st.doc;
let selected: Vec<NodeId> = st.selection.objects.to_vec();
let paths: Vec<NodeId> = text_ids(d, &selected).into_iter().filter(|id| path_text(d.node(*id)).is_some()).collect();
if paths.is_empty() {
return Err("select type on a closed path".into());
}
path_center_refusal(d, &paths).map_or(Ok(()), Err)
}
/// The brackets that center type on a path `t` on its path's topmost point (`bottom`: its
/// bottommost) as `t.xf` draws it, its span keeping its length; None for a path with no length or
/// no height.
fn centered_brackets(t: &TextObject, bottom: bool) -> Option<(f64, Option<f64>)> {
let TextKind::OnPath { path, end, .. } = &t.kind else { return None };
let (from, to) = t.kind.path_span()?;
let arc = vectorcraft_geom::ArcPath::new(path);
if arc.is_empty() {
return None;
}
let flip = if bottom { Affine::FLIP_Y } else { Affine::IDENTITY };
let middle = arc.topmost(flip * t.xf)? / arc.len();
let run = to - from;
let start = (middle - run * 0.5).rem_euclid(1.0).clamp(0.0, 1.0);
// Without an end bracket the type runs once round; a full turn's end stays on its start.
let end = end.map(|_| if run >= 1.0 - 1e-12 { start } else { (start + run).rem_euclid(1.0).clamp(0.0, 1.0) });
Some((start, end))
}
/// Whether brackets `a` and `b`, fractions of a closed path's length, are at one place within
/// rounding (0 and 1 are one place).
fn same_bracket(a: f64, b: f64) -> bool {
let d = (a - b).rem_euclid(1.0);
d.min(1.0 - d) <= 1e-9
}
fn path_center_top(s: &mut Session, p: &Value) -> Result<Value> {
path_center(s, p, false)
}
fn path_center_bottom(s: &mut Session, p: &Value) -> Result<Value> {
path_center(s, p, true)
}
/// Type › Type on a Path › Center on Top and Center on Bottom: slide the selected type on closed
/// paths along them so the middle of each span lands on the path's top (`bottom`: its bottom) as
/// drawn, and center the first paragraph, in one undo step; none when nothing changes.
fn path_center(s: &mut Session, p: &Value, bottom: bool) -> Result<Value> {
let c = if bottom { "type.pathCenter.bottom" } else { "type.pathCenter.top" };
let centered = |j: Justify| matches!(j, Justify::Center | Justify::JustifyCenter);
// Each object the command changes, with its new brackets when they move and whether its first
// paragraph is set to Center.
let (ids, changes) = {
let d = &s.doc()?.doc;
let ids: Vec<NodeId> = texts(s, p, c)?.into_iter().filter(|i| path_text(d.node(*i)).is_some()).collect();
if ids.is_empty() {
return Err(bad(c, "select type on a closed path"));
}
if let Some(why) = path_center_refusal(d, &ids) {
return Err(bad(c, why));
}
let (mut changes, mut flat) = (vec![], vec![]);
for id in &ids {
let Some(t) = path_text(d.node(*id)) else { continue };
let TextKind::OnPath { start, end, .. } = &t.kind else { continue };
let Some((to_start, to_end)) = centered_brackets(t, bottom) else {
flat.push(id.0.to_string());
continue;
};
let stays = same_bracket(*start, to_start)
&& match (end, to_end) {
(None, None) => true,
(Some(a), Some(b)) => same_bracket(*a, b),
_ => false,
};
let align = !centered(t.para_at(0).justify);
if !stays || align {
changes.push((*id, (!stays).then_some((to_start, to_end)), align));
}
}
if !flat.is_empty() {
let flat = flat.join(", ");
return Err(bad(c, format!("type on a path with no length or no height cannot be centered: {flat}")));
}
(ids, changes)
};
if !changes.is_empty() {
s.edit(if bottom { "Center on Bottom" } else { "Center on Top" }, |d, _| {
for (id, brackets, align) in &changes {
let Some(NodeKind::Text(t)) = d.node_mut(*id).map(|n| &mut n.kind) else { continue };
if let (Some(b), TextKind::OnPath { start, end, .. }) = (brackets, &mut t.kind) {
(*start, *end) = *b;
}
if *align {
t.edit_paras(Some(0..1), |para| para.justify = Justify::Center);
}
refresh_bounds(t);
}
Ok(())
})?;
}
let d = &s.doc()?.doc;
let (start, end) = ids.first().and_then(|id| path_text(d.node(*id))).map_or((0.0, None), |t| match &t.kind {
TextKind::OnPath { start, end, .. } => (*start, *end),
_ => (0.0, None),
});
Ok(json!({"ids": ids.iter().map(|i| i.0).collect::<Vec<_>>(), "start": start, "end": end}))
}
/// The options `type.pathOptions` sets; with none of them given it queries.
const PATH_OPTIONS: [&str; 6] = ["start", "end", "flip", "effect", "alignToPath", "spacing"];
+178
View File
@@ -184,6 +184,184 @@ fn a_flip_round_a_closed_path_keeps_the_type_in_place() {
assert!(origins(&s, id)[0].distance(first) < 8.0);
}
/// A circle of radius 100 round (200, 200), starting at 9 o'clock and running clockwise, with
/// type on it from its start.
fn on_circle(s: &mut Session, text: &str) -> NodeId {
path_type(s, "M100 200 A100 100 0 1 1 300 200 A100 100 0 1 1 100 200 Z", [90.0, 200.0], text)
}
#[test]
fn center_on_top_and_bottom_of_a_circle() {
let mut s = session();
let id = on_circle(&mut s, "OOOO");
assert!(near(brackets(&s, id).0, 0.0), "{:?}", brackets(&s, id));
let steps = undo_len(&s);
let r = s.execute("type.pathCenter.top", &json!({})).unwrap();
// Once round, its middle at 12 o'clock, a quarter of the way round: the type sits on the top, centered.
let (start, end) = brackets(&s, id);
assert!(near(start, 0.75) && end.is_none(), "{start} {end:?}");
assert_eq!((r["ids"].clone(), r["end"].clone()), (json!([id.0]), Value::Null));
assert_eq!(text(&s, id).para_at(0).justify, vectorcraft_doc::Justify::Center);
let top = ink(&s, id);
assert!((top.center().x - 200.0).abs() < 0.5 && top.y1 < 101.0, "{top:?}");
assert_eq!((undo_len(&s), last_step(&s)), (steps + 1, "Center on Top".to_string()));
// Again: nothing moves, and no undo step is recorded.
s.execute("type.pathCenter.top", &json!({})).unwrap();
assert_eq!(undo_len(&s), steps + 1);
// The bottom: 6 o'clock, outside the circle (the type is not flipped).
s.execute("type.pathCenter.bottom", &json!({})).unwrap();
let (start, end) = brackets(&s, id);
assert!(near(start, 0.25) && end.is_none(), "{start} {end:?}");
let bottom = ink(&s, id);
assert!((bottom.center().x - 200.0).abs() < 0.5 && bottom.y0 > 299.0, "{bottom:?}");
assert_eq!((undo_len(&s), last_step(&s)), (steps + 2, "Center on Bottom".to_string()));
// Justify Center stays as it is.
s.execute("text.setStyle", &json!({"justify": "justifyCenter"})).unwrap();
s.execute("type.pathCenter.top", &json!({})).unwrap();
assert_eq!(text(&s, id).para_at(0).justify, vectorcraft_doc::Justify::JustifyCenter);
s.execute("edit.undo", &json!({})).unwrap();
assert!(near(brackets(&s, id).0, 0.25));
// Brackets already in place with another alignment: only the alignment changes, in one step.
s.execute("text.setStyle", &json!({"justify": "left"})).unwrap();
let (steps, before) = (undo_len(&s), brackets(&s, id));
s.execute("type.pathCenter.bottom", &json!({})).unwrap();
assert_eq!((undo_len(&s), brackets(&s, id)), (steps + 1, before));
assert_eq!(text(&s, id).para_at(0).justify, vectorcraft_doc::Justify::Center);
}
#[test]
fn center_on_top_twice_records_one_step_for_any_span() {
let mut s = session();
let id = on_circle(&mut s, "OOOO");
for (start, end) in [(0.0, 0.2), (0.13, 0.41), (0.37, 0.52), (0.61, 0.97), (0.89, 0.07), (0.3, 0.3001)] {
s.execute("type.pathOptions", &json!({"start": start, "end": end})).unwrap();
for c in ["type.pathCenter.top", "type.pathCenter.bottom"] {
s.execute(c, &json!({})).unwrap();
let (steps, placed) = (undo_len(&s), brackets(&s, id));
// Again: the brackets stay exactly where they are, and no undo step is recorded.
s.execute(c, &json!({})).unwrap();
assert_eq!((undo_len(&s), brackets(&s, id)), (steps, placed), "{c} {start} {end}");
}
}
}
#[test]
fn center_on_top_keeps_the_span_and_measures_the_path_as_drawn() {
let mut s = session();
let id = on_circle(&mut s, "OOOO");
// A span of a fifth of the path keeps its length.
s.execute("type.pathOptions", &json!({"end": 0.2})).unwrap();
s.execute("type.pathCenter.top", &json!({})).unwrap();
let (start, end) = brackets(&s, id);
assert!(near(start, 0.15) && end.is_some_and(|e| near(e, 0.35)), "{start} {end:?}");
// Turned a quarter turn counter-clockwise, its 3 o'clock point, halfway round, is at the top.
s.execute("type.pathOptions", &json!({"start": 0, "end": null})).unwrap();
s.execute("object.rotate", &json!({"angle": 90})).unwrap();
s.execute("type.pathCenter.top", &json!({})).unwrap();
let (start, _) = brackets(&s, id);
assert!(near(start.rem_euclid(1.0), 0.0) || near(start, 1.0), "{start}");
let bounds = text(&s, id).bounds().unwrap();
let top = ink(&s, id);
assert!((top.center().x - bounds.center().x).abs() < 0.5 && top.y1 < bounds.y0 + 1.0, "{top:?} {bounds:?}");
// A rectangle: the middle of its top edge.
let id = path_type(&mut s, "M100 100 L300 100 L300 300 L100 300 Z", [90.0, 100.0], "OOOO");
s.execute("type.pathCenter.top", &json!({})).unwrap();
let (start, end) = brackets(&s, id);
assert!(near(start, 0.625) && end.is_none(), "{start} {end:?}");
assert!((ink(&s, id).center().x - 200.0).abs() < 0.5);
}
#[test]
fn center_on_top_moves_every_selected_type_on_a_path_in_one_step() {
let mut s = session();
let a = on_circle(&mut s, "OOOO");
let b = path_type(&mut s, "M100 100 L300 100 L300 300 L100 300 Z", [90.0, 100.0], "OOOO");
let p = s.execute("text.create", &json!({"x": 40, "y": 40, "text": "Point"})).unwrap()["id"].as_u64().unwrap();
s.execute("select.set", &json!({"ids": [a.0, b.0, p]})).unwrap();
let steps = undo_len(&s);
let r = s.execute("type.pathCenter.top", &json!({})).unwrap();
assert_eq!(r["ids"], json!([a.0, b.0]));
assert!(near(brackets(&s, a).0, 0.75) && near(brackets(&s, b).0, 0.625));
assert_eq!(undo_len(&s), steps + 1);
// Dynamic Text stays on, and the layout fits the text to the span where it now runs: a fifth of
// the circle, at its bottom.
s.execute("select.set", &json!({"ids": [a.0]})).unwrap();
s.execute("type.pathOptions", &json!({"start": 0, "end": 0.2})).unwrap();
s.execute("type.dynamicText", &json!({"on": true})).unwrap();
s.execute("type.pathCenter.bottom", &json!({})).unwrap();
assert!(text(&s, a).dynamic_text && near(brackets(&s, a).0, 0.65), "{:?}", brackets(&s, a));
let lay = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &text(&s, a));
assert!(lay.fit_scale > 1.5 && !lay.overflow, "{}", lay.fit_scale);
let bottom = ink(&s, a);
assert!((bottom.center().x - 200.0).abs() < 1.0 && bottom.y1 > 300.0, "{bottom:?}");
// Vertical type on a path moves the same way.
s.execute("type.pathOptions", &json!({"start": 0, "end": null})).unwrap();
s.execute("type.orientation.vertical", &json!({})).unwrap();
s.execute("type.pathCenter.top", &json!({})).unwrap();
assert!(near(brackets(&s, a).0, 0.75));
}
#[test]
fn center_on_top_refuses_open_compound_and_threaded_type() {
let mut s = session();
let circle = on_circle(&mut s, "OOOO");
let line = on_line(&mut s);
s.execute("select.set", &json!({"ids": [circle.0, line.0]})).unwrap();
let steps = undo_len(&s);
let before = brackets(&s, circle);
let e = s.execute("type.pathCenter.top", &json!({})).unwrap_err().to_string();
assert!(e.contains("type on an open or compound path cannot be centered on its top or bottom") && e.ends_with(&line.0.to_string()), "{e}");
// Nothing changes, the type on the circle included, and the same holds for an explicit list.
assert_eq!((undo_len(&s), brackets(&s, circle)), (steps, before));
let e = s.execute("type.pathCenter.bottom", &json!({"ids": [circle.0, line.0]})).unwrap_err().to_string();
assert!(e.contains("open or compound path") && e.ends_with(&line.0.to_string()), "{e}");
s.execute("select.set", &json!({"ids": [circle.0]})).unwrap();
for bad in [json!({"ids": "all"}), json!({"ids": [999_999]})] {
assert!(s.execute("type.pathCenter.top", &bad).is_err(), "{bad}");
}
assert_eq!(undo_len(&s), steps);
// Two circles side by side in one path: a compound path.
let pair = path_type(
&mut s,
"M100 450 A40 40 0 1 1 180 450 A40 40 0 1 1 100 450 Z M300 450 A40 40 0 1 1 380 450 A40 40 0 1 1 300 450 Z",
[90.0, 450.0],
"OOOO",
);
let e = s.execute("type.pathCenter.top", &json!({})).unwrap_err().to_string();
assert!(e.contains("open or compound path") && e.ends_with(&pair.0.to_string()), "{e}");
// A closed path that is level all round has no height.
let flat = path_type(&mut s, "M100 400 L300 400 Z", [90.0, 400.0], "OOOO");
let e = s.execute("type.pathCenter.top", &json!({})).unwrap_err().to_string();
assert!(e.contains("type on a path with no length or no height cannot be centered") && e.ends_with(&flat.0.to_string()), "{e}");
// A frame of a thread is refused, alone or with other type on a closed path.
let threaded = on_circle(&mut s, "Threaded story");
let frame = s.execute("shape.rectangle", &json!({"x": 400, "y": 100, "width": 200, "height": 100})).unwrap()["id"].as_u64().unwrap();
s.execute("select.set", &json!({"ids": [threaded.0, frame]})).unwrap();
s.execute("text.thread.create", &json!({})).unwrap();
s.execute("select.set", &json!({"ids": [threaded.0]})).unwrap();
let before = brackets(&s, threaded);
for ids in [json!([threaded.0]), json!([circle.0, threaded.0])] {
let steps = undo_len(&s);
let e = s.execute("type.pathCenter.top", &json!({ "ids": ids })).unwrap_err().to_string();
assert!(e.contains("type on a path in a thread cannot be centered on its top or bottom") && e.ends_with(&threaded.0.to_string()), "{e}");
assert_eq!((undo_len(&s), brackets(&s, threaded)), (steps, before));
}
// Without type on one closed path selected, the commands are not available.
let p = s.execute("text.create", &json!({"x": 40, "y": 40, "text": "Point"})).unwrap()["id"].as_u64().unwrap();
for (ids, why) in [
(json!([p]), "select type on a closed path"),
(json!([line.0]), "open or compound path"),
(json!([circle.0, line.0]), "open or compound path"),
(json!([threaded.0]), "in a thread"),
] {
s.execute("select.set", &json!({ "ids": ids })).unwrap();
for c in ["type.pathCenter.top", "type.pathCenter.bottom"] {
let e = s.execute(c, &json!({})).unwrap_err().to_string();
assert!(e.contains(why), "{c} {ids}: {e}");
}
}
}
#[test]
fn align_to_path_and_spacing() {
let mut s = session();
+133 -1
View File
@@ -2,7 +2,7 @@
//! distance along it of the point nearest another (type on a path lays its glyphs out and the
//! selection tools drag its brackets by distance along the path).
use kurbo::{ParamCurve, ParamCurveArclen, ParamCurveNearest, PathSeg, Point, Vec2};
use kurbo::{Affine, ParamCurve, ParamCurveArclen, ParamCurveExtrema, ParamCurveNearest, PathSeg, Point, Vec2};
use crate::PathData;
@@ -76,6 +76,78 @@ impl ArcPath {
pub fn fraction_at(&self, p: Point) -> Option<f64> {
(self.len > 0.0).then(|| self.nearest(p)).flatten().map(|s| (s / self.len).clamp(0.0, 1.0))
}
/// The distance along the path of the middle of its topmost points (the least y) where `xf`
/// draws it: the topmost point when there is one, the middle of a level stretch (a
/// rectangle's top edge), and when several points or stretches are topmost, the middle of the
/// shortest part of the path that holds them all. None for a path with no length, or a closed
/// path whose every point is topmost.
pub fn topmost(&self, xf: Affine) -> Option<f64> {
let drawn: Vec<(PathSeg, PathSeg, f64, f64)> = self.segs.iter().map(|&(s, c, l)| (s, xf * s, c, l)).collect();
// The candidates for the least y in each segment: its ends and where it turns.
let turns = |d: &PathSeg| [0.0, 1.0].into_iter().chain(d.extrema()).collect::<Vec<f64>>();
let mut bounds: Option<kurbo::Rect> = None;
let mut top = f64::INFINITY;
for (_, d, _, _) in &drawn {
let b = d.bounding_box();
bounds = Some(bounds.map_or(b, |a| a.union(b)));
top = turns(d).into_iter().map(|t| d.eval(t).y).fold(top, f64::min);
}
let b = bounds?;
if !(top.is_finite() && b.width().is_finite() && b.height().is_finite()) {
return None;
}
// Points this close to the top count as topmost: a part of the drawn size, and no less than
// the rounding error of coordinates this far from the origin.
let tol = (1e-9 * (b.width() + b.height())).max(64.0 * f64::EPSILON * b.y0.abs().max(b.y1.abs()));
// Stretches this close along the path meet.
let near = 1e-9 * self.len;
// The topmost points as stretches of distance along the path, in order.
let mut runs: Vec<(f64, f64)> = vec![];
for (s, d, c, l) in &drawn {
let db = d.bounding_box();
if db.y1 - db.y0 <= tol && db.y0 <= top + tol {
runs.push((*c, c + l));
continue;
}
for t in turns(d) {
if d.eval(t).y <= top + tol {
let at = c + s.subsegment(0.0..t).arclen(ACCURACY).min(*l);
runs.push((at, at));
}
}
}
runs.sort_by(|a, b| a.0.total_cmp(&b.0));
// Stretches that meet or overlap are one.
let mut merged: Vec<(f64, f64)> = vec![];
for (a, z) in runs {
match merged.last_mut() {
Some(last) if a <= last.1 + near => last.1 = last.1.max(z),
_ => merged.push((a, z)),
}
}
let (first, last) = (*merged.first()?, *merged.last()?);
if !self.closed {
return Some((first.0 + last.1) * 0.5);
}
let len = self.len;
// Round a closed path, the shortest part holding every stretch leaves out the longest
// gap between two of them (the gap from the last round to the first included).
let wrap = first.0 + len - last.1;
let (mut gap, mut from, mut to) = (wrap, first.0, last.1);
for w in merged.windows(2) {
if let [a, b] = w
&& b.0 - a.1 > gap
{
(gap, from, to) = (b.0 - a.1, b.0, a.1);
}
}
if gap <= near {
return None;
}
let run = (to - from).rem_euclid(len);
Some((from + run * 0.5).rem_euclid(len))
}
}
#[cfg(test)]
@@ -99,4 +171,64 @@ mod tests {
assert!(sq.at(45.0).0.distance(sq.at(5.0).0) < 1e-6);
assert!(ArcPath::new(&PathData::default()).is_empty());
}
fn polyline(points: &[Point], closed: bool) -> PathData {
PathData::single(crate::SubPath::polyline(points, closed))
}
/// Where `topmost` puts the middle, drawn by `xf`.
fn top_point(a: &ArcPath, xf: Affine) -> Point {
xf * a.at(a.topmost(xf).unwrap()).0
}
#[test]
fn topmost_points_as_drawn() {
// A circle starts at 9 o'clock and runs clockwise (y down): its top is a quarter of the way round, its
// bottom three quarters.
let circle = ArcPath::new(&shapes::ellipse(Rect::new(0.0, 0.0, 100.0, 100.0)));
assert!((circle.topmost(Affine::IDENTITY).unwrap() - circle.len() * 0.25).abs() < 1e-6);
assert!((circle.topmost(Affine::FLIP_Y).unwrap() - circle.len() * 0.75).abs() < 1e-6);
// As the transform draws it: turned a quarter turn the other way, 3 o'clock is at the top.
let turned = Affine::rotate_about(-std::f64::consts::FRAC_PI_2, Point::new(50.0, 50.0));
assert!((circle.topmost(turned).unwrap() - circle.len() * 0.5).abs() < 1e-6);
// Turned part way, the top lies inside a segment.
let tilted = Affine::rotate_about(0.4, Point::new(50.0, 50.0));
assert!(top_point(&circle, tilted).distance(Point::new(50.0, 0.0)) < 0.05, "{:?}", top_point(&circle, tilted));
// An ellipse squashed and skewed by its transform.
let skewed = Affine::skew(0.5, 0.0) * Affine::scale_non_uniform(2.0, 0.5);
let p = top_point(&circle, skewed);
let lowest = (0..3600).map(|i| (skewed * circle.at(circle.len() * i as f64 / 3600.0).0).y).fold(f64::INFINITY, f64::min);
assert!((p.y - lowest).abs() < 1e-3, "{p:?} {lowest}");
// A level top: the middle of the top edge, for a rectangle and a rounded rectangle.
for path in [shapes::rectangle(Rect::new(10.0, 20.0, 110.0, 70.0)), shapes::rounded_rectangle(Rect::new(10.0, 20.0, 110.0, 70.0), 8.0)] {
let a = ArcPath::new(&path);
assert!(top_point(&a, Affine::IDENTITY).distance(Point::new(60.0, 20.0)) < 1e-6, "{:?}", top_point(&a, Affine::IDENTITY));
assert!(top_point(&a, Affine::FLIP_Y).distance(Point::new(60.0, -70.0)) < 1e-6);
}
// Far from the origin the top is still the top edge's middle.
let square = ArcPath::new(&shapes::rectangle(Rect::new(0.0, 0.0, 100.0, 100.0)));
let far = Affine::translate((1e12, 1e12));
assert!(top_point(&square, far).distance(Point::new(1e12 + 50.0, 1e12)) < 1e-3, "{:?}", top_point(&square, far));
// A level top across the path's start: the start in the middle of the top edge.
let across = ArcPath::new(&polyline(
&[Point::new(50.0, 0.0), Point::new(100.0, 0.0), Point::new(100.0, 50.0), Point::new(0.0, 50.0), Point::new(0.0, 0.0)],
true,
));
assert!(top_point(&across, Affine::IDENTITY).distance(Point::new(50.0, 0.0)) < 1e-6);
// Two peaks: the middle of the shortest part that holds both, the dip between them.
let peaks = ArcPath::new(&polyline(
&[Point::new(0.0, 10.0), Point::new(2.0, 0.0), Point::new(4.0, 5.0), Point::new(6.0, 0.0), Point::new(8.0, 10.0)],
true,
));
assert!(top_point(&peaks, Affine::IDENTITY).distance(Point::new(4.0, 5.0)) < 1e-6, "{:?}", top_point(&peaks, Affine::IDENTITY));
// An open path: the middle of its topmost points too.
let open = ArcPath::new(&polyline(&[Point::new(0.0, 10.0), Point::new(5.0, 0.0), Point::new(10.0, 10.0)], false));
assert!(top_point(&open, Affine::IDENTITY).distance(Point::new(5.0, 0.0)) < 1e-6);
// No top: a closed path that is level all round, and a path with no length.
let flat = ArcPath::new(&shapes::ellipse(Rect::new(0.0, 10.0, 100.0, 10.0)));
assert!(!flat.is_empty() && flat.topmost(Affine::IDENTITY).is_none());
assert!(ArcPath::new(&PathData::default()).topmost(Affine::IDENTITY).is_none());
let point = ArcPath::new(&polyline(&[Point::new(5.0, 5.0), Point::new(5.0, 5.0)], true));
assert!(point.topmost(Affine::IDENTITY).is_none());
}
}
+16 -1
View File
@@ -153,7 +153,10 @@ pub(super) fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) {
match kind {
TextKind::Area { .. } => items.push((tl!("Convert To Point Type"), "type", "type.convertToPointType", none())),
TextKind::Point => items.push((tl!("Convert To Area Type"), "dc-type-area", "type.convertToAreaType", none())),
TextKind::OnPath { .. } => items.push((tl!("Type on a Path Options"), "dc-type-path", "type.pathOptions", none())),
TextKind::OnPath { .. } => {
items.push((tl!("Type on a Path Options"), "dc-type-path", "type.pathOptions", none()));
center_on_path_items(app, &mut items);
}
}
// Dynamic Text turns on or off for type on a path and area type (Block), as the Type menu's item does;
// left out for point type and type in a thread, which is VectorCraft's own rule
@@ -350,6 +353,18 @@ pub(super) fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) {
}
}
/// The Contextual Task Bar's Center on Top and Center on Bottom, as in the Type menu, for type on a
/// closed path.
fn center_on_path_items(app: &VectorcraftApp, items: &mut Vec<(&'static str, &'static str, &'static str, serde_json::Value)>) {
for (label, icon, id) in
[(tl!("Center on Top"), "dc-al-top", "type.pathCenter.top"), (tl!("Center on Bottom"), "dc-al-bottom", "type.pathCenter.bottom")]
{
if crate::menus::enabled(app, id) {
items.push((label, icon, id, json!({})));
}
}
}
/// The Contextual Task Bar's Image Trace button: a trace with the Default preset.
const TASK_BAR_IMAGE_TRACE: &str = "taskBar.imageTrace";
+2
View File
@@ -3634,6 +3634,8 @@ digits Malayalam Малаялам
Type on a Path Текст по пътя
Type on a Path Options Опции за текст по пътя
Type on a Path Options… Опции за текст по път…
Center on Top Центриране горе
Center on Bottom Центриране долу
Type on a Path Tool Инструмент „Текст по път“
Type Orientation Ориентация на текста
Type stays text, and every stroke is a line with its lineweight and dashes. Текстът остава текст, а всеки щрих е линия с дебелина и чертички.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -398,6 +398,8 @@
Stair Step Stepenice
Gravity Gravitacija
Type on a Path Options… Opcije teksta na putanji…
Center on Top Centriraj na vrhu
Center on Bottom Centriraj na dnu
Threaded Text Povezani tekst
Create Kreiraj
Release Selection Oslobodi odabir
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -2933,6 +2933,10 @@ digits Malayalam Malaiàlam
Arch Volta
@msg select point type or type on a path seleccioneu text de punt o text sobre un traçat
@msg type on a path in a thread cannot use Dynamic Text: {frames} el text sobre un traçat d’un text enllaçat no pot fer servir el text dinàmic: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} el text sobre un traçat obert o compost no es pot centrar a dalt o a baix: {open}
@msg type on a path with no length or no height cannot be centered: {flat} el text sobre un traçat sense longitud o sense alçada no es pot centrar: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} el text sobre un traçat d’un text enllaçat no es pot centrar a dalt o a baix: {frames}
@msg select type on a closed path seleccioneu text sobre un traçat tancat
Optimize for fast web view Optimitza per a la visualització web ràpida
Optimized Optimitzat
Options Opcions
@@ -4711,6 +4715,8 @@ digits Malayalam Malaiàlam
Type on a Path Text sobre un traçat
Type on a Path Options Opcions de text sobre un traçat
Type on a Path Options… Opcions de text sobre un traçat…
Center on Top Centra a dalt
Center on Bottom Centra a baix
Type on a Path Tool Eina de text sobre un traçat
Type Optimized Optimitzat per al text
Type Orientation Orientació del text
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -405,6 +405,8 @@
Stair Step Schody
Gravity Gravitace
Type on a Path Options… Možnosti textu na cestě…
Center on Top Vystředit nahoře
Center on Bottom Vystředit dole
Threaded Text Zřetězený text
Create Vytvořit
Release Selection Uvolnit výběr
@@ -425,6 +427,10 @@
Arch Klenba
@msg select point type or type on a path vyberte bodový text nebo text na cestě
@msg type on a path in a thread cannot use Dynamic Text: {frames} text na cestě ve zřetězeném textu nemůže používat dynamický text: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} text na otevřené nebo složené cestě nelze vystředit nahoře ani dole: {open}
@msg type on a path with no length or no height cannot be centered: {flat} text na cestě bez délky nebo bez výšky nelze vystředit: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} text na cestě ve zřetězeném textu nelze vystředit nahoře ani dole: {frames}
@msg select type on a closed path vyberte text na uzavřené cestě
Fill with Placeholder Text Vyplnit zástupným textem
Insert Inline Symbol Vložit symbol do textu
Show Hidden Characters Zobrazit skryté znaky
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -366,6 +366,8 @@
Stair Step Trappetrin
Gravity Tyngdekraft
Type on a Path Options… Indstillinger for tekst på kurve…
Center on Top Centrer øverst
Center on Bottom Centrer nederst
Threaded Text Sammenkædet tekst
Create Opret
Release Selection Frigør markering
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -2322,6 +2322,10 @@ file-filter Templates Vorlagen
Arch Rundbogen
@msg select point type or type on a path wählen Sie Punkttext oder Pfadtext aus
@msg type on a path in a thread cannot use Dynamic Text: {frames} Pfadtext in verkettetem Text kann keinen dynamischen Text verwenden: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} Pfadtext auf einem offenen oder zusammengesetzten Pfad lässt sich nicht oben oder unten zentrieren: {open}
@msg type on a path with no length or no height cannot be centered: {flat} Pfadtext auf einem Pfad ohne Länge oder ohne Höhe lässt sich nicht zentrieren: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} Pfadtext in verkettetem Text lässt sich nicht oben oder unten zentrieren: {frames}
@msg select type on a closed path wählen Sie Pfadtext auf einem geschlossenen Pfad aus
Optimize for fast web view Für schnelle Webanzeige optimieren
Optimized Optimiert
Options Optionen
@@ -3677,6 +3681,8 @@ effect Points Punkte
Type on a Path Pfadtext
Type on a Path Options Pfadtextoptionen
Type on a Path Options… Pfadtextoptionen…
Center on Top Oben zentrieren
Center on Bottom Unten zentrieren
Type on a Path Tool Pfadtext-Werkzeug
Type Orientation Textrichtung
Type stays text, and every stroke is a line with its lineweight and dashes. Text bleibt Text, und jede Kontur ist eine Linie mit ihrer Linienstärke und ihren Strichen.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -3095,6 +3095,10 @@ theme Appearance Εμφάνιση
Arch Αψίδα
@msg select point type or type on a path επιλέξτε κείμενο σημείου ή κείμενο σε διαδρομή
@msg type on a path in a thread cannot use Dynamic Text: {frames} το κείμενο σε διαδρομή μέσα σε συνδεδεμένο κείμενο δεν μπορεί να χρησιμοποιήσει δυναμικό κείμενο: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} κείμενο σε ανοιχτή ή σύνθετη διαδρομή δεν μπορεί να κεντραριστεί επάνω ή κάτω: {open}
@msg type on a path with no length or no height cannot be centered: {flat} κείμενο σε διαδρομή χωρίς μήκος ή χωρίς ύψος δεν μπορεί να κεντραριστεί: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} το κείμενο σε διαδρομή μέσα σε συνδεδεμένο κείμενο δεν μπορεί να κεντραριστεί επάνω ή κάτω: {frames}
@msg select type on a closed path επιλέξτε κείμενο σε κλειστή διαδρομή
Optimize for fast web view Βελτιστοποίηση για γρήγορη προβολή στον ιστό
Options for Selection… Επιλογές για την επιλογή…
or more spaces with a tab ή περισσότερα διαστήματα με στηλοθέτη
@@ -4067,6 +4071,8 @@ digits Malayalam Μαλαγιάλαμ
Type on a Path Κείμενο σε διαδρομή
Type on a Path Options Επιλογές κειμένου σε διαδρομή
Type on a Path Options… Επιλογές κειμένου σε διαδρομή…
Center on Top Κεντράρισμα επάνω
Center on Bottom Κεντράρισμα κάτω
Type on a Path Tool Κείμενο σε διαδρομή (Type on a Path Tool)
Type Orientation Προσανατολισμός κειμένου
Type stays text, and every stroke is a line with its lineweight and dashes. Το κείμενο διατηρείται ως κείμενο και κάθε περίγραμμα γίνεται γραμμή με το πάχος και τις παύλες του.
Can't render this file because it is too large.
+6
View File
@@ -2360,6 +2360,10 @@ font-technology Variable Variables
Arch Bóveda
@msg select point type or type on a path seleccione texto de punto o texto en trazado
@msg type on a path in a thread cannot use Dynamic Text: {frames} el texto en trazado de un texto enlazado no puede usar texto dinámico: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} el texto en un trazado abierto o compuesto no se puede centrar arriba o abajo: {open}
@msg type on a path with no length or no height cannot be centered: {flat} el texto en un trazado sin longitud o sin altura no se puede centrar: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} el texto en trazado de un texto enlazado no se puede centrar arriba o abajo: {frames}
@msg select type on a closed path seleccione texto en un trazado cerrado
Optimize for fast web view Optimizar para vista web rápida
Optimized Optimizado
Options Opciones
@@ -3723,6 +3727,8 @@ digits Malayalam Malayálam
Type on a Path Texto en trazado
Type on a Path Options Opciones de texto en trazado
Type on a Path Options… Opciones de texto en trazado…
Center on Top Centrar arriba
Center on Bottom Centrar abajo
Type on a Path Tool Herramienta Texto en trazado
Type Orientation Orientación del texto
Type stays text, and every stroke is a line with its lineweight and dashes. El texto sigue siendo texto, y cada trazo es una línea con su grosor y guiones.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -402,6 +402,8 @@
Stair Step Porras
Gravity Painovoima
Type on a Path Options… Teksti polulla -asetukset…
Center on Top Keskitä ylös
Center on Bottom Keskitä alas
Threaded Text Ketjutettu teksti
Create Luo
Release Selection Vapauta valinta
@@ -422,6 +424,10 @@
Arch Holvi
@msg select point type or type on a path valitse pisteteksti tai polkuteksti
@msg type on a path in a thread cannot use Dynamic Text: {frames} ketjutetun tekstin polkuteksti ei voi käyttää dynaamista tekstiä: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} avoimella polulla tai yhdistelmäpolulla olevaa tekstiä ei voi keskittää ylös tai alas: {open}
@msg type on a path with no length or no height cannot be centered: {flat} polkutekstiä ei voi keskittää polulle, jolla ei ole pituutta tai korkeutta: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} ketjutetun tekstin polkutekstiä ei voi keskittää ylös tai alas: {frames}
@msg select type on a closed path valitse suljetulla polulla oleva teksti
Fill with Placeholder Text Täytä paikkamerkkitekstillä
Insert Inline Symbol Lisää tekstinsisäinen symboli
Show Hidden Characters Näytä piilotetut merkit
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -2352,6 +2352,10 @@ font-technology Variable Variables
Arch Voûte
@msg select point type or type on a path sélectionnez du texte de point ou du texte curviligne
@msg type on a path in a thread cannot use Dynamic Text: {frames} le texte curviligne d’un texte lié ne peut pas utiliser le texte dynamique : {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} un texte curviligne sur un tracé ouvert ou un tracé transparent ne peut pas être centré en haut ou en bas : {open}
@msg type on a path with no length or no height cannot be centered: {flat} un texte curviligne sur un tracé sans longueur ou sans hauteur ne peut pas être centré : {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} le texte curviligne d’un texte lié ne peut pas être centré en haut ou en bas : {frames}
@msg select type on a closed path sélectionnez un texte curviligne sur un tracé fermé
Optimize for fast web view Optimiser pour un affichage web rapide
Optimized Optimisé
Options Options
@@ -3714,6 +3718,8 @@ digits Malayalam Malayalam
Type on a Path Texte curviligne
Type on a Path Options Options de texte curviligne
Type on a Path Options… Options de texte curviligne…
Center on Top Centrer en haut
Center on Bottom Centrer en bas
Type on a Path Tool Outil Texte curviligne
Type Orientation Orientation du texte
Type stays text, and every stroke is a line with its lineweight and dashes. Le texte reste du texte, et chaque contour devient une ligne avec son épaisseur et ses tirets.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -3189,6 +3189,10 @@ antiAlias Sharp शार्प
Arch मेहराब
@msg select point type or type on a path पॉइंट टाइप या पाथ पर टाइप चुनें
@msg type on a path in a thread cannot use Dynamic Text: {frames} थ्रेडेड टेक्स्ट में पाथ पर टाइप डायनैमिक टेक्स्ट का उपयोग नहीं कर सकता: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} खुले पाथ या कंपाउंड पाथ पर टाइप को ऊपर या नीचे केंद्रित नहीं किया जा सकता: {open}
@msg type on a path with no length or no height cannot be centered: {flat} बिना लंबाई या बिना ऊँचाई वाले पाथ पर टाइप केंद्रित नहीं किया जा सकता: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} थ्रेडेड टेक्स्ट में पाथ पर टाइप को ऊपर या नीचे केंद्रित नहीं किया जा सकता: {frames}
@msg select type on a closed path बंद पाथ पर टाइप चुनें
Optimize for fast web view तेजी से वेब दृश्य के लिए ऑप्टिमाइज़
Optimized ऑप्टिमाइज़्ड
Options ऑप्शन्स
@@ -5106,6 +5110,8 @@ antiAlias Sharp शार्प
Type on a Path पाथ पर टाइप
Type on a Path Options पाथ पर टाइप ऑप्शन्स
Type on a Path Options… पाथ पर टाइप ऑप्शन्स…
Center on Top ऊपर केंद्रित करें
Center on Bottom नीचे केंद्रित करें
Type on a Path Tool पाथ पर टाइप टूल
Type Optimized टाइप ऑप्टिमाइज़
Type Orientation प्रकार अभिविन्यास
Can't render this file because it is too large.
+6
View File
@@ -2854,6 +2854,10 @@ file-filter Templates Sablonok
Arch Boltív
@msg select point type or type on a path jelöljön ki pontszöveget vagy útvonalszöveget
@msg type on a path in a thread cannot use Dynamic Text: {frames} a láncolt szövegben lévő útvonalszöveg nem használhat dinamikus szöveget: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} nyitott vagy összetett útvonalon lévő útvonalszöveg nem igazítható középre felül vagy alul: {open}
@msg type on a path with no length or no height cannot be centered: {flat} a hossz vagy magasság nélküli útvonalon lévő útvonalszöveg nem igazítható középre: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} a láncolt szövegben lévő útvonalszöveg nem igazítható középre felül vagy alul: {frames}
@msg select type on a closed path jelöljön ki zárt útvonalon lévő útvonalszöveget
Optimize for fast web view Optimalizálás gyors webes megtekintéshez
Optimized Optimalizált
Options Beállítások
@@ -4440,6 +4444,8 @@ mark-style Japanese Japán
Type on a Path Útvonalszöveg
Type on a Path Options Útvonalszöveg beállításai
Type on a Path Options… Útvonalszöveg beállításai…
Center on Top Középre igazítás felül
Center on Bottom Középre igazítás alul
Type on a Path Tool Útvonalszöveg
Type Optimized Szövegre optimalizált
Type Orientation Szöveg iránya
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -366,6 +366,8 @@
Stair Step Undakan tangga
Gravity Gravitasi
Type on a Path Options… Opsi teks pada jalur…
Center on Top Pusatkan di atas
Center on Bottom Pusatkan di bawah
Threaded Text Teks beruntun
Create Buat
Release Selection Lepaskan seleksi
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
366
367
368
369
370
371
372
373
+6
View File
@@ -2398,6 +2398,10 @@ file-filter Templates Modelli
Arch Volta
@msg select point type or type on a path seleziona testo per punti o testo su tracciato
@msg type on a path in a thread cannot use Dynamic Text: {frames} il testo su tracciato in un testo concatenato non può usare il testo dinamico: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} il testo su un tracciato aperto o composto non può essere centrato in alto o in basso: {open}
@msg type on a path with no length or no height cannot be centered: {flat} il testo su un tracciato senza lunghezza o senza altezza non può essere centrato: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} il testo su tracciato in un testo concatenato non può essere centrato in alto o in basso: {frames}
@msg select type on a closed path seleziona del testo su un tracciato chiuso
Optimize for fast web view Ottimizza per visualizzazione web veloce
Optimized Ottimizzato
Options Opzioni
@@ -3769,6 +3773,8 @@ digits Malayalam Malayalam
Type on a Path Testo su tracciato
Type on a Path Options Opzioni testo su tracciato
Type on a Path Options… Opzioni testo su tracciato…
Center on Top Centra in alto
Center on Bottom Centra in basso
Type on a Path Tool Strumento Testo su tracciato
Type Orientation Orientamento testo
Type stays text, and every stroke is a line with its lineweight and dashes. Il testo resta testo e ogni traccia diventa una linea con il suo spessore e i suoi tratteggi.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -423,6 +423,8 @@ digits Malayalam マラヤーラム数字
Stair Step 階段
Gravity 引力
Type on a Path Options… パス上文字オプション…
Center on Top 上端に中央揃え
Center on Bottom 下端に中央揃え
Threaded Text スレッドテキストオプション
Create 作成
Release Selection 選択部分をリリース
@@ -453,6 +455,10 @@ digits Malayalam マラヤーラム数字
Arch アーチ
@msg select point type or type on a path ポイント文字またはパス上文字を選択してください
@msg type on a path in a thread cannot use Dynamic Text: {frames} スレッドテキスト内のパス上文字にはダイナミックテキストを使用できません: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} オープンパスや複合パス上のパス上文字は上端または下端に中央揃えできません: {open}
@msg type on a path with no length or no height cannot be centered: {flat} 長さや高さのないパスのパス上文字は中央揃えできません: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} スレッドテキスト内のパス上文字は上端または下端に中央揃えできません: {frames}
@msg select type on a closed path クローズパス上のパス上文字を選択してください
Fill with Placeholder Text サンプルテキストの割り付け
Insert Inline Symbol インラインシンボルを挿入
Show Hidden Characters 制御文字を表示
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -331,6 +331,8 @@
Stair Step საფეხურები
Gravity გრავიტაცია
Type on a Path Options… ტრაექტორიაზე ტექსტის პარამეტრები…
Center on Top ზემოთ ცენტრში მოთავსება
Center on Bottom ქვემოთ ცენტრში მოთავსება
Threaded Text დაკავშირებული ტექსტი
Create შექმნა
Release Selection მონიშნულის მოხსნა
@@ -351,6 +353,10 @@
Arch თაღი
@msg select point type or type on a path აირჩიეთ წერტილოვანი ტექსტი ან ტრაექტორიაზე ტექსტი
@msg type on a path in a thread cannot use Dynamic Text: {frames} დაკავშირებულ ტექსტში ტრაექტორიაზე ტექსტი ვერ გამოიყენებს დინამიკურ ტექსტს: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} ღია ან შედგენილ ტრაექტორიაზე ტექსტი ვერ მოთავსდება ზემოთ ან ქვემოთ ცენტრში: {open}
@msg type on a path with no length or no height cannot be centered: {flat} ტექსტი ვერ მოთავსდება ცენტრში ტრაექტორიაზე, რომელსაც არ აქვს სიგრძე ან სიმაღლე: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} დაკავშირებულ ტექსტში ტრაექტორიაზე ტექსტი ვერ მოთავსდება ზემოთ ან ქვემოთ ცენტრში: {frames}
@msg select type on a closed path მონიშნეთ დახურულ ტრაექტორიაზე ტექსტი
Fill with Placeholder Text შემავსებელი ტექსტით შევსება
Insert Inline Symbol ჩადგმული სიმბოლოს ჩასმა
Show Hidden Characters დამალული სიმბოლოების ჩვენება
Can't render this file because it is too large.
+6
View File
@@ -416,6 +416,8 @@ digits Malayalam 말라얄람어
Stair Step 계단
Gravity 중력
Type on a Path Options… 패스 위 문자 옵션…
Center on Top 위쪽에 가운데 맞춤
Center on Bottom 아래쪽에 가운데 맞춤
Threaded Text 연결된 문자
Create 만들기
Release Selection 선택 해제
@@ -446,6 +448,10 @@ digits Malayalam 말라얄람어
Arch 아치
@msg select point type or type on a path 포인트 문자 또는 패스 위 문자를 선택하십시오
@msg type on a path in a thread cannot use Dynamic Text: {frames} 연결된 문자의 패스 위 문자에는 동적 텍스트를 사용할 수 없습니다: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} 열린 패스나 컴파운드 패스 위 문자는 위쪽이나 아래쪽에 가운데 맞춤할 수 없습니다: {open}
@msg type on a path with no length or no height cannot be centered: {flat} 길이나 높이가 없는 패스 위 문자는 가운데 맞춤할 수 없습니다: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} 연결된 문자의 패스 위 문자는 위쪽이나 아래쪽에 가운데 맞춤할 수 없습니다: {frames}
@msg select type on a closed path 닫힌 패스 위 문자를 선택하세요
Fill with Placeholder Text 자리 표시 문자로 채우기
Insert Inline Symbol 인라인 심볼 삽입
Show Hidden Characters 숨겨진 문자 표시
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -380,6 +380,8 @@
Stair Step Trap
Gravity Zwaartekracht
Type on a Path Options… Opties voor tekst op pad…
Center on Top Boven centreren
Center on Bottom Onder centreren
Threaded Text Gekoppelde tekst
Create Maken
Release Selection Selectie vrijgeven
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -364,6 +364,8 @@
Stair Step Schodki
Gravity Grawitacja
Type on a Path Options… Opcje tekstu na ścieżce…
Center on Top Wyśrodkuj na górze
Center on Bottom Wyśrodkuj na dole
Threaded Text Tekst wątkowy
Create Utwórz
Release Selection Uwolnij zaznaczenie
@@ -384,6 +386,10 @@
Arch Sklepienie
@msg select point type or type on a path zaznacz tekst punktowy lub tekst na ścieżce
@msg type on a path in a thread cannot use Dynamic Text: {frames} tekst na ścieżce w tekście wątkowym nie może używać tekstu dynamicznego: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} nie można wyśrodkować na górze ani na dole tekstu na ścieżce otwartej lub złożonej: {open}
@msg type on a path with no length or no height cannot be centered: {flat} nie można wyśrodkować tekstu na ścieżce bez długości lub bez wysokości: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} nie można wyśrodkować na górze ani na dole tekstu na ścieżce w tekście wątkowym: {frames}
@msg select type on a closed path zaznacz tekst na zamkniętej ścieżce
Fill with Placeholder Text Wypełnij tekstem zastępczym
Insert Inline Symbol Wstaw symbol w tekście
Show Hidden Characters Pokaż ukryte znaki
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -1956,6 +1956,10 @@ antiAlias Sharp Nítido
Arch Abóbada
@msg select point type or type on a path selecione texto de ponto ou texto em um caminho
@msg type on a path in a thread cannot use Dynamic Text: {frames} o texto em um caminho de um texto encadeado não pode usar Texto Dinâmico: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} o texto em um caminho aberto ou composto não pode ser centralizado em cima ou embaixo: {open}
@msg type on a path with no length or no height cannot be centered: {flat} o texto em um caminho sem comprimento ou sem altura não pode ser centralizado: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} o texto em um caminho de um texto encadeado não pode ser centralizado em cima ou embaixo: {frames}
@msg select type on a closed path selecione texto em um caminho fechado
Optimize for fast web view Otimizar para visualização rápida na web
Optimized Otimizado
Options Opções
@@ -3068,6 +3072,8 @@ digits Malayalam Malaiala
Type becomes outlines, and strokes a CAD line can't draw become filled shapes. O texto vira contornos, e os traços que uma linha CAD não pode desenhar viram formas preenchidas.
Type on a Path Texto em um Caminho
Type on a Path Options… Opções de Texto em um Caminho…
Center on Top Centralizar em Cima
Center on Bottom Centralizar em Baixo
Type on a Path Tool Ferramenta Texto em um Caminho
Type stays text, and every stroke is a line with its lineweight and dashes. O texto permanece texto, e todo traço é uma linha com sua espessura e tracejados.
Type: Tipo:
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -2385,6 +2385,10 @@ file-filter Templates Шаблоны
Arch Арка
@msg select point type or type on a path выберите точечный текст или текст по контуру
@msg type on a path in a thread cannot use Dynamic Text: {frames} текст по контуру в связанном тексте не может использовать динамический текст: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} нельзя центрировать вверху или внизу текст по незамкнутому или составному контуру: {open}
@msg type on a path with no length or no height cannot be centered: {flat} нельзя центрировать текст по контуру без длины или без высоты: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} нельзя центрировать вверху или внизу текст по контуру в связанном тексте: {frames}
@msg select type on a closed path выделите текст по замкнутому контуру
Optimize for fast web view Оптимизировать для быстрого просмотра в web
Optimized Оптимизировано
Options Параметры
@@ -3755,6 +3759,8 @@ digits Malayalam Малаялам
Type on a Path Текст по контуру
Type on a Path Options Параметры текста по контуру
Type on a Path Options… Параметры текста по контуру…
Center on Top Центрировать вверху
Center on Bottom Центрировать внизу
Type on a Path Tool Инструмент «Текст по контуру»
Type Orientation Ориентация текста
Type stays text, and every stroke is a line with its lineweight and dashes. Текст остаётся текстом, и каждая обводка — линия с её толщиной и штрихами.
Can't render this file because it is too large.
+6
View File
@@ -2299,6 +2299,10 @@ font-technology Variable Variabla
Arch Valv
@msg select point type or type on a path markera punkttext eller text på bana
@msg type on a path in a thread cannot use Dynamic Text: {frames} text på bana i länkad text kan inte använda dynamisk text: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} text på en öppen eller sammansatt bana kan inte centreras överst eller nederst: {open}
@msg type on a path with no length or no height cannot be centered: {flat} text på en bana utan längd eller utan höjd kan inte centreras: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} text på bana i länkad text kan inte centreras överst eller nederst: {frames}
@msg select type on a closed path markera text på en sluten bana
Optimize for fast web view Optimera för snabb webbvisning
Optimized Optimerad
Options Alternativ
@@ -3644,6 +3648,8 @@ digits Malayalam Malayalam
Type on a Path Text på bana
Type on a Path Options Alternativ för text på bana
Type on a Path Options… Alternativ för text på bana…
Center on Top Centrera överst
Center on Bottom Centrera nederst
Type on a Path Tool Verktyg för text på bana
Type Orientation Textorientering
Type stays text, and every stroke is a line with its lineweight and dashes. Text förblir text, och varje kontur blir en linje med sin linjetjocklek och sina streck.
Can't render this file because it contains an unexpected character in line 5 and column 25.
+6
View File
@@ -2171,6 +2171,10 @@ shape Open Açık
Arch Kemer
@msg select point type or type on a path nokta metni veya yol üzerindeki yazıyı seçin
@msg type on a path in a thread cannot use Dynamic Text: {frames} zincirli metindeki yol üzerindeki yazı dinamik metin kullanamaz: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} açık veya bileşik bir yol üzerindeki yazı üstte veya altta ortalanamaz: {open}
@msg type on a path with no length or no height cannot be centered: {flat} uzunluğu ya da yüksekliği olmayan yol üzerindeki yazı ortalanamaz: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} zincirli metindeki yol üzerindeki yazı üstte veya altta ortalanamaz: {frames}
@msg select type on a closed path kapalı bir yol üzerindeki yazıyı seçin
Optimize for fast web view Hızlı web görünümü için optimize edin
Optimized Optimize edilmiş
Options Seçenekler
@@ -3532,6 +3536,8 @@ digits Malayalam Malayalam
Type on a Path Bir Yola Yazın
Type on a Path Options Yol Seçenekleri Üzerine Yazma
Type on a Path Options… Yol Seçenekleri Üzerine Yazma…
Center on Top Üstte ortala
Center on Bottom Altta ortala
Type on a Path Tool Yola Yazma Aracı
Type Orientation Tip Yönü
Type stays text, and every stroke is a line with its lineweight and dashes. Yazı metin olarak kalır ve her kontur, çizgi kalınlığı ve tireleriyle birlikte bir çizgidir.
Can't render this file because it contains an unexpected character in line 5 and column 25.
+6
View File
@@ -2390,6 +2390,10 @@ file-filter Templates Шаблони
Arch Арка
@msg select point type or type on a path виберіть точковий текст або текст за контуром
@msg type on a path in a thread cannot use Dynamic Text: {frames} текст за контуром у пов’язаному тексті не може використовувати динамічний текст: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} не можна центрувати вгорі чи внизу текст за незамкненим або складеним контуром: {open}
@msg type on a path with no length or no height cannot be centered: {flat} не можна центрувати текст за контуром без довжини або без висоти: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} не можна центрувати вгорі чи внизу текст за контуром у пов’язаному тексті: {frames}
@msg select type on a closed path виділіть текст за замкненим контуром
Optimize for fast web view Оптимізувати для швидкого перегляду web
Optimized Оптимізовано
Options Параметри
@@ -3762,6 +3766,8 @@ effect Points Точки
Type on a Path Текст за контуром
Type on a Path Options Параметри тексту за контуром
Type on a Path Options… Параметри тексту за контуром…
Center on Top Центрувати вгорі
Center on Bottom Центрувати внизу
Type on a Path Tool Інструмент «Текст за контуром»
Type Orientation Орієнтація тексту
Type stays text, and every stroke is a line with its lineweight and dashes. Текст залишається текстом, і кожне обведення — лінія з її товщиною та штрихами.
Can't render this file because it contains an unexpected character in line 182 and column 5.
+6
View File
@@ -2675,6 +2675,10 @@ digits Hindi Hindi
Arch Vòm
@msg select point type or type on a path chọn chữ điểm hoặc chữ trên đường dẫn
@msg type on a path in a thread cannot use Dynamic Text: {frames} chữ trên đường dẫn trong văn bản chuỗi không thể dùng văn bản động: {frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} không thể căn giữa ở trên hoặc ở dưới chữ trên đường dẫn mở hoặc đường dẫn phức: {open}
@msg type on a path with no length or no height cannot be centered: {flat} không thể căn giữa chữ trên đường dẫn không có độ dài hoặc không có chiều cao: {flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} không thể căn giữa ở trên hoặc ở dưới chữ trên đường dẫn trong văn bản chuỗi: {frames}
@msg select type on a closed path hãy chọn chữ trên một đường dẫn khép kín
Optimize for fast web view Tối ưu để xem web nhanh
Optimized Đã tối ưu
Options Tùy chọn
@@ -4145,6 +4149,8 @@ mark-style Japanese Kiểu Nhật
Type on a Path Chữ trên đường dẫn
Type on a Path Options Tùy chọn chữ trên đường dẫn
Type on a Path Options… Tùy chọn chữ trên đường dẫn…
Center on Top Căn giữa ở trên
Center on Bottom Căn giữa ở dưới
Type on a Path Tool Công cụ Chữ trên đường dẫn
Type Orientation Hướng chữ
Type stays text, and every stroke is a line with its lineweight and dashes. Chữ vẫn là văn bản, và mỗi nét là đường thẳng với độ dày và nét đứt.
Can't render this file because it contains an unexpected character in line 204 and column 5.
+6
View File
@@ -2258,6 +2258,10 @@ file-filter Templates 模板
Arch 拱形
@msg select point type or type on a path 请选择点文字或路径文字
@msg type on a path in a thread cannot use Dynamic Text: {frames} 串接文本中的路径文字不能使用动态文本:{frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} 开放路径或复合路径上的路径文字无法在顶部或底部居中:{open}
@msg type on a path with no length or no height cannot be centered: {flat} 路径没有长度或没有高度时,其路径文字无法居中:{flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} 串接文本中的路径文字无法在顶部或底部居中:{frames}
@msg select type on a closed path 请选择闭合路径上的路径文字
Optimize for fast web view 优化快速 Web 查看
Optimized 优化
Options 选项
@@ -3589,6 +3593,8 @@ digits Malayalam 马拉雅拉姆数字
Type on a Path 路径文字
Type on a Path Options 路径文字选项
Type on a Path Options… 路径文字选项…
Center on Top 在顶部居中
Center on Bottom 在底部居中
Type on a Path Tool 路径文字工具
Type Orientation 文字方向
Type stays text, and every stroke is a line with its lineweight and dashes. 文字维持为文字,每个描边都是带有线宽与虚线的线条。
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -3359,6 +3359,10 @@ file-filter Templates 範本
Arch 拱形
@msg select point type or type on a path 請選取點狀文字或路徑文字
@msg type on a path in a thread cannot use Dynamic Text: {frames} 串連文字中的路徑文字無法使用動態文字:{frames}
@msg type on an open or compound path cannot be centered on its top or bottom: {open} 開放路徑或複合路徑上的路徑文字無法在頂端或底端置中:{open}
@msg type on a path with no length or no height cannot be centered: {flat} 路徑沒有長度或沒有高度時,其路徑文字無法置中:{flat}
@msg type on a path in a thread cannot be centered on its top or bottom: {frames} 串連文字中的路徑文字無法在頂端或底端置中:{frames}
@msg select type on a closed path 請選取封閉路徑上的路徑文字
Optimize for fast web view 最佳化以便快速網頁檢視
Optimized 最佳化
Options 選項
@@ -4735,6 +4739,8 @@ digits Malayalam 馬來亞拉姆數字
Type on a Path 路徑文字
Type on a Path Options 路徑文字選項
Type on a Path Options… 路徑文字選項…
Center on Top 在頂端置中
Center on Bottom 在底端置中
Type on a Path Tool 路徑文字工具
Type Orientation 文字方向
Type stays text, and every stroke is a line with its lineweight and dashes. 文字維持為文字,每個筆畫都是帶有線寬與虛線的線條。
Can't render this file because it contains an unexpected character in line 6 and column 47.
+52
View File
@@ -358,6 +358,58 @@ fn dynamic_text_is_a_checked_type_menu_item() {
assert_eq!(checked(&app, id, &Value::Null), Some(true));
}
/// Type › Type on a Path › Center on Top and Center on Bottom run `type.pathCenter.top` and
/// `.bottom`, which the command palette also runs without params, and are enabled for type on a
/// closed path only.
#[test]
fn center_on_top_and_bottom_are_type_on_a_path_items() {
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default());
app.run("file.new", json!({})).unwrap();
let items: Vec<(&str, &str)> = menu_tree()
.into_iter()
.filter(|(menu, _)| *menu == "Type")
.flat_map(|(_, items)| items)
.filter_map(|item| match item {
Item::Sub("Type on a Path", children) => Some(children),
_ => None,
})
.flatten()
.filter_map(|item| match item {
Item::Cmd(label, id, Value::Null) if id.starts_with("type.pathCenter") => Some((label, id)),
_ => None,
})
.collect();
let ids = ["type.pathCenter.top", "type.pathCenter.bottom"];
assert_eq!(items, [("Center on Top", ids[0]), ("Center on Bottom", ids[1])]);
let palette = crate::palette::items();
for (label, id) in items {
assert!(palette.iter().any(|(l, i, _)| l == &format!("Type › Type on a Path › {label}") && i == id), "{label}");
}
let line = app.run("path.create", json!({"d": "M60 150 L340 150"})).unwrap()["id"].clone();
let open = app.run("text.createInPath", json!({"path": line, "mode": "onPath", "text": "Along", "at": [60, 150]})).unwrap()["id"].clone();
app.run("select.set", json!({"ids": [open]})).unwrap();
assert!(ids.iter().all(|id| !enabled(&app, id)), "type on an open path");
let square = app.run("path.create", json!({"d": "M60 200 L160 200 L160 300 L60 300 Z"})).unwrap()["id"].clone();
let closed = app.run("text.createInPath", json!({"path": square, "mode": "onPath", "text": "Round", "at": [60, 200]})).unwrap()["id"].clone();
app.run("select.set", json!({"ids": [closed]})).unwrap();
assert!(ids.iter().all(|id| enabled(&app, id)));
app.run("select.set", json!({"ids": [closed, open]})).unwrap();
assert!(ids.iter().all(|id| !enabled(&app, id)), "type on a closed path and on an open one");
// A palette pick runs the command with no params.
app.run("select.set", json!({"ids": [closed]})).unwrap();
crate::palette::run_pick(&mut app, ids[0]);
let doc = &app.session.active().unwrap().doc;
let start = match &doc.node(vectorcraft_doc::NodeId(closed.as_u64().unwrap())).unwrap().kind {
vectorcraft_doc::NodeKind::Text(t) => match t.kind {
vectorcraft_doc::TextKind::OnPath { start, .. } => start,
ref k => panic!("{k:?}"),
},
k => panic!("{k:?}"),
};
// Once round, the middle of the top edge an eighth of the way round.
assert!((start - 0.625).abs() < 1e-6, "{start}");
}
/// Type › Dynamic Text Presets › Circle, Arch and Bow run `type.dynamicTextPreset` with their
/// preset, enabled for point type and type on a path but not for area type alone.
#[test]
+7 -1
View File
@@ -396,7 +396,13 @@ pub fn menu_tree_named(english_names: bool) -> Vec<(&'static str, Vec<Item>)> {
PATH_EFFECTS
.iter()
.map(|&(label, effect)| cp(label, "type.pathOptions", json!({ "effect": effect })))
.chain([Sep, c("Type on a Path Options…", "type.pathOptions")])
.chain([
Sep,
c("Center on Top", "type.pathCenter.top"),
c("Center on Bottom", "type.pathCenter.bottom"),
Sep,
c("Type on a Path Options…", "type.pathOptions"),
])
.collect(),
),
c("Dynamic Text", "type.dynamicText"),
+28
View File
@@ -208,6 +208,34 @@ fn type_on_a_path_offers_dynamic_text() {
assert!(!has(&texts, "Dynamic Text"), "{texts:?}");
}
/// Type on a closed path offers Center on Top and Center on Bottom; type on an open path does not.
#[test]
fn type_on_a_closed_path_offers_center_on_top_and_bottom() {
let mut app = app();
app.run("tool.select", json!({"tool": "selection"})).unwrap();
let line = id(app.run("path.create", json!({"d": "M60 150 L340 150"})).unwrap());
let open = id(app.run("text.createInPath", json!({"path": line, "mode": "onPath", "text": "Along", "at": [60, 150]})).unwrap());
select(&mut app, &[open]);
let (_, texts) = bar(&mut app);
assert!(has(&texts, "Type on a Path Options") && !has(&texts, "Center on Top") && !has(&texts, "Center on Bottom"), "{texts:?}");
let circle = id(app.run("path.create", json!({"d": "M100 300 A100 100 0 1 1 300 300 A100 100 0 1 1 100 300 Z"})).unwrap());
let t = id(app.run("text.createInPath", json!({"path": circle, "mode": "onPath", "text": "Round", "at": [90, 300]})).unwrap());
select(&mut app, &[t]);
let start = |app: &VectorcraftApp| match kind(app, t) {
NodeKind::Text(t) => match t.kind {
TextKind::OnPath { start, .. } => start,
k => panic!("{k:?}"),
},
k => panic!("{k:?}"),
};
let (_, texts) = bar(&mut app);
assert!(has(&texts, "Center on Top") && has(&texts, "Center on Bottom"), "{texts:?}");
click(&mut app, "Center on Top");
assert!((start(&app) - 0.75).abs() < 1e-3, "{}", start(&app));
click(&mut app, "Center on Bottom");
assert!((start(&app) - 0.25).abs() < 1e-3, "{}", start(&app));
}
/// Area type offers Dynamic Text beside Convert To Point Type, which turns Block on and off; area
/// type in a thread does not.
#[test]
+20
View File
@@ -7972,6 +7972,26 @@ begins or ends, dragging the centre bracket slides the type along its path and,
{"name":"run_command","arguments":{"command":"type.pathOptions","params":{"start":0.25,"end":0.75,"flip":true}}}
```
`type.pathCenter.top {ids?|id?}` and `type.pathCenter.bottom {ids?|id?}` (Type › Type on a Path › Center on Top and
Center on Bottom, and the Contextual Task Bar for type on a closed path) slide the selected type on a closed path along
it so that the middle of the span between its brackets lands on the path's topmost or bottommost point. This is
VectorCraft's own behavior. The path is measured as the object is drawn, after its transform, so a rotated, scaled or
skewed circle centers on its drawn top. On a level stretch, such as a rectangle's top edge, the middle of the stretch
is used; with several separate topmost points, the middle of the shortest part of the path that holds them all is
used. The span keeps its length, and type that runs once round stays once round. The first paragraph's alignment
becomes Center unless it is Center or Justify Center, so the type is centered there. The type is not flipped: on the
bottom of a circle, whose path runs clockwise, it reads upside down outside the circle until `type.pathOptions {flip:
true}` turns it inside. Point type and area type in the selection are left as they are. Type on an open or compound
path, type in a thread, and type on a path with no length or no height are refused with an error that names them,
and nothing changes. The menu items are enabled, and the task bar shows them, only while every selected type on a
path is on one closed path and none of it is in a thread. Several objects move in one undo step. When every object is
already centered there, nothing changes and no undo step is recorded. Each replies `{ids, start, end}` with the first
object's brackets.
```json
{"name":"run_command","arguments":{"command":"type.pathCenter.top","params":{}}}
```
## Dynamic Text
`type.dynamicText {on?: bool, ids?}` (Type › Dynamic Text, and the Contextual Task Bar for type on a path) turns
+1
View File
@@ -1196,6 +1196,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
Object › Flatten Transparency, the Flattener Preview, and EPS, PDF and print output that flatten transparency leave art without transparency as it is when it overlaps only other art without transparency, unless it overlaps a member of a see-through object's group and lies above that member and below the group's topmost object. Such art joins the group, and the group takes the place of its topmost object, so the stacking order stays the same. In files without transparency with Clip Complex Regions on, art under the group within one image pixel of a member joins it too. The 1 pt antialiasing margin goes around a group's art once, however deeply its groups nest. On a grid of 3,000 rectangles with a 0.5 pt stroke, 60 of them with a drop shadow, the Flattener Preview counts 180 affected objects where it counted 3,000, and Flatten Transparency with the Medium preset makes 240 flat regions and 2 images where it made one image of the whole grid. These rules are VectorCraft's own (`tests::flatten::opaque_art_that_touches_only_opaque_art_stays`, `tests::flatten::opaque_art_the_group_would_move_under_joins_it`).
New Symbol and Redefine Symbol on a frame of threaded text take it out of the thread with the text it shows, which the symbol's art holds, and the other frames keep the text they hold. When the last frame goes into the symbol, the text it has no room for goes after the text of the last frame that stays. When every frame of a thread goes into the symbol, the copy of the last frame keeps all its text.
With Optical Margin Alignment on, or Roman Hanging Punctuation on in the first paragraph, the glyphs at the ends of left-to-right type on a path hang outside its brackets, measured along the path. The type hangs at the start bracket when its line is placed there, hangs at the end bracket when the line is placed there, and hangs at both when it is centered. Glyphs that hang past the end bracket do not make the type overflow, and a hang stops at an open path's end. SVG export writes such type as outlines.
Type › Type on a Path › Center on Top and Center on Bottom (`type.pathCenter.top`, `type.pathCenter.bottom`, also on the Contextual Task Bar for type on a closed path) slide type on a closed path so the middle of its span lands on the path's topmost or bottommost point as drawn, keep the span's length, and set the first paragraph to Center unless it is Center or Justify Center. This is VectorCraft's own behavior (`crates/geom/src/arc.rs`, `crates/engine/src/tests/pathtype.rs`).
Type on a Path Options › Align to Path (option names as in [Create type on a path](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/create-type-on-a-path.html)) moves each character by its own font, size and vertical scale: Ascender runs the top of the font's em box along the path (the ideographic em box that its `BASE` table, its typographic or `hhea` box or its ideographs give, else an em box centered on the cap height), Descender runs the bottom of the font's bounding box along it, and Center runs a line at half the height of the top of the font's H above the baseline along it. In vertical type, Ascender and Descender move the characters half an em, and Center leaves them on the path. These distances are VectorCraft's own values (`tests::on_path_align_runs_each_glyph_by_its_own_metrics`, `tests::on_path_align_finds_the_em_box_of_each_kind_of_face`, `tests::on_path_align_in_vertical_type_moves_half_an_em`).
Type › Dynamic Text (`type.dynamicText`, also on the Contextual Task Bar for type on a path) sizes type on a path to fill the span between its start and end brackets and fits it again after each edit to the text or a bracket ([Dynamic Text overview](https://helpx.adobe.com/photoshop/desktop/text-typography/work-with-dynamic-text/dynamic-text-overview.html), [start and end points](https://helpx.adobe.com/photoshop/desktop/text-typography/work-with-dynamic-text/reposition-start-and-end-points.html)). VectorCraft's own rules: reshaping or scaling the path fits the text again; one factor scales every size, leading and baseline shift; the first character's size moves in 0.1 pt steps within 0.1 to 1,296 pt; turning it off, threading the type or Release Type on a Path keeps the drawn sizes; SVG, EPS, legacy `.ai` and the editing data write the drawn sizes (`crates/text/src/tests/fit.rs`, `crates/engine/src/tests/dynamic_text.rs`).
Dynamic Text on area type is Block (Type › Dynamic Text, the Contextual Task Bar, Area Type Options › Block, the Properties panel's Dynamic Text checkbox with Word Gap and Line Gap; [Dynamic Text overview](https://helpx.adobe.com/photoshop/desktop/text-typography/work-with-dynamic-text/dynamic-text-overview.html)): each line as typed is set to the frame's width by its font size, again after every edit to the text or the frame. VectorCraft's own rules: lines are not broken again, the first character's size moves in 0.1 pt steps within 0.1 to 1,296 pt, Word Gap (0 to 1,000 %, default 100) is the Desired word spacing and Line Gap (-1,296 to 1,296 pt, default 0) is added to each size for the leading, the sizes are written into the characters, threaded frames cannot use it, and Convert To Point Type turns it off, keeping the sizes (`crates/engine/src/cmd/typecmd/block_tests.rs`).