#1063: a fill chosen with characters selected colours just them

Choosing a fill or stroke (Color panel, Swatches, the proxies) while the
Type tool had a few characters selected painted every run of the type
object. As in Illustrator, it now goes to the selected characters only:
the runs are split at the selection's ends and the paint is placed on
the runs in between, as one undo step; a stroke gets a 1 pt weight
where it had none. With only a caret, or with explicit ids or an item,
paint.setFill/setStroke behave as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-11 06:59:36 +07:00
co-authored by Claude Opus 5.5
parent 10f1c56a86
commit 75c587c8a0
4 changed files with 63 additions and 3 deletions
+29 -1
View File
@@ -16,7 +16,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Fill",
[],
None,
"{color?: \"#rrggbb\"|[r,g,b]|{c,m,y,k}|{gray}|{l,a,b} (CIE Lab), none?: true, swatch?: name (a global or spot colour stays linked, so swatch edits recolour it; a tint swatch links to its base at its tint; a gradient swatch is recorded as the gradient's swatch and fits each object, keeping its aspect; the built-in \"[Registration]\" prints on every plate), tint?: 0..100 (% of a global or spot `swatch`; default 100, or a tint swatch's own), gradient?: {kind?: linear|radial|freeform, stops?: [{offset 0..1, color, opacity? 0..1 (or 0..100), midpoint? 0.13..0.87, swatch?: global or spot colour (or tint) swatch the stop links to (its colour comes from the swatch), tint?: 0..100}] (at least 2; default white→black), angle?: deg, start?: [x,y], end?: [x,y] (the vector in document coordinates, both or neither; type objects keep it in text space), aspect?: % (radial; without start/end the gradient is placed on each object's bounds), focal?: [x,y] (radial, with start/end: the focal point, where the first stop sits), swatch?: linked gradient swatch name}, item?: appearance item index|null (omitted: the Appearance panel's active item if it is a fill, else the top fill), ids?, focus?: true (false keeps the active proxy), keepModel?: false (in a CMYK document, RGB colours and gradient stops are stored as CMYK unless true; Gray stays Gray)} sets the selection's fill and the default (new art fits a gradient to itself)",
"{color?: \"#rrggbb\"|[r,g,b]|{c,m,y,k}|{gray}|{l,a,b} (CIE Lab), none?: true, swatch?: name (a global or spot colour stays linked, so swatch edits recolour it; a tint swatch links to its base at its tint; a gradient swatch is recorded as the gradient's swatch and fits each object, keeping its aspect; the built-in \"[Registration]\" prints on every plate), tint?: 0..100 (% of a global or spot `swatch`; default 100, or a tint swatch's own), gradient?: {kind?: linear|radial|freeform, stops?: [{offset 0..1, color, opacity? 0..1 (or 0..100), midpoint? 0.13..0.87, swatch?: global or spot colour (or tint) swatch the stop links to (its colour comes from the swatch), tint?: 0..100}] (at least 2; default white→black), angle?: deg, start?: [x,y], end?: [x,y] (the vector in document coordinates, both or neither; type objects keep it in text space), aspect?: % (radial; without start/end the gradient is placed on each object's bounds), focal?: [x,y] (radial, with start/end: the focal point, where the first stop sits), swatch?: linked gradient swatch name}, item?: appearance item index|null (omitted: the Appearance panel's active item if it is a fill, else the top fill), ids?, focus?: true (false keeps the active proxy), keepModel?: false (in a CMYK document, RGB colours and gradient stops are stored as CMYK unless true; Gray stays Gray)} sets the selection's fill and the default (new art fits a gradient to itself); while the Type tool has characters selected (and no ids or item are given), the paint goes to those characters only",
has_doc,
|s, p| set_paint(s, p, true)
),
@@ -375,6 +375,34 @@ fn apply_to_proxy(s: &mut Session, p: &Value, paint: Paint) -> Result<Value> {
/// remember the paint.
fn apply_paint(s: &mut Session, p: &Value, paint: Paint, fill: bool) -> Result<Value> {
let cmd = if fill { "paint.setFill" } else { "paint.setStroke" };
// Characters selected with the Type tool take the paint, not the whole type object (#1063).
if p.get("ids").is_none()
&& p.get("item").is_none()
&& let Some((id, a, b)) = super::textedit::editing_range(s)?
&& a != b
{
s.edit(if fill { "Fill Color" } else { "Stroke Color" }, |d, _| {
let Some(NodeKind::Text(t)) = d.node_mut(id).map(|n| &mut n.kind) else { return Err(EngineError::NoNode(id)) };
let (xf, lb) = (t.xf, t.local_bounds());
vectorcraft_text::edit::style_range(&mut t.runs, a, b, |st| {
if fill {
st.fill = place_run_paint(&paint, p, xf, lb);
} else {
if !paint.is_none() {
run_stroke_weight(st);
}
st.stroke = place_run_paint(&paint, p, xf, vectorcraft_doc::appearance::stroke_paint_bounds(lb, st.stroke_width));
}
});
super::typecmd::refresh_bounds(t);
Ok(())
})?;
if bool_or(p, "focus", true) {
s.fill_active = fill;
}
s.remember_paint(&paint);
return ok();
}
// Resolve the requested targets before mutating defaults or the active proxy.
let item = item_target(s, p, cmd)?.of_kind(s, fill);
let selected = item.targets(s, p)?;
+1 -1
View File
@@ -542,7 +542,7 @@ enum Step {
}
/// The Type tool's selected text while it edits (its typing session ends first: one undo step).
fn editing_range(s: &mut Session) -> Result<Option<(NodeId, usize, usize)>> {
pub(crate) fn editing_range(s: &mut Session) -> Result<Option<(NodeId, usize, usize)>> {
let o = s.tool_options();
let Some(id) = o.get("editing").and_then(Value::as_u64).map(NodeId) else { return Ok(None) };
if super::edit::typing_in_progress(s) {