M3.16: the eyedropper samples type gradients in text space
Shift-sampling a type object's run gradient projected the document point onto a vector kept in text space, so placed gradients on moved or turned type sampled the wrong colour. The eyedropper now reads the paint through Node::proxy_paint and samples in the paint's own space and box (text space for runs, the stroke-inflated box for strokes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
cca1d11032
commit
2ed962e7d0
|
|
@ -42,15 +42,11 @@ impl Tool for EyedropperTool {
|
|||
let src = paint_owner(cx.doc, h.leaf);
|
||||
let Some(n) = cx.doc.node(src) else { return vec![] };
|
||||
if ev.mods.shift {
|
||||
let (fill, stroke) = match &n.kind {
|
||||
vectorcraft_doc::NodeKind::Text(t) => {
|
||||
let st = t.runs.first().map(|r| r.style.clone());
|
||||
(st.as_ref().map(|s| s.fill.clone()).unwrap_or_default(), st.map(|s| s.stroke).unwrap_or_default())
|
||||
}
|
||||
_ => (n.appearance.fill_paint(), n.appearance.stroke_paint()),
|
||||
};
|
||||
let paint = if h.kind == HitKind::Stroke || fill.is_none() { stroke } else { fill };
|
||||
return match paint_color_at(&paint, n.geometric_bounds(), ev.pos) {
|
||||
let fill_none = n.proxy_paint(false).is_none_or(|(p, ..)| p.is_none());
|
||||
let Some((paint, to_doc, bounds)) = n.proxy_paint(h.kind == HitKind::Stroke || fill_none) else { return vec![] };
|
||||
// Sample in the paint's own space (text space for type runs) and box.
|
||||
let p = if to_doc.determinant().abs() > 1e-12 { to_doc.inverse() * ev.pos } else { ev.pos };
|
||||
return match paint_color_at(paint, Some(bounds), p) {
|
||||
Some(c) => vec![Action::Exec("paint.sampleColor".into(), json!({ "color": c.to_hex() }))],
|
||||
None => vec![],
|
||||
};
|
||||
|
|
@ -96,6 +92,27 @@ mod tests {
|
|||
assert_eq!(paint_color_at(&paint, Some(r), Point::new(100.0, 5.0)).unwrap().to_hex(), "#000000");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_gradients_are_sampled_in_text_space() {
|
||||
use vectorcraft_color::{Gradient, GradientGeom, GradientPaint};
|
||||
use vectorcraft_doc::{CharStyle, Document, Node, NodeKind, TextObject};
|
||||
let mut d = Document::new(500.0, 500.0);
|
||||
let l = d.layers[0].id;
|
||||
let id = d.alloc_id();
|
||||
// Type turned 90°: its baseline runs down from (100, 100).
|
||||
let mut t = TextObject::point(Point::ZERO, "Gradient", CharStyle { size: 20.0, ..Default::default() });
|
||||
t.xf = vectorcraft_geom::Affine::translate((100.0, 100.0)) * vectorcraft_geom::Affine::rotate(std::f64::consts::FRAC_PI_2);
|
||||
let mut g = GradientPaint::new(Gradient::default());
|
||||
g.geom = Some(GradientGeom { start: Point::ZERO, end: Point::new(100.0, 0.0), aspect: 1.0 });
|
||||
t.runs[0].style.fill = Paint::Gradient(Box::new(g));
|
||||
d.insert(Some(l), 0, Node::new(id, NodeKind::Text(Box::new(t)))).unwrap();
|
||||
let (s, p) = (Selection::default(), paint());
|
||||
let shift = Mods { shift: true, ..Default::default() };
|
||||
// 80 % along the baseline (text space (80, -5)).
|
||||
let a = EyedropperTool.pointer(&cx(&d, &s, &p), &PointerEvent::new(PointerKind::Down, 105.0, 180.0).with_mods(shift));
|
||||
assert_eq!(a, vec![Action::Exec("paint.sampleColor".into(), json!({"color": "#333333"}))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn radial_sample_honours_aspect() {
|
||||
let mut g = vectorcraft_color::GradientPaint::new(vectorcraft_color::Gradient {
|
||||
|
|
|
|||
Loading…
Reference in New Issue