diff --git a/crates/effects/src/group.rs b/crates/effects/src/group.rs index 2211a4364..72dfb3274 100644 --- a/crates/effects/src/group.rs +++ b/crates/effects/src/group.rs @@ -13,12 +13,13 @@ use std::sync::Arc; +use vectorcraft_doc::color::{Color, Paint}; use vectorcraft_doc::{Appearance, AppearanceItem, Node, NodeId, NodeKind, StrokeLayer}; use vectorcraft_geom::{FillRule, PathData, Rect}; use vectorcraft_pathops::{self as po, PathfinderOp}; /// Effect ids of the Pathfinder submenu with their operation, in Illustrator's menu order. -pub const PATHFINDER_EFFECTS: [(&str, &str, PathfinderOp); 10] = [ +pub const PATHFINDER_EFFECTS: [(&str, &str, PathfinderOp); 11] = [ ("pathfinder.add", "Add", PathfinderOp::Unite), ("pathfinder.intersect", "Intersect", PathfinderOp::Intersect), ("pathfinder.exclude", "Exclude", PathfinderOp::Exclude), @@ -29,6 +30,7 @@ pub const PATHFINDER_EFFECTS: [(&str, &str, PathfinderOp); 10] = [ ("pathfinder.merge", "Merge", PathfinderOp::Merge), ("pathfinder.crop", "Crop", PathfinderOp::Crop), ("pathfinder.outline", "Outline", PathfinderOp::Outline), + ("pathfinder.hardMix", "Hard Mix", PathfinderOp::HardMix), ]; /// Is `id` an Effect → Pathfinder effect? @@ -138,15 +140,56 @@ pub fn pathfinder_children(group: &Node, hook: Option) -> Option Node { + let mut cmyk = [0.0f32; 4]; + let mut painted = false; + for &si in covering { + let src = &sources[shapes[si].key as usize]; + let paint = src.appearance.fill_paint(); + if let Some(color) = paint.color() { + let tinted = match paint { + Paint::Solid { tint, .. } => color.tinted(tint), + _ => color, + }; + let [c, m, y, k] = tinted.to_cmyk(); + for (out, v) in cmyk.iter_mut().zip([c, m, y, k]) { + *out = out.max(v); + } + painted = true; + } + } + let fill = if painted { + Paint::solid(Color::cmyk(cmyk[0], cmyk[1], cmyk[2], cmyk[3])) + } else { + Paint::None + }; + let top = covering.last().and_then(|&si| sources.get(shapes[si].key as usize)); + let mut n = Node::path(top.map_or(NodeId(0), |s| s.id), path, Appearance::basic(fill, Paint::None, 0.0)); + if let Some(src) = top { + n.opacity = src.opacity; + n.blend = src.blend; + } + n +} + fn is_container(n: &Node) -> bool { matches!(n.kind, NodeKind::Group { .. } | NodeKind::Layer { .. }) } @@ -320,6 +363,32 @@ mod tests { po::area(n.path_data().unwrap(), FillRule::NonZero).abs() } + #[test] + fn hard_mix_takes_the_highest_of_each_cmyk_channel_and_drops_strokes() { + let a = Appearance::basic(Paint::solid(Color::cmyk(0.2, 0.66, 0.4, 0.0)), Paint::solid(Color::BLACK), 2.0); + let b = Appearance::basic(Paint::solid(Color::cmyk(0.4, 0.2, 0.3, 0.1)), Paint::solid(Color::BLACK), 2.0); + let mut g = Node::group( + NodeId(10), + vec![ + Arc::new(Node::path(NodeId(1), shapes::rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)), a)), + Arc::new(Node::path(NodeId(2), shapes::rectangle(Rect::new(5.0, 0.0, 15.0, 10.0)), b)), + ], + ); + g.appearance.effects.push(Effect { id: "pathfinder.hardMix".into(), params: serde_json::json!({}), visible: true }); + let faces = pathfinder_children(&g, None).unwrap(); + assert_eq!(faces.len(), 3); + let overlap = faces + .iter() + .find(|n| { + let b = n.geometric_bounds().unwrap(); + b.x0 >= 4.9 && b.x1 <= 10.1 && b.y0 <= 0.1 && b.y1 >= 9.9 + }) + .expect("overlap face"); + assert!(overlap.appearance.stroke_paint().is_none()); + let [c, m, y, k] = overlap.appearance.fill_paint().color().unwrap().to_cmyk(); + assert!((c - 0.4).abs() < 0.02 && (m - 0.66).abs() < 0.02 && (y - 0.4).abs() < 0.02 && (k - 0.1).abs() < 0.02, "{c} {m} {y} {k}"); + } + #[test] fn shape_modes_follow_the_pathfinder_panel() { let add = pathfinder_children(&group_with("pathfinder.add"), None).unwrap(); diff --git a/crates/engine/src/cmd/pathops.rs b/crates/engine/src/cmd/pathops.rs index 06e53e186..cbed03515 100644 --- a/crates/engine/src/cmd/pathops.rs +++ b/crates/engine/src/cmd/pathops.rs @@ -381,6 +381,7 @@ fn pf_label(op: PathfinderOp) -> &'static str { PathfinderOp::Crop => "Crop", PathfinderOp::Outline => "Outline", PathfinderOp::MinusBack => "Minus Back", + PathfinderOp::HardMix => "Hard Mix", } } diff --git a/crates/pathops/src/pathfinder.rs b/crates/pathops/src/pathfinder.rs index aa63f0381..aa28d66d7 100644 --- a/crates/pathops/src/pathfinder.rs +++ b/crates/pathops/src/pathfinder.rs @@ -50,6 +50,9 @@ pub enum PathfinderOp { Outline, /// Front-most minus everything behind; keeps the front-most key. MinusBack, + /// Effect only: split into faces like Divide; overlapping faces take the highest of each CMYK + /// channel from every shape covering them (Illustrator Effect → Pathfinder → Hard Mix). + HardMix, } /// One face of the planar arrangement of a set of shapes (Shape Builder / Live Paint face). @@ -172,6 +175,13 @@ pub fn pathfinder(op: PathfinderOp, shapes: &[Shape]) -> Vec { .collect() } PathfinderOp::Outline => outline_edges(&arr, shapes), + PathfinderOp::HardMix => regions_of(&arr) + .into_iter() + .filter_map(|r| { + let key = shapes.get(r.top()?)?.key; + Some(Shape::new(r.path, FillRule::NonZero, key)) + }) + .collect(), } } diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 10e9a3085..d33d4c3c2 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1284,6 +1284,7 @@ Hand-Drawn Ръчно рисувани Handles Дръжки Hard Light Твърда светлина + Hard Mix Твърдо смесване Harmony Rules: Правила за хармония: Harmony Sets Набори от хармонии Height Височина diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 5fc134364..38e940602 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2906,6 +2906,7 @@ lighting Light Světlo Darken Ztmavit Exclusion Vyloučit Hard Light Tvrdé světlo + Hard Mix Tvrdé míchání Lighten Zesvětlit Luminosity Světlost Overlay Překrýt diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 795b35141..c3ffbf418 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -2679,6 +2679,7 @@ lighting Light Lys Darken Mørkere Exclusion Udelukkelse Hard Light Hårdt lys + Hard Mix Hård blanding Lighten Lysere Luminosity Luminans Overlay Overlejring diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 70f8595c2..be5b88971 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1310,6 +1310,7 @@ file-filter Templates Vorlagen Hand-Drawn Handgezeichnet Handles Griffe Hard Light Hartes Licht + Hard Mix Hart mischen Harmony Rules: Harmonieregeln: Harmony Sets Harmoniesätze Height Höhe diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 65e0b77ab..6a62721bc 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1247,6 +1247,7 @@ Hand-Drawn Σχεδιασμένο στο χέρι Handles Λαβές Hard Light Έντονο φως + Hard Mix Έντονη ανάμειξη Harmony Rules: Κανόνες χρωματικής αρμονίας: Harmony Sets Σύνολα χρωματικής αρμονίας Height Ύψος diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 582d33b53..ea6d88348 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1316,6 +1316,7 @@ Hand-Drawn Dibujado a mano Handles Manejadores Hard Light Luz fuerte + Hard Mix Mezcla definida Harmony Rules: Reglas de armonía: Harmony Sets Conjuntos de armonía Height Altura diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 68d780b65..2a4ba6ba0 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -2852,6 +2852,7 @@ lighting Light Valo Darken Tummenna Exclusion Poissulkeminen Hard Light Kova valo + Hard Mix Kova sekoitus Lighten Vaalenna Luminosity Luminanssi Overlay Sulautus diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 563b9fe44..e0c557dd4 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1315,6 +1315,7 @@ Hand-Drawn Dessiné à la main Handles Poignées Hard Light Lumière crue + Hard Mix Mélange maximal Harmony Rules: Règles d’harmonie : Harmony Sets Ensembles d’harmonies Height Hauteur diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 6db74f1bd..654864d4e 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1684,6 +1684,7 @@ file-filter Templates Sablonok Handles Fogantyúk Hard Szigorú Hard Light Kemény fény + Hard Mix Kemény keverés Harmony Rules Harmóniaszabályok Harmony Rules: Harmóniaszabályok: Harmony Sets Harmóniakészletek diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index e53341bb4..e46790e49 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -2679,6 +2679,7 @@ lighting Light Cahaya Darken Menggelapkan Exclusion Pengecualian Hard Light Cahaya keras + Hard Mix Campuran keras Lighten Menerangkan Luminosity Luminositas Overlay Menimpa diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index d312ce862..d654dfab5 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1326,6 +1326,7 @@ file-filter Templates Modelli Hand-Drawn Disegnato a mano Handles Maniglie Hard Light Luce intensa + Hard Mix Miscela intensa Harmony Rules: Regole di armonia: Harmony Sets Gruppi di armonia Height Altezza diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index da1d679a5..3a40ad331 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1598,6 +1598,7 @@ Hand Tool 手のひらツール Handles ハンドル Hard Light ハードライト + Hard Mix ハードミックス Harmony Rules: ハーモニールール: Height 高さ Height: 高さ: diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 0ba856864..41c86cb43 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -2491,6 +2491,7 @@ lighting Light სინათლე Darken ჩამუქება Exclusion გამორიცხვა Hard Light მკვეთრი სინათლე + Hard Mix მკაცრი შერევა Lighten გაღიავება Luminosity ნათება Overlay გადაფარვა diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 3e9de3daa..cf336f39a 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -1593,6 +1593,7 @@ Hand Tool 손 도구 Handles 핸들 Hard Light 하드 라이트 + Hard Mix 하드 믹스 Harmony Rules: 하모니 규칙: Height 높이 Height: 높이: diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 966001792..e0d559649 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -2693,6 +2693,7 @@ lighting Light Licht Darken Donkerder Exclusion Uitsluiting Hard Light Hard licht + Hard Mix Hard mengen Lighten Lichter Luminosity Luminositeit Overlay Bedekken diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 8840c2659..a6e5824b3 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2784,6 +2784,7 @@ lighting Light Światło Darken Ciemniej Exclusion Wykluczenie Hard Light Ostre światło + Hard Mix Mieszanie twarde Lighten Jaśniej Luminosity Jasność Overlay Nakładka diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index f7b086f73..117b541a1 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1137,6 +1137,7 @@ Hand-Drawn Desenhado à Mão Handles Alças Hard Light Luz Forte + Hard Mix Mistura definida Harmony Rules: Regras de Harmonia: Harmony Sets Conjuntos de Harmonia Height Altura diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 006b59de7..72ae9a1a4 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1324,6 +1324,7 @@ file-filter Templates Шаблоны Hand-Drawn Нарисованный от руки Handles Маркеры Hard Light Жёсткий свет + Hard Mix Жёсткое наложение Harmony Rules: Правила гармонии: Harmony Sets Наборы гармонии Height Высота diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 680f91ddb..41feec1d6 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -1286,6 +1286,7 @@ Hand-Drawn Handritad Handles Handtag Hard Light Hårt ljus + Hard Mix Hård blandning Harmony Rules: Harmoniregler: Harmony Sets Harmoniuppsättningar Height Höjd diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index e539fca5a..8a2632faf 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1320,6 +1320,7 @@ file-filter Templates Шаблони Hand-Drawn Намальований від руки Handles Маркери Hard Light Жорстке світло + Hard Mix Жорстке накладання Harmony Rules: Правила гармонії: Harmony Sets Набори гармонії Height Висота diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 53e3b1f60..0b16b5910 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1525,6 +1525,7 @@ file-filter Templates Mẫu Handles Tay nắm Hard Cứng Hard Light Ánh sáng mạnh + Hard Mix Trộn cứng Harmony Rules: Quy tắc hài hòa màu: Harmony Sets Bộ hài hòa màu Height Chiều cao diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index d40a9b918..1cfe855d8 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1272,6 +1272,7 @@ file-filter Templates 模板 Hand-Drawn 手绘 Handles 手柄 Hard Light 强光 + Hard Mix 实色混合 Harmony Rules: 协调规则: Harmony Sets 色彩调和组 Height 高度 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index d1d9340b7..86b007de8 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1298,6 +1298,7 @@ file-filter Templates 範本 Hand-Drawn 手繪 Handles 控制把手 Hard Light 實光 + Hard Mix 實色混合 Harmony Rules: 色彩調和規則: Harmony Sets 色彩調和組 Height 高度