M9.1: Character panel parity (V/H scale, baseline shift, stroke order, small caps); layer.setText keys for them

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas 2026-10-01 22:03:37 -04:00
parent 5a308a6964
commit fb6b3aa680
3 changed files with 47 additions and 11 deletions

View File

@ -110,6 +110,21 @@ fn text_doc_from(p: &Value, base: TextDoc) -> TextDoc {
if let Some(v) = b_p(p, "allCaps") {
d.all_caps = v;
}
if let Some(v) = b_p(p, "smallCaps") {
d.small_caps = v;
}
if let Some(v) = f_p(p, "hScale") {
d.h_scale = v.clamp(1.0, 1000.0);
}
if let Some(v) = f_p(p, "vScale") {
d.v_scale = v.clamp(1.0, 1000.0);
}
if let Some(v) = f_p(p, "baselineShift") {
d.baseline_shift = v;
}
if let Some(v) = b_p(p, "strokeOverFill") {
d.stroke_over_fill = v;
}
if let Some(v) = b_p(p, "fauxBold") {
d.faux_bold = v;
}
@ -940,7 +955,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Edit Text",
[],
None,
"{layer?, text?, size?, font?, style?, fill?, stroke?, applyFill?, applyStroke?, strokeWidth?, tracking?, leading?: px|\"auto\", justify?: left|center|right|justifyLeft|justifyCenter|justifyRight|justifyAll, allCaps?, fauxBold?, fauxItalic?}",
"{layer?, text?, size?, font?, style?, fill?, stroke?, applyFill?, applyStroke?, strokeWidth?, tracking?, leading?: px|\"auto\", justify?: left|center|right|justifyLeft|justifyCenter|justifyRight|justifyAll, allCaps?, smallCaps?, fauxBold?, fauxItalic?, hScale? %, vScale? %, baselineShift? px, strokeOverFill?}",
has_layers,
set_text
),

View File

@ -168,6 +168,9 @@ fn set_text_paragraph_fill_and_leading() {
assert_eq!((d.leading, d.apply_fill, d.apply_stroke), (Some(50.0), false, true));
s.execute("layer.setText", json!({"layer": t, "leading": "auto"})).unwrap();
assert_eq!(doc(&s).leading, None);
s.execute("layer.setText", json!({"layer": t, "hScale": 120, "vScale": 80, "baselineShift": 4, "smallCaps": true, "strokeOverFill": true})).unwrap();
let d = doc(&s);
assert_eq!((d.h_scale, d.v_scale, d.baseline_shift, d.small_caps, d.stroke_over_fill), (120.0, 80.0, 4.0, true, true));
}
#[test]

View File

@ -57,16 +57,21 @@ pub fn character(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
actions.push(json!({"style": styles[i]}));
}
y += 34.0;
// Numeric fields in a 2-column grid.
let fields: [(&str, &str, f64, (f64, f64), &str); 6] = [
// Numeric fields in AE's two-column grid.
let fields: [(&str, &str, f64, (f64, f64), &str); 8] = [
("size", "T", doc.size, (1.0, 2000.0), " px"),
("leading", "A", doc.leading.unwrap_or(doc.size * 1.2), (0.0, 5000.0), " px"),
("tracking", "VA", doc.tracking, (-1000.0, 10000.0), ""),
("strokeWidth", "≡", doc.stroke_width, (0.0, 500.0), " px"),
("strokeWidth", "W", doc.stroke_width, (0.0, 500.0), " px"),
("vScale", "↕T", doc.v_scale, (1.0, 1000.0), " %"),
("hScale", "↔T", doc.h_scale, (1.0, 1000.0), " %"),
("baselineShift", "A↑", doc.baseline_shift, (-1000.0, 1000.0), " px"),
("hScale", "↔", doc.h_scale, (1.0, 1000.0), "%"),
("", "", 0.0, (0.0, 0.0), ""),
];
for (i, (key, glyph, v, range, suffix)) in fields.into_iter().enumerate() {
if key.is_empty() {
continue;
}
let col = i % 2;
let row = i / 2;
let fx = x0 + col as f32 * (w / 2.0);
@ -77,13 +82,25 @@ pub fn character(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
if let Some(nv) = nv
&& enabled
{
let k = if key == "baselineShift" || key == "hScale" { "tracking" } else { key };
if k == key {
actions.push(json!({key: nv, "merge": format!("char-{key}")}));
}
actions.push(json!({key: nv, "merge": format!("char-{key}")}));
}
}
y += 3.0 * 28.0 + 8.0;
// Stroke Over Fill / Fill Over Stroke (bottom-right cell).
{
let fx = x0 + w / 2.0;
let fy = y + 3.0 * 28.0;
let dr = Rect::from_min_size(pos2(fx, fy), vec2(w / 2.0, 20.0));
let labels = vec!["Fill Over Stroke".to_string(), "Stroke Over Fill".to_string()];
let pop = egui::Id::new("char-stroke-order-pop");
if widgets::dropdown(ui, dr, &labels[doc.stroke_over_fill as usize], &t, egui::Id::new("char-stroke-order")).clicked() && enabled {
widgets::open_popup(ui, pop);
}
app.auto.add("character.strokeOverFill", dr, "Stroke order");
if let Some(i) = widgets::popup_menu(ui, pop, dr.left_bottom(), &labels, Some(doc.stroke_over_fill as usize)) {
actions.push(json!({"strokeOverFill": i == 1}));
}
}
y += 4.0 * 28.0 + 8.0;
// Fill / stroke swatches.
p.text(pos2(x0, y + 10.0), Align2::LEFT_CENTER, "Fill", Tokens::ui(11.5), t.text_dim);
let fs = Rect::from_min_size(pos2(x0 + 34.0, y), vec2(26.0, 20.0));
@ -107,7 +124,8 @@ pub fn character(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
y += 32.0;
// Style toggles.
let toggles = [("fauxBold", "T", doc.faux_bold), ("fauxItalic", "T", doc.faux_italic), ("allCaps", "TT", doc.all_caps)];
let toggles =
[("fauxBold", "T", doc.faux_bold), ("fauxItalic", "T", doc.faux_italic), ("allCaps", "TT", doc.all_caps), ("smallCaps", "Tт", doc.small_caps)];
for (i, (key, label, on)) in toggles.into_iter().enumerate() {
let r = Rect::from_min_size(pos2(x0 + i as f32 * 34.0, y), vec2(30.0, 24.0));
let resp = ui.interact(r, egui::Id::new(("char-t", key)), Sense::click());