Color Effects: Apply Fill to Stroke and Apply Stroke to Fill become add-on commands

As asked in review: the reference app has no such commands, so they ship
in the Color Effects add-on. paint.fillToStroke / paint.strokeToFill are
now addon.color.fillToStroke / addon.color.strokeToFill in
cmd/addons/color.rs, listed in Object > Plug-ins > Color Effects instead
of the Color panel menu, with catalog rows in all 26 catalogs.

Two fixes from CodeRabbit's review:
- A graph's series marks and labels give the graph the copied paint in
  the same undo step (capture_series_paints / capture_label_styles, as
  paint.setFill does), so drawing the graph again keeps it. Targets go
  through graph::paint_targets, so a series group stands for its marks.
- A copy that would leave the effective paint as it is skips the object,
  so None copied onto a missing fill or stroke adds no item and records
  no undo step.
This commit is contained in:
Hamza
2026-11-05 21:46:43 +07:00
committed by bflatastic
parent 0db0c7ddae
commit 728352269d
32 changed files with 327 additions and 135 deletions
+120 -6
View File
@@ -3,9 +3,10 @@
//! Filter writes any colour adjustment into the colours themselves, Select by Color Kind selects the
//! objects that use spot colours, carry a live colour adjustment, or are images of a colour model,
//! Print Ink Fixes ([`print_fixes`]) prepares a CMYK document's inks for print, Find Nearest
//! Swatch ([`nearest`]) matches colours to a swatch library's, and Create Swatch Legend
//! ([`legend`]) draws labelled chips of the document's swatches. Its panel and its submenu, Object ›
//! Plug-ins › Color Effects, are the UI's.
//! Swatch ([`nearest`]) matches colours to a swatch library's, Create Swatch Legend
//! ([`legend`]) draws labelled chips of the document's swatches, and Apply Fill to Stroke and Apply
//! Stroke to Fill ([`copy_paint`]) copy each object's own paint across. Its panel and its submenu,
//! Object › Plug-ins › Color Effects, are the UI's.
pub mod legend;
mod nearest;
@@ -14,13 +15,14 @@ mod print_fixes;
mod tests;
use serde_json::{Value, json};
use vectorcraft_doc::{Document, Node, NodeId, NodeKind};
use vectorcraft_doc::{CharStyle, Document, Node, NodeId, NodeKind};
use vectorcraft_render::effects;
use super::super::colorcmds::recolor_links;
use super::super::paint::{restore_unpainted_stroke, set_run_stroke};
use super::super::select::walk_selectable;
use super::super::{CommandSpec, bad, checked_ids_or_selection, cmd, has_doc, has_selection, text_param};
use crate::{Result, Session};
use super::super::{CommandSpec, bad, checked_ids_or_selection, cmd, has_doc, has_selection, paint_targets, switch_or, text_param};
use crate::{EngineError, Result, Session};
/// The kinds Select by Color Kind selects (`addon.color.select`).
pub const SELECT_KINDS: [&str; 5] = ["spotColors", "colorEffects", "rgbImages", "cmykImages", "grayImages"];
@@ -72,6 +74,24 @@ pub fn specs() -> Vec<CommandSpec> {
has_selection,
nearest::apply
),
cmd!(
"addon.color.fillToStroke",
"Apply Fill to Stroke",
["Object", "Plug-ins", "Color Effects"],
None,
"{ids?|id?, skipNone?: bool} each target's top stroke takes its own top fill, as it is (a colour in its model, a global or spot colour linked at its tint, a gradient or pattern with its placement, None); a stroke given paint at width 0 gets 1 pt, as Swap Fill and Stroke does. Type: each run's stroke takes its own fill, and while the Type tool has characters selected (no ids), only those. A graph's series marks and labels keep the paint when the graph is drawn again. skipNone: leave alone a target whose fill is None. A target whose stroke already has that paint is left as it is, and when nothing changes no undo step is recorded. The default stroke takes the default fill the same way → {objects}",
has_doc,
|s, p| copy_paint(s, p, true)
),
cmd!(
"addon.color.strokeToFill",
"Apply Stroke to Fill",
["Object", "Plug-ins", "Color Effects"],
None,
"{ids?|id?, skipNone?: bool} each target's top fill takes its own top stroke's paint, as addon.color.fillToStroke does the other way; the stroke itself is unchanged. The default fill takes the default stroke → {objects}",
has_doc,
|s, p| copy_paint(s, p, false)
),
]
.into_iter()
.chain(legend::specs())
@@ -156,3 +176,97 @@ fn filter(s: &mut Session, p: &Value) -> Result<Value> {
let swatches = |name: &str| kinds.get(name).copied();
recolor_links(s, &given, label, &|c| map.apply(c), &|c, link, tint| map.apply_link(c, link, tint, &swatches))
}
/// Apply Fill to Stroke (`to_stroke`) or Apply Stroke to Fill: each target copies one of its own
/// paints onto the other, so a mixed selection keeps each object's colour (#5530). Aimed and
/// undone like Swap Fill and Stroke; characters selected with the Type tool are the only ones that
/// change, as for `paint.setFill`. A graph's series marks and labels give the graph the paint in
/// the same undo step, as `paint.setFill` does, so drawing the graph again keeps it. When nothing
/// changes, the document and its undo history stay as they are.
fn copy_paint(s: &mut Session, p: &Value, to_stroke: bool) -> Result<Value> {
let (cmd, label) =
if to_stroke { ("addon.color.fillToStroke", "Apply Fill to Stroke") } else { ("addon.color.strokeToFill", "Apply Stroke to Fill") };
let skip_none = switch_or(p, "skipNone", false, cmd)?;
// One text style: its stroke takes its fill, or the other way.
let copy_run = |st: &mut CharStyle| {
let from = if to_stroke { st.fill.clone() } else { st.stroke.clone() };
if (skip_none && from.is_none()) || from == if to_stroke { st.stroke.clone() } else { st.fill.clone() } {
return;
}
if to_stroke {
set_run_stroke(st, from);
} else {
st.fill = from;
}
};
if p.get("ids").is_none()
&& p.get("id").is_none()
&& let Some((id, a, b)) = super::super::textedit::editing_range(s)?
&& a != b
{
// After Select All in threaded text, every frame's text takes it.
let story = super::super::textedit::editing_story(s);
let ranges: Vec<(NodeId, usize, usize)> =
if story.is_empty() { vec![(id, a, b)] } else { story.iter().map(|f| (*f, 0, usize::MAX)).collect() };
let ids: Vec<NodeId> = ranges.iter().map(|(f, _, _)| *f).collect();
let before = s.doc()?.doc.clone();
let n = super::super::overprint::edit_counted(s, label, |d| {
for &(id, a, b) in &ranges {
let Some(NodeKind::Text(t)) = d.node_mut(id).map(|n| &mut n.kind) else { return Err(EngineError::NoNode(id)) };
vectorcraft_text::edit::style_range(&mut t.runs, a, b, copy_run);
super::super::typecmd::refresh_bounds(t);
}
Ok(super::super::appearance::count_changed(d, &before, &ids))
})?;
return Ok(json!({"objects": n}));
}
// A graph's series group stands for its marks, as for `paint.setFill`.
let ids = super::super::graph::paint_targets(&s.doc()?.doc, &paint_targets(s, p)?);
let mut objects = 0;
if !ids.is_empty() {
let before = s.doc()?.doc.clone();
super::super::overprint::edit_counted(s, label, |d| {
for id in &ids {
let Some(node) = d.node_mut(*id) else { continue };
if let NodeKind::Text(t) = &mut node.kind {
for r in &mut t.runs {
copy_run(&mut r.style);
}
super::super::typecmd::refresh_bounds(t);
continue;
}
let ap = &mut node.appearance;
let (from, to) = if to_stroke { (ap.fill_paint(), ap.stroke_paint()) } else { (ap.stroke_paint(), ap.fill_paint()) };
// A missing item reads as None: copying None onto it would add an item that
// changes nothing.
if (skip_none && from.is_none()) || from == to {
continue;
}
if to_stroke {
restore_unpainted_stroke(ap, None, &from);
ap.set_stroke(from);
} else {
ap.set_fill(from);
}
}
objects = super::super::appearance::count_changed(d, &before, &ids);
// The graph keeps the paint its series marks and labels now have.
let series = super::super::graph::capture_series_paints(d, &ids, !to_stroke);
let labels = super::super::graph::capture_label_styles(d, &ids, &copy_run);
Ok(objects + usize::from(series || labels))
})?;
}
// The defaults for new art change the same way, as Swap Fill and Stroke changes them.
let from = if to_stroke { s.paint.fill.clone() } else { s.paint.stroke.clone() };
if !(skip_none && from.is_none()) {
if to_stroke {
if s.paint.stroke.is_none() && !from.is_none() && s.paint.stroke_width == 0.0 {
s.paint.stroke_width = 1.0;
}
s.paint.stroke = from;
} else {
s.paint.fill = from;
}
}
Ok(json!({"objects": objects}))
}
+1 -108
View File
@@ -41,24 +41,6 @@ pub fn specs() -> Vec<CommandSpec> {
has_doc,
swap
),
cmd!(
"paint.fillToStroke",
"Apply Fill to Stroke",
[],
None,
"{ids?|id?, skipNone?: bool} each target's top stroke takes its own top fill, as it is (a colour in its model, a global or spot colour linked at its tint, a gradient or pattern with its placement, None); a stroke given paint at width 0 gets 1 pt, as Swap Fill and Stroke does. Type: each run's stroke takes its own fill, and while the Type tool has characters selected (no ids), only those. skipNone: leave alone a target whose fill is None. The default stroke takes the default fill the same way → {objects}",
has_doc,
|s, p| copy_paint(s, p, true)
),
cmd!(
"paint.strokeToFill",
"Apply Stroke to Fill",
[],
None,
"{ids?|id?, skipNone?: bool} each target's top fill takes its own top stroke's paint, as paint.fillToStroke does the other way; the stroke itself is unchanged. The default fill takes the default stroke → {objects}",
has_doc,
|s, p| copy_paint(s, p, false)
),
cmd!(
"paint.default",
"Default Fill and Stroke",
@@ -750,7 +732,7 @@ pub(crate) fn picked_mesh_points_of(s: &Session, ids: &[NodeId]) -> Option<Vec<(
}
/// A type run's stroke; a painted stroke on a run without one gets 1 pt.
fn set_run_stroke(style: &mut CharStyle, paint: Paint) {
pub(super) fn set_run_stroke(style: &mut CharStyle, paint: Paint) {
if !paint.is_none() {
run_stroke_weight(style);
}
@@ -811,95 +793,6 @@ fn swap(s: &mut Session, p: &Value) -> Result<Value> {
ok()
}
/// Apply Fill to Stroke (`to_stroke`) or Apply Stroke to Fill: each target copies one of its own
/// paints onto the other, so a mixed selection keeps each object's colour (#5530). Aimed and
/// undone like Swap Fill and Stroke; characters selected with the Type tool are the only ones that
/// change, as for `paint.setFill`. When nothing changes, the document and its undo history stay
/// as they are.
fn copy_paint(s: &mut Session, p: &Value, to_stroke: bool) -> Result<Value> {
let (cmd, label) = if to_stroke { ("paint.fillToStroke", "Apply Fill to Stroke") } else { ("paint.strokeToFill", "Apply Stroke to Fill") };
let skip_none = switch_or(p, "skipNone", false, cmd)?;
// One text style: its stroke takes its fill, or the other way.
let copy_run = |st: &mut CharStyle| {
let from = if to_stroke { st.fill.clone() } else { st.stroke.clone() };
if skip_none && from.is_none() {
return;
}
if to_stroke {
set_run_stroke(st, from);
} else {
st.fill = from;
}
};
if p.get("ids").is_none()
&& p.get("id").is_none()
&& let Some((id, a, b)) = super::textedit::editing_range(s)?
&& a != b
{
// After Select All in threaded text, every frame's text takes it.
let story = super::textedit::editing_story(s);
let ranges: Vec<(NodeId, usize, usize)> =
if story.is_empty() { vec![(id, a, b)] } else { story.iter().map(|f| (*f, 0, usize::MAX)).collect() };
let ids: Vec<NodeId> = ranges.iter().map(|(f, _, _)| *f).collect();
let before = s.doc()?.doc.clone();
let n = super::overprint::edit_counted(s, label, |d| {
for &(id, a, b) in &ranges {
let Some(NodeKind::Text(t)) = d.node_mut(id).map(|n| &mut n.kind) else { return Err(EngineError::NoNode(id)) };
vectorcraft_text::edit::style_range(&mut t.runs, a, b, copy_run);
super::typecmd::refresh_bounds(t);
}
Ok(super::appearance::count_changed(d, &before, &ids))
})?;
return Ok(json!({"objects": n}));
}
let ids = paint_targets(s, p)?;
let mut n = 0;
if !ids.is_empty() {
let before = s.doc()?.doc.clone();
n = super::overprint::edit_counted(s, label, |d| {
for id in &ids {
let Some(node) = d.node_mut(*id) else { continue };
if let NodeKind::Text(t) = &mut node.kind {
for r in &mut t.runs {
copy_run(&mut r.style);
}
super::typecmd::refresh_bounds(t);
continue;
}
let ap = &mut node.appearance;
if to_stroke {
let f = ap.fill_paint();
if skip_none && f.is_none() {
continue;
}
restore_unpainted_stroke(ap, None, &f);
ap.set_stroke(f);
} else {
let st = ap.stroke_paint();
if skip_none && st.is_none() {
continue;
}
ap.set_fill(st);
}
}
Ok(super::appearance::count_changed(d, &before, &ids))
})?;
}
// The defaults for new art change the same way, as Swap Fill and Stroke changes them.
let from = if to_stroke { s.paint.fill.clone() } else { s.paint.stroke.clone() };
if !(skip_none && from.is_none()) {
if to_stroke {
if s.paint.stroke.is_none() && !from.is_none() && s.paint.stroke_width == 0.0 {
s.paint.stroke_width = 1.0;
}
s.paint.stroke = from;
} else {
s.paint.fill = from;
}
}
Ok(json!({"objects": n}))
}
fn default_paint(s: &mut Session, p: &Value) -> Result<Value> {
let ids = paint_targets(s, p)?;
if !ids.is_empty() {
+123 -14
View File
@@ -434,7 +434,7 @@ fn each_object_copies_its_own_paint_onto_the_other() {
s.execute("object.setStrokeWidth", &json!({"ids": [id.0], "width": 0})).ok();
}
s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap();
let r = s.execute("paint.fillToStroke", &json!({})).unwrap();
let r = s.execute("addon.color.fillToStroke", &json!({})).unwrap();
assert_eq!(r["objects"], 2);
for (id, hex) in [(a, "#ff0000"), (b, "#0080ff")] {
let (fill, stroke, width) = paints(&s, id);
@@ -443,7 +443,7 @@ fn each_object_copies_its_own_paint_onto_the_other() {
assert!(width > 0.0, "and shows: {width}");
}
s.execute("paint.setStroke", &json!({"ids": [a.0], "color": "#00ff00"})).unwrap();
s.execute("paint.strokeToFill", &json!({"ids": [a.0]})).unwrap();
s.execute("addon.color.strokeToFill", &json!({"ids": [a.0]})).unwrap();
let (fill, stroke, _) = paints(&s, a);
assert_eq!((fill.color().unwrap().to_hex(), stroke.color().unwrap().to_hex()), ("#00ff00".into(), "#00ff00".into()));
assert_eq!(paints(&s, b).0.color().unwrap().to_hex(), "#0080ff", "an object not aimed at is left alone");
@@ -457,7 +457,7 @@ fn a_linked_swatch_and_a_gradient_copy_as_they_are() {
let a = rect(&mut s);
s.execute("swatch.new", &json!({"name": "Ink", "color": "#c82828", "global": true})).unwrap();
s.execute("paint.setFill", &json!({"ids": [a.0], "swatch": "Ink", "tint": 40})).unwrap();
s.execute("paint.fillToStroke", &json!({"ids": [a.0]})).unwrap();
s.execute("addon.color.fillToStroke", &json!({"ids": [a.0]})).unwrap();
let (fill, stroke, _) = paints(&s, a);
assert!(matches!(&stroke, Paint::Solid { swatch: Some(name), .. } if name == "Ink"), "{stroke:?}");
assert_eq!(stroke, fill);
@@ -465,7 +465,7 @@ fn a_linked_swatch_and_a_gradient_copy_as_they_are() {
let mut q = g.clone();
q["ids"] = json!([a.0]);
s.execute("paint.setFill", &q).unwrap();
s.execute("paint.fillToStroke", &json!({"ids": [a.0]})).unwrap();
s.execute("addon.color.fillToStroke", &json!({"ids": [a.0]})).unwrap();
let (fill, stroke, _) = paints(&s, a);
assert!(matches!(stroke, Paint::Gradient { .. }), "{stroke:?}");
assert_eq!(stroke, fill, "the same stops and placement");
@@ -479,11 +479,11 @@ fn none_copies_unless_skipped() {
s.execute("paint.setFill", &json!({"ids": [a.0], "none": true})).unwrap();
s.execute("paint.setStroke", &json!({"ids": [a.0, b.0], "color": "#123456"})).unwrap();
s.execute("paint.setFill", &json!({"ids": [b.0], "color": "#ff8000"})).unwrap();
let r = s.execute("paint.fillToStroke", &json!({"ids": [a.0, b.0], "skipNone": true})).unwrap();
let r = s.execute("addon.color.fillToStroke", &json!({"ids": [a.0, b.0], "skipNone": true})).unwrap();
assert_eq!(r["objects"], 1, "only the object with a fill changed");
assert_eq!(paints(&s, a).1.color().unwrap().to_hex(), "#123456", "skipped: its stroke stays");
assert_eq!(paints(&s, b).1.color().unwrap().to_hex(), "#ff8000");
s.execute("paint.fillToStroke", &json!({"ids": [a.0]})).unwrap();
s.execute("addon.color.fillToStroke", &json!({"ids": [a.0]})).unwrap();
assert!(paints(&s, a).1.is_none(), "without skipNone, no fill gives no stroke");
}
@@ -497,7 +497,7 @@ fn a_groups_members_each_copy_their_own() {
s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap();
let g = NodeId(s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap());
s.execute("select.set", &json!({"ids": [g.0]})).unwrap();
s.execute("paint.fillToStroke", &json!({})).unwrap();
s.execute("addon.color.fillToStroke", &json!({})).unwrap();
assert_eq!(paints(&s, a).1.color().unwrap().to_hex(), "#ff0000");
assert_eq!(paints(&s, b).1.color().unwrap().to_hex(), "#00ff00");
}
@@ -509,7 +509,7 @@ fn each_text_run_copies_its_own_fill() {
let id = NodeId(s.execute("text.create", &json!({"x": 10, "y": 50, "text": "Hi"})).unwrap()["id"].as_u64().unwrap());
s.execute("paint.setFill", &json!({"ids": [id.0], "color": "#aa0000"})).unwrap();
s.execute("paint.setStroke", &json!({"ids": [id.0], "none": true})).unwrap();
s.execute("paint.fillToStroke", &json!({"ids": [id.0]})).unwrap();
s.execute("addon.color.fillToStroke", &json!({"ids": [id.0]})).unwrap();
let NodeKind::Text(t) = &node(&s, id).kind else { panic!("not text") };
for r in &t.runs {
assert_eq!(r.style.stroke, r.style.fill, "{:?}", r.text);
@@ -527,18 +527,18 @@ fn one_undo_step_none_when_nothing_changes_and_bad_params_change_nothing() {
s.execute("paint.setStroke", &json!({"ids": [id.0], "color": "#ff0000"})).unwrap();
select_with_a_step_to_redo(&mut s, &[id]);
let before = history(&s);
s.execute("paint.fillToStroke", &json!({})).unwrap();
s.execute("paint.strokeToFill", &json!({})).unwrap();
s.execute("addon.color.fillToStroke", &json!({})).unwrap();
s.execute("addon.color.strokeToFill", &json!({})).unwrap();
assert_eq!(history(&s), before, "the paints were already the same");
s.execute("paint.setStroke", &json!({"ids": [id.0], "color": "#0000ff"})).unwrap();
let before = history(&s);
s.execute("paint.fillToStroke", &json!({})).unwrap();
s.execute("addon.color.fillToStroke", &json!({})).unwrap();
assert_eq!(history(&s), (before.0 + 1, 0, true), "one step");
assert_eq!(s.doc().unwrap().history.undo.last().map(|e| e.label.as_str()), Some("Apply Fill to Stroke"));
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(paints(&s, id).1.color().unwrap().to_hex(), "#0000ff");
let defaults = (s.paint.fill.clone(), s.paint.stroke.clone());
for (cmd, p) in [("paint.fillToStroke", json!({"ids": "not an array"})), ("paint.strokeToFill", json!({"skipNone": "yes"}))] {
for (cmd, p) in [("addon.color.fillToStroke", json!({"ids": "not an array"})), ("addon.color.strokeToFill", json!({"skipNone": "yes"}))] {
assert!(s.execute(cmd, &p).is_err(), "{cmd} {p}");
}
assert_eq!((s.paint.fill.clone(), s.paint.stroke.clone()), defaults, "the defaults are as they were");
@@ -552,11 +552,120 @@ fn with_nothing_selected_the_defaults_take_it() {
s.execute("select.set", &json!({"ids": []})).unwrap();
s.execute("paint.setFill", &json!({"color": "#336699"})).unwrap();
s.execute("paint.setStroke", &json!({"none": true})).unwrap();
let r = s.execute("paint.fillToStroke", &json!({})).unwrap();
let r = s.execute("addon.color.fillToStroke", &json!({})).unwrap();
assert_eq!(r["objects"], 0);
assert_eq!(s.paint.stroke, s.paint.fill);
assert!(s.paint.stroke_width > 0.0);
s.execute("paint.setStroke", &json!({"color": "#ff00ff"})).unwrap();
s.execute("paint.strokeToFill", &json!({})).unwrap();
s.execute("addon.color.strokeToFill", &json!({})).unwrap();
assert_eq!(s.paint.fill.color().unwrap().to_hex(), "#ff00ff");
}
/// Copying None onto an item the object doesn't have adds nothing: with no fill and no stroke
/// item at all, neither command records an undo step or touches the object.
#[test]
fn copying_none_onto_a_missing_item_changes_nothing() {
let mut s = session();
let id = rect(&mut s);
s.edit("No paint items", |d, _| {
d.node_mut(id).unwrap().appearance.items.clear();
Ok(())
})
.unwrap();
assert_eq!((paints(&s, id).0, paints(&s, id).1), (Paint::None, Paint::None));
select_with_a_step_to_redo(&mut s, &[id]);
let (before, art) = (history(&s), node(&s, id));
for cmd in ["addon.color.fillToStroke", "addon.color.strokeToFill"] {
assert_eq!(s.execute(cmd, &json!({})).unwrap()["objects"], 0, "{cmd}");
assert_eq!(history(&s), before, "{cmd}: no undo step");
assert_eq!(node(&s, id), art, "{cmd}: no item added");
}
}
/// A graph's series and its labels keep the copied paint when the graph is drawn again: the
/// graph takes it in the same undo step, as it does from paint.setFill.
#[test]
fn a_graph_series_and_its_labels_keep_the_copied_paint_when_drawn_again() {
let mut s = session();
let csv = ",2024,2025\nQ1,3,2\nQ2,5,4";
let g = NodeId(
s.execute("graph.create", &json!({"type": "column", "x": 100, "y": 100, "width": 300, "height": 200, "csv": csv})).unwrap()["id"]
.as_u64()
.unwrap(),
);
let series = |s: &Session| node(s, g).children().unwrap().iter().find(|c| c.extras().series_index == Some(0)).unwrap().clone();
let label = |s: &Session| {
let axes = node(s, g).children().unwrap().iter().find(|c| c.name.as_deref() == Some("Axes")).unwrap().clone();
axes.children().unwrap().iter().find(|c| matches!(&c.kind, NodeKind::Text(t) if t.plain_text() == "4")).unwrap().clone()
};
let sid = series(&s).id;
s.execute("paint.setFill", &json!({"ids": [sid.0], "color": "#12ab34"})).unwrap();
s.execute("paint.setStroke", &json!({"ids": [sid.0], "none": true})).unwrap();
let lid = label(&s).id;
s.execute("paint.setFill", &json!({"ids": [lid.0], "color": "#e8573f"})).unwrap();
let steps = s.doc().unwrap().history.undo.len();
s.execute("addon.color.fillToStroke", &json!({"ids": [sid.0, lid.0]})).unwrap();
assert_eq!(s.doc().unwrap().history.undo.len(), steps + 1, "one step");
let kept = s.doc().unwrap().doc.node(g).unwrap().extras().graph.as_deref().unwrap().clone();
assert_eq!(kept.series_paints[0].stroke.as_ref().and_then(Paint::color).map(|c| c.to_hex()), Some("#12ab34".into()));
assert_eq!(kept.label_styles.values.as_ref().and_then(|st| st.stroke.color()).map(|c| c.to_hex()), Some("#e8573f".into()));
s.execute("graph.setData", &json!({"id": g.0, "rows": [[1, 2], [3, 4]]})).unwrap();
let marks = series(&s).children().unwrap().to_vec();
let bars: Vec<_> = marks.iter().filter(|m| matches!(m.kind, NodeKind::Path { .. })).collect();
assert!(!bars.is_empty());
for m in bars {
assert_eq!(m.appearance.stroke_paint().color().map(|c| c.to_hex()), Some("#12ab34".into()), "a redrawn mark keeps the stroke");
}
let redrawn = node(&s, g).children().unwrap().iter().find(|c| c.name.as_deref() == Some("Axes")).unwrap().clone();
let text = redrawn.children().unwrap().iter().find_map(|c| if let NodeKind::Text(t) = &c.kind { Some(t.clone()) } else { None }).unwrap();
assert_eq!(text.runs[0].style.stroke.color().map(|c| c.to_hex()), Some("#e8573f".into()), "a redrawn value label keeps the stroke");
s.execute("edit.undo", &json!({})).unwrap();
s.execute("edit.undo", &json!({})).unwrap();
let back = s.doc().unwrap().doc.node(g).unwrap().extras().graph.as_deref().unwrap().clone();
assert!(
back.series_paints[0].stroke.as_ref().is_none_or(|p| p.is_none()),
"undo takes the graph's stroke back: {:?}",
back.series_paints[0].stroke
);
}
/// A series drawn with a marker design: its own marks copy their paint, but the design's art
/// keeps the design's paint, as paint.setFill leaves it (painting it would last only until the
/// graph is drawn again).
#[test]
fn a_marker_designs_art_in_a_series_keeps_its_paint() {
let mut s = session();
let r = s.execute("shape.rectangle", &json!({"x": 600, "y": 600, "width": 10, "height": 10})).unwrap()["id"].as_u64().unwrap();
s.execute("paint.setFill", &json!({"ids": [r], "color": "#0000ff"})).unwrap();
s.execute("paint.setStroke", &json!({"ids": [r], "none": true})).unwrap();
s.execute("select.set", &json!({"ids": [r]})).unwrap();
s.execute("graph.design", &json!({"new": "Square"})).unwrap();
let csv = ",2024,2025\nQ1,3,2\nQ2,5,4";
let g = NodeId(
s.execute("graph.create", &json!({"type": "line", "x": 100, "y": 100, "width": 300, "height": 200, "csv": csv})).unwrap()["id"]
.as_u64()
.unwrap(),
);
s.execute("select.set", &json!({"ids": [g.0]})).unwrap();
s.execute("graph.marker", &json!({"design": "Square"})).unwrap();
let series = node(&s, g).children().unwrap().iter().find(|c| c.extras().series_index == Some(0)).unwrap().clone();
// The design's paths sit in groups inside the series; its own marks are its direct children.
let design_strokes = |s: &Session| {
let grp = node(s, series.id);
let mut out = vec![];
for c in grp.children().unwrap() {
if !matches!(c.kind, NodeKind::Path { .. }) {
c.walk(&mut |n| {
if let NodeKind::Path { .. } = n.kind {
out.push(n.appearance.stroke_paint());
}
});
}
}
out
};
let before = design_strokes(&s);
assert!(!before.is_empty(), "the series draws the design");
s.execute("addon.color.fillToStroke", &json!({"ids": [series.id.0]})).unwrap();
assert_eq!(design_strokes(&s), before, "the design's art keeps its paint");
}
+1 -1
View File
@@ -3019,7 +3019,7 @@ fn apply_fill_to_stroke_with_characters_selected_changes_only_them() {
s.set_tool_option("select", &json!({"start": 0, "end": 6}));
s.execute("paint.setFill", &json!({"color": "#00a000"})).unwrap();
let steps = undo_labels(&s).len();
s.execute("paint.fillToStroke", &json!({})).unwrap();
s.execute("addon.color.fillToStroke", &json!({})).unwrap();
let t = obj(&s, id);
assert_eq!(t.runs[0].text, "Sample");
assert_eq!(t.runs[0].style.stroke, t.runs[0].style.fill, "the selected characters take their fill as a stroke");
+4
View File
@@ -104,6 +104,10 @@ pub fn menu() -> Vec<Item> {
Item::Cmd("Find Nearest Swatch…", nearest::COMMAND, Value::Null),
Item::Cmd("Create Swatch Legend…", legend::COMMAND, Value::Null),
Item::Sep,
// Each selected object's stroke takes its own fill, or the other way (#5530).
Item::Cmd("Apply Fill to Stroke", "addon.color.fillToStroke", json!({})),
Item::Cmd("Apply Stroke to Fill", "addon.color.strokeToFill", json!({})),
Item::Sep,
Item::Sub(
"Select by Color Kind",
[
+3
View File
@@ -5952,6 +5952,9 @@ anchor Points: Точки:
Find Nearest Swatch… Намиране на най-близкия образец…
Find Nearest Swatch Намиране на най-близкия образец
Apply Nearest Swatch Прилагане на най-близкия образец
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Прилагане на запълването към щриха
Apply Stroke to Fill Прилагане на щриха към запълването
Select objects to find the swatches nearest their colors. Изберете обекти, за да намерите най-близките до цветовете им образци.
@msg select objects or give `color` изберете обекти или задайте `color`
@msg `color` is not a colour `color` не е цвят
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6078,6 +6078,9 @@ shape Open Otvoren
Find Nearest Swatch… Pronađi najbliži uzorak boje…
Find Nearest Swatch Pronađi najbliži uzorak boje
Apply Nearest Swatch Primijeni najbliži uzorak boje
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Primijeni ispunu na potez
Apply Stroke to Fill Primijeni potez na ispunu
Select objects to find the swatches nearest their colors. Odaberite objekte da pronađete uzorke boja najbliže njihovim bojama.
@msg select objects or give `color` odaberite objekte ili navedite `color`
@msg `color` is not a colour `color` nije boja
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6087,6 +6087,9 @@ anchor Points: Punts:
Find Nearest Swatch… Cerca la mostra més propera…
Find Nearest Swatch Cerca la mostra més propera
Apply Nearest Swatch Aplica la mostra més propera
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Aplica el farciment al traç
Apply Stroke to Fill Aplica el traç al farciment
Select objects to find the swatches nearest their colors. Seleccioneu objectes per trobar les mostres més properes als seus colors.
@msg select objects or give `color` seleccioneu objectes o indiqueu `color`
@msg `color` is not a colour `color` no és un color
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6103,6 +6103,9 @@ anchor Points: Body:
Find Nearest Swatch… Najít nejbližší položku vzorníku…
Find Nearest Swatch Najít nejbližší položku vzorníku
Apply Nearest Swatch Použít nejbližší položku vzorníku
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Použít výplň na tah
Apply Stroke to Fill Použít tah na výplň
Select objects to find the swatches nearest their colors. Vyberte objekty, k jejichž barvám chcete najít nejbližší položky vzorníku.
@msg select objects or give `color` vyberte objekty nebo zadejte `color`
@msg `color` is not a colour `color` není barva
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5969,6 +5969,9 @@ anchor Points: Punkter:
Find Nearest Swatch… Find nærmeste farveprøve…
Find Nearest Swatch Find nærmeste farveprøve
Apply Nearest Swatch Anvend nærmeste farveprøve
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Anvend udfyldning på kontur
Apply Stroke to Fill Anvend kontur på udfyldning
Select objects to find the swatches nearest their colors. Markér objekter for at finde de farveprøver, der ligger tættest på deres farver.
@msg select objects or give `color` markér objekter, eller angiv `color`
@msg `color` is not a colour `color` er ikke en farve
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5688,6 +5688,9 @@ anchor Points: Punkte:
Find Nearest Swatch… Ähnlichstes Farbfeld suchen…
Find Nearest Swatch Ähnlichstes Farbfeld suchen
Apply Nearest Swatch Ähnlichstes Farbfeld anwenden
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Fläche auf Kontur anwenden
Apply Stroke to Fill Kontur auf Fläche anwenden
Select objects to find the swatches nearest their colors. Wählen Sie Objekte aus, um die Farbfelder zu finden, die ihren Farben am nächsten kommen.
@msg select objects or give `color` wählen Sie Objekte aus oder geben Sie `color` an
@msg `color` is not a colour `color` ist keine Farbe
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6054,6 +6054,9 @@ anchor Points: Σημεία:
Find Nearest Swatch… Εύρεση πλησιέστερου δείγματος χρώματος…
Find Nearest Swatch Εύρεση πλησιέστερου δείγματος χρώματος
Apply Nearest Swatch Εφαρμογή πλησιέστερου δείγματος χρώματος
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Εφαρμογή γεμίσματος στο περίγραμμα
Apply Stroke to Fill Εφαρμογή περιγράμματος στο γέμισμα
Select objects to find the swatches nearest their colors. Επιλέξτε αντικείμενα για να βρείτε τα δείγματα χρωμάτων που πλησιάζουν περισσότερο τα χρώματά τους.
@msg select objects or give `color` επιλέξτε αντικείμενα ή δώστε `color`
@msg `color` is not a colour το `color` δεν είναι χρώμα
Can't render this file because it contains an unexpected character in line 163 and column 5.
+3
View File
@@ -6045,6 +6045,9 @@ anchor Points: Puntos:
Find Nearest Swatch… Buscar muestra más cercana…
Find Nearest Swatch Buscar muestra más cercana
Apply Nearest Swatch Aplicar muestra más cercana
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Aplicar relleno al trazo
Apply Stroke to Fill Aplicar trazo al relleno
Select objects to find the swatches nearest their colors. Seleccione objetos para buscar las muestras más cercanas a sus colores.
@msg select objects or give `color` seleccione objetos o indique `color`
@msg `color` is not a colour `color` no es un color
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6133,6 +6133,9 @@ anchor Points: Pisteet:
Find Nearest Swatch… Etsi lähin värimalli…
Find Nearest Swatch Etsi lähin värimalli
Apply Nearest Swatch Käytä lähintä värimallia
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Käytä täyttöä viivaan
Apply Stroke to Fill Käytä viivaa täyttöön
Select objects to find the swatches nearest their colors. Valitse objektit, joiden väreille haluat etsiä lähimmät värimallit.
@msg select objects or give `color` valitse objekteja tai anna `color`
@msg `color` is not a colour `color` ei ole väri
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6098,6 +6098,9 @@ anchor Points: Points :
Find Nearest Swatch… Rechercher la nuance la plus proche…
Find Nearest Swatch Rechercher la nuance la plus proche
Apply Nearest Swatch Appliquer la nuance la plus proche
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Appliquer le fond au contour
Apply Stroke to Fill Appliquer le contour au fond
Select objects to find the swatches nearest their colors. Sélectionnez des objets pour trouver les nuances les plus proches de leurs couleurs.
@msg select objects or give `color` sélectionnez des objets ou indiquez `color`
@msg `color` is not a colour `color` n’est pas une couleur
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6087,6 +6087,9 @@ anchor Points: Pontok:
Find Nearest Swatch… Legközelebbi színminta keresése…
Find Nearest Swatch Legközelebbi színminta keresése
Apply Nearest Swatch Legközelebbi színminta alkalmazása
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Kitöltés alkalmazása a körvonalra
Apply Stroke to Fill Körvonal alkalmazása a kitöltésre
Select objects to find the swatches nearest their colors. Jelöljön ki objektumokat a színeikhez legközelebbi színminták megkereséséhez.
@msg select objects or give `color` jelöljön ki objektumokat, vagy adja meg ezt: `color`
@msg `color` is not a colour a(z) `color` nem szín
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5969,6 +5969,9 @@ anchor Points: Titik:
Find Nearest Swatch… Cari Swatch Terdekat…
Find Nearest Swatch Cari Swatch Terdekat
Apply Nearest Swatch Terapkan Swatch Terdekat
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Terapkan isian ke guratan
Apply Stroke to Fill Terapkan guratan ke isian
Select objects to find the swatches nearest their colors. Pilih objek untuk menemukan swatch yang paling dekat dengan warnanya.
@msg select objects or give `color` pilih objek atau berikan `color`
@msg `color` is not a colour `color` bukan warna
Can't render this file because it contains an unexpected character in line 2235 and column 5.
+3
View File
@@ -5722,6 +5722,9 @@ anchor Points: Punti:
Find Nearest Swatch… Trova campione più vicino…
Find Nearest Swatch Trova campione più vicino
Apply Nearest Swatch Applica campione più vicino
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Applica riempimento alla traccia
Apply Stroke to Fill Applica traccia al riempimento
Select objects to find the swatches nearest their colors. Seleziona gli oggetti per trovare i campioni più vicini ai loro colori.
@msg select objects or give `color` seleziona gli oggetti o indica `color`
@msg `color` is not a colour `color` non è un colore
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5787,6 +5787,9 @@ anchor Points: ポイント:
Find Nearest Swatch… 最も近いスウォッチを検索…
Find Nearest Swatch 最も近いスウォッチを検索
Apply Nearest Swatch 最も近いスウォッチを適用
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke 塗りを線に適用
Apply Stroke to Fill 線を塗りに適用
Select objects to find the swatches nearest their colors. カラーに最も近いスウォッチを探すオブジェクトを選択してください。
@msg select objects or give `color` オブジェクトを選択するか `color` を指定してください
@msg `color` is not a colour `color` はカラーではありません
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6100,6 +6100,9 @@ anchor Points: წერტილები:
Find Nearest Swatch… უახლოესი ფერის ნიმუშის პოვნა…
Find Nearest Swatch უახლოესი ფერის ნიმუშის პოვნა
Apply Nearest Swatch უახლოესი ფერის ნიმუშის გამოყენება
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke შევსების გამოყენება კონტურზე
Apply Stroke to Fill კონტურის გამოყენება შევსებაზე
Select objects to find the swatches nearest their colors. აირჩიეთ ობიექტები მათ ფერებთან უახლოესი ფერის ნიმუშების საპოვნელად.
@msg select objects or give `color` აირჩიეთ ობიექტები ან მიუთითეთ `color`
@msg `color` is not a colour `color` ფერი არ არის
Can't render this file because it is too large.
+3
View File
@@ -5683,6 +5683,9 @@ anchor Points: 포인트:
Find Nearest Swatch… 가장 가까운 견본 찾기…
Find Nearest Swatch 가장 가까운 견본 찾기
Apply Nearest Swatch 가장 가까운 견본 적용
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke 칠을 선에 적용
Apply Stroke to Fill 선을 칠에 적용
Select objects to find the swatches nearest their colors. 색상에 가장 가까운 견본을 찾을 개체를 선택하십시오.
@msg select objects or give `color` 개체를 선택하거나 `color`를 지정하십시오
@msg `color` is not a colour `color`은(는) 색상이 아닙니다
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6013,6 +6013,9 @@ anchor Points: Punten:
Find Nearest Swatch… Dichtstbijzijnde staal zoeken…
Find Nearest Swatch Dichtstbijzijnde staal zoeken
Apply Nearest Swatch Dichtstbijzijnde staal toepassen
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Vulling op streek toepassen
Apply Stroke to Fill Streek op vulling toepassen
Select objects to find the swatches nearest their colors. Selecteer objecten om de stalen te vinden die het dichtst bij hun kleuren liggen.
@msg select objects or give `color` selecteer objecten of geef `color` op
@msg `color` is not a colour `color` is geen kleur
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5957,6 +5957,9 @@ anchor Points: Punkty:
Find Nearest Swatch… Znajdź najbliższą próbkę…
Find Nearest Swatch Znajdź najbliższą próbkę
Apply Nearest Swatch Zastosuj najbliższą próbkę
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Zastosuj wypełnienie do obrysu
Apply Stroke to Fill Zastosuj obrys do wypełnienia
Select objects to find the swatches nearest their colors. Zaznacz obiekty, aby znaleźć próbki najbliższe ich kolorom.
@msg select objects or give `color` zaznacz obiekty lub podaj `color`
@msg `color` is not a colour `color` nie jest kolorem
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6019,6 +6019,9 @@ anchor Points: Pontos:
Find Nearest Swatch… Localizar Amostra Mais Próxima…
Find Nearest Swatch Localizar Amostra Mais Próxima
Apply Nearest Swatch Aplicar Amostra Mais Próxima
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Aplicar Preenchimento ao Traçado
Apply Stroke to Fill Aplicar Traçado ao Preenchimento
Select objects to find the swatches nearest their colors. Selecione objetos para localizar as amostras mais próximas de suas cores.
@msg select objects or give `color` selecione objetos ou informe `color`
@msg `color` is not a colour `color` não é uma cor
Can't render this file because it contains an unexpected character in line 6 and column 47.
+3
View File
@@ -6080,6 +6080,9 @@ anchor Points: Точки:
Find Nearest Swatch… Найти ближайший образец…
Find Nearest Swatch Найти ближайший образец
Apply Nearest Swatch Применить ближайший образец
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Применить заливку к обводке
Apply Stroke to Fill Применить обводку к заливке
Select objects to find the swatches nearest their colors. Выделите объекты, чтобы найти образцы, ближайшие к их цветам.
@msg select objects or give `color` выделите объекты или укажите `color`
@msg `color` is not a colour `color` не является цветом
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6155,6 +6155,9 @@ shape Open Öppen
Find Nearest Swatch… Sök närmaste färgruta…
Find Nearest Swatch Sök närmaste färgruta
Apply Nearest Swatch Använd närmaste färgruta
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Använd fyllning på kontur
Apply Stroke to Fill Använd kontur på fyllning
Select objects to find the swatches nearest their colors. Markera objekt för att hitta de färgrutor som ligger närmast deras färger.
@msg select objects or give `color` markera objekt eller ange `color`
@msg `color` is not a colour `color` är ingen färg
Can't render this file because it contains an unexpected character in line 5 and column 25.
+3
View File
@@ -6060,6 +6060,9 @@ anchor Points: Noktalar:
Find Nearest Swatch… En Yakın Renk Örneğini Bul…
Find Nearest Swatch En Yakın Renk Örneğini Bul
Apply Nearest Swatch En Yakın Renk Örneğini Uygula
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Dolguyu Kontura Uygula
Apply Stroke to Fill Konturu Dolguya Uygula
Select objects to find the swatches nearest their colors. Renklerine en yakın renk örneklerini bulmak için nesneleri seçin.
@msg select objects or give `color` nesneleri seçin veya `color` verin
@msg `color` is not a colour `color` bir renk değil
Can't render this file because it contains an unexpected character in line 5 and column 25.
+3
View File
@@ -5702,6 +5702,9 @@ anchor Points: Точки:
Find Nearest Swatch… Знайти найближчий зразок…
Find Nearest Swatch Знайти найближчий зразок
Apply Nearest Swatch Застосувати найближчий зразок
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Застосувати заливку до обведення
Apply Stroke to Fill Застосувати обведення до заливки
Select objects to find the swatches nearest their colors. Виділіть об’єкти, щоб знайти зразки, найближчі до їхніх кольорів.
@msg select objects or give `color` виділіть об’єкти або вкажіть `color`
@msg `color` is not a colour `color` не є кольором
Can't render this file because it contains an unexpected character in line 177 and column 5.
+3
View File
@@ -5703,6 +5703,9 @@ anchor Points: Điểm:
Find Nearest Swatch… Tìm mẫu màu gần nhất…
Find Nearest Swatch Tìm mẫu màu gần nhất
Apply Nearest Swatch Áp dụng mẫu màu gần nhất
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke Áp dụng màu tô cho nét
Apply Stroke to Fill Áp dụng nét cho màu tô
Select objects to find the swatches nearest their colors. Chọn đối tượng để tìm các mẫu màu gần nhất với màu của chúng.
@msg select objects or give `color` chọn đối tượng hoặc cung cấp `color`
@msg `color` is not a colour `color` không phải là màu
Can't render this file because it contains an unexpected character in line 199 and column 5.
+3
View File
@@ -5944,6 +5944,9 @@ anchor Points: 点:
Find Nearest Swatch… 查找最接近的色板…
Find Nearest Swatch 查找最接近的色板
Apply Nearest Swatch 应用最接近的色板
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke 将填色应用于描边
Apply Stroke to Fill 将描边应用于填色
Select objects to find the swatches nearest their colors. 选择对象以查找与其颜色最接近的色板。
@msg select objects or give `color` 请选择对象或提供 `color`
@msg `color` is not a colour `color` 不是颜色
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5740,6 +5740,9 @@ anchor Points: 點:
Find Nearest Swatch… 尋找最接近的色票…
Find Nearest Swatch 尋找最接近的色票
Apply Nearest Swatch 套用最接近的色票
# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530).
Apply Fill to Stroke 將填色套用至筆畫
Apply Stroke to Fill 將筆畫套用至填色
Select objects to find the swatches nearest their colors. 選取物件以尋找與其顏色最接近的色票。
@msg select objects or give `color` 請選取物件或提供 `color`
@msg `color` is not a colour `color` 不是顏色
Can't render this file because it contains an unexpected character in line 6 and column 47.
-6
View File
@@ -782,12 +782,6 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
recolor(app, ui, &tgt, invert, false);
}
}
// Each selected object's stroke takes its own fill, or the other way (#5530).
for (label, cmd) in [(tl!("Apply Fill to Stroke"), "paint.fillToStroke"), (tl!("Apply Stroke to Fill"), "paint.strokeToFill")] {
if menu_item(ui, label, true, false) {
app.run(cmd, json!({})).ok();
}
}
ui.separator();
if menu_item(ui, tl!("Create New Swatch…"), color.is_some(), false)
&& let Some(c) = color