diff --git a/ROADMAP.md b/ROADMAP.md index 3e8cfb9d34..b28d4dbef8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -138,6 +138,7 @@ Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gap - 2026-10-31: Export for Screens works as the reference app's dialog: Artboards and Assets tabs with a grid or list of thumbnails, Clear Selection and Prefix, Create Sub-folders by Scale or Format (on by default), Export PDFs As Single File (the chosen artboards as one PDF's pages, `pdfs: "single"`) or Multiple Files, iOS and Android rows, format presets in a ☰ menu, and a count of the files the export writes (#4895). About 55 community PRs and fixes landed the same day: Find Font, Replace Font, Outline Stroke and Expand leave hidden and locked art alone (with a warning); DXF export writes LINE and ELLIPSE entities and outlines only the strokes a CAD line can't draw; Blob Brush Angle and Roundness; Shift-constrained Perspective Selection; Grid Repeat Options Preview; a gradient mesh from a file budgeted at about a million quads; width-profile strokes keep their shape zoomed out; `.ai`/EPS import fidelity (area type, envelopes, transparency in dictionaries, character styles); PDF import finds hyphenated and CMap-suffixed font names; Windows renders follow the process's core affinity; Enter or Escape in a floating panel's field no longer reaches the canvas (#4803); Properties shows Blend Options for any selected blend (#4817); Artwork Cleanup finds doubled type (#4835) and joins stacked lines into one object (Join Point Text, #4826). Headline numbers unchanged. - 2026-10-31: Arrange Tools, a built-in add-on (Object › Plug-ins › Arrange Tools): Arrange in Grid lays the selected objects out in rows and columns, a row or a column with gaps, from the selection's top-left or centred on the artboard; Swap Objects swaps two objects' places (and, with Swap Objects and Sizes, their sizes); Replace with Top Object puts a copy of the topmost selected object in place of each of the others, kept, stretched or fitted in proportion; Set Size of Each gives every selected object an exact width and/or height about a reference point; Zoom to Selection fits the view to the selection. Each is one undo step with a previewed dialog where it has options. A VectorCraft addition: headline numbers unchanged. - 2026-11-11: Window › Swatch Libraries › Gradients › Organic Fades: fifteen soft fades (ten linear, five radial) through three to five unevenly spaced stops with off-center midpoints, mixed with the Perceptual method, that thin out to transparent at the end of a linear one and at the edge of a radial one; they also list in the Swatches panel's library menu, `swatch.library.list` and the Gradient panel's Presets. +- 2026-11-11: Window › Swatch Libraries › Gradients › Holographic: seven foils (opaque linear gradients of six narrow pastel and pearl bands with off-center midpoints) and seven glows (radial gradients from a bright center out to a clear edge), mixed with the Perceptual method; they also list in the Swatches panel's library menu, `swatch.library.list` and the Gradient panel's Presets. - 2026-10-30: Gradient panel presets: a Presets menu applies one of the built-in gradient libraries' presets (grouped by theme) or a saved one to the gradient behind the active proxy, keeping its placement, as one undo step; the panel menu saves the current gradient as a preset, kept with the preferences, and deletes saved ones (`gradient.presets.list/apply/save/delete`). - 2026-10-23: Pathfinder › Trap: the Pathfinder panel's Trap… and Effect › Pathfinder › Trap… add a thin overprinting strip of the lighter colour inside the darker one where two solid colours meet, so misregistered plates leave no gap ([trapping](https://helpx.adobe.com/illustrator/using/trapping.html)), with Thickness, Height/Width, Tint Reduction, Traps with Process Color and Reverse Traps. Illustrator effects 49/50 (SVG Filters remains); Live vector effects hours 6–10 → 4–8; headline numbers unchanged. - 2026-10-23: OpenType SVG colour fonts draw as vector art: each glyph's SVG document is read by the SVG importer, in the text's colours where it asks for them, and draws, exports and outlines as COLR glyphs do; all colour font formats are now drawn. diff --git a/crates/color/src/libraries.rs b/crates/color/src/libraries.rs index 5b5fd3653a..5fc952a061 100644 --- a/crates/color/src/libraries.rs +++ b/crates/color/src/libraries.rs @@ -73,6 +73,7 @@ pub const GRADIENT_LIBRARIES: &[BuiltinLibrary] = &[ lib!("sky-gradients", "Sky Gradients", sky_gradients), lib!("neutral-gradients", "Neutral Gradients", neutral_gradients), lib!("organic-fades", "Organic Fades", organic_fades), + lib!("holographic", "Holographic", holographic), lib!("duotone-gradients", "Duotone Gradients", duotone_gradients), lib!("radial-glows", "Radial Glows", radial_glows), ]; @@ -531,6 +532,62 @@ fn organic_fades() -> (Vec, Vec) { (s, vec![]) } +/// The stops of a [`holographic`] look: offset, opacity and midpoint toward the next stop. +type Bands = [(f32, f32, f32)]; + +/// Foil: six opaque linear bands, a narrow bright one among them, with off-center midpoints. +const FOIL: &Bands = &[(0.0, 1.0, 0.37), (0.13, 1.0, 0.64), (0.34, 1.0, 0.28), (0.48, 1.0, 0.68), (0.71, 1.0, 0.42), (1.0, 1.0, 0.5)]; + +/// Glow: five radial rings from a bright center out to a clear edge. +const GLOW: &Bands = &[(0.0, 0.95, 0.4), (0.12, 1.0, 0.58), (0.31, 0.72, 0.34), (0.58, 0.3, 0.62), (1.0, 0.0, 0.5)]; + +/// A [`holographic`] look: name, kind, stops and the OKLCH color of each stop in order. +type Look = (&'static str, GradientKind, &'static Bands, Anchors); + +/// Iridescent looks mixed perceptually: linear foils of narrow pastel and pearl bands, and radial +/// glows that fade out to a clear edge. Each color is one stop of [`FOIL`] or [`GLOW`] in order. +fn holographic() -> (Vec, Vec) { + use GradientKind::{Linear, Radial}; + // Pale pearl, pastel and deep-violet OKLCH `(l, c, h°)` anchors. + const PEARL: (f64, f64, f64) = (0.97, 0.01, 90.0); + const SILVER: (f64, f64, f64) = (0.86, 0.015, 255.0); + const BLUSH: (f64, f64, f64) = (0.82, 0.055, 340.0); + const VIOLET: (f64, f64, f64) = (0.72, 0.1, 310.0); + const BLUE: (f64, f64, f64) = (0.73, 0.09, 255.0); + const CYAN: (f64, f64, f64) = (0.79, 0.09, 210.0); + const MINT: (f64, f64, f64) = (0.82, 0.08, 165.0); + const LIME: (f64, f64, f64) = (0.86, 0.085, 125.0); + const GOLD: (f64, f64, f64) = (0.89, 0.08, 85.0); + const AMBER: (f64, f64, f64) = (0.82, 0.1, 55.0); + const CORAL: (f64, f64, f64) = (0.78, 0.09, 25.0); + const DEEP_VIOLET: (f64, f64, f64) = (0.42, 0.04, 275.0); + let looks: [Look; 14] = [ + ("Prism Veil", Linear, FOIL, &[SILVER, BLUSH, PEARL, CYAN, VIOLET, SILVER]), + ("Opal Ribbon", Linear, FOIL, &[PEARL, MINT, BLUSH, PEARL, BLUE, SILVER]), + ("Pearl Flash", Linear, FOIL, &[SILVER, VIOLET, PEARL, GOLD, CYAN, PEARL]), + ("Aurora Foil", Linear, FOIL, &[BLUE, MINT, PEARL, BLUSH, CYAN, SILVER]), + ("Iris Sheen", Linear, FOIL, &[BLUSH, VIOLET, PEARL, CYAN, MINT, SILVER]), + ("Frost Spectrum", Linear, FOIL, &[CYAN, BLUE, PEARL, MINT, BLUSH, SILVER]), + ("Silver Tide", Linear, FOIL, &[SILVER, CYAN, PEARL, VIOLET, BLUE, SILVER]), + ("Rose Quartz Gleam", Radial, GLOW, &[PEARL, BLUSH, CORAL, VIOLET, BLUSH]), + ("Moonstone Shift", Radial, GLOW, &[PEARL, CYAN, MINT, BLUE, SILVER]), + ("Violet Edge", Radial, GLOW, &[PEARL, VIOLET, BLUSH, BLUE, DEEP_VIOLET]), + ("Mint Halo", Radial, GLOW, &[PEARL, MINT, CYAN, LIME, MINT]), + ("Amber Corona", Radial, GLOW, &[PEARL, GOLD, AMBER, CORAL, AMBER]), + ("Polar Glint", Radial, GLOW, &[PEARL, CYAN, BLUE, VIOLET, BLUE]), + ("Pastel Orbit", Radial, GLOW, &[PEARL, BLUSH, MINT, CYAN, VIOLET]), + ]; + let s = looks + .iter() + .map(|&(name, kind, bands, colors)| { + let stops: Vec<(f32, Color, f32, f32)> = + bands.iter().zip(colors).map(|(&(at, opacity, mid), &(l, c, h))| (at, oklch(l, c, h), opacity, mid)).collect(); + gradient_with(name, kind, GradientMethod::Perceptual, &stops) + }) + .collect(); + (s, vec![]) +} + /// The sequential scales of [`perceptual`]: name and anchors of rising lightness. const SEQUENTIAL: [(&str, Anchors); 5] = [ ("Ember", &[(0.22, 0.10, 300.0), (0.55, 0.18, 30.0), (0.93, 0.15, 100.0)]), @@ -708,9 +765,63 @@ mod tests { } } + #[test] + fn holographic_holds_seven_opaque_foils_and_seven_glows_to_a_clear_edge() { + let lib = builtin_library("holographic").unwrap(); + let order: Vec<&str> = GRADIENT_LIBRARIES.iter().map(|b| b.name).collect(); + assert_eq!(order.iter().position(|n| *n == "Holographic"), Some(4), "after Organic Fades: {order:?}"); + assert!(lib.groups.is_empty()); + assert_eq!(lib.len(), 14); + // No name another built-in gradient has, in any case. + let mut others = std::collections::HashSet::new(); + for other in builtins().filter(|b| b.id != "holographic").map(|b| builtin_library(b.id).unwrap()) { + others.extend(other.iter().filter(|s| matches!(s.paint, Paint::Gradient(_))).map(|s| s.name.to_lowercase())); + } + assert!(lib.iter().all(|s| !others.contains(&s.name.to_lowercase())), "a name another library has"); + let (mut foils, mut glows) = (0, 0); + for s in lib.iter() { + let Paint::Gradient(g) = &s.paint else { panic!("{} is a gradient", s.name) }; + let (g, n) = (&g.gradient, &s.name); + assert_eq!(g.method, GradientMethod::Perceptual, "{n}"); + let bands = match &g.kind { + GradientKind::Linear => { + foils += 1; + FOIL + } + GradientKind::Radial => { + glows += 1; + GLOW + } + kind => panic!("{n}: {kind:?}"), + }; + // Every stop takes its band's offset, opacity and midpoint, and none is dropped. + let shape: Vec<(f32, f32, f32)> = g.stops.iter().map(|st| (st.offset, st.opacity, st.midpoint)).collect(); + assert_eq!(shape, bands, "{n}"); + assert!(g.stops.iter().all(|st| matches!(st.color, Color::Rgb { .. })), "{n}"); + } + assert_eq!((foils, glows), (7, 7)); + // Foils are opaque; a glow is clear at the edge. + assert!(FOIL.iter().all(|&(_, opacity, _)| opacity == 1.0)); + assert_eq!(GLOW.last().map(|b| b.1), Some(0.0)); + // Every segment's midpoint is off center and within the Gradient panel's 13 to 87%. + for bands in [FOIL, GLOW] { + assert!(bands[..bands.len() - 1].iter().all(|&(_, _, m)| (m - 0.5).abs() > 0.02 && (0.13..=0.87).contains(&m))); + assert!(bands.windows(2).all(|w| w[0].0 < w[1].0) && bands[0].0 == 0.0 && bands[bands.len() - 1].0 == 1.0); + } + // Sampled colors: Prism Veil's bright band is the pearl anchor, and Violet Edge ends in deep violet. + let stop = |name: &str, i: usize| match &lib.swatch(name).unwrap().paint { + Paint::Gradient(g) => g.gradient.stops[i].color, + _ => panic!("{name} is a gradient"), + }; + assert_eq!(stop("Prism Veil", 2), oklch(0.97, 0.01, 90.0)); + assert_eq!(stop("Violet Edge", 4), oklch(0.42, 0.04, 275.0)); + let [l, ..] = to_oklab(&stop("Violet Edge", 4)); + assert!(l < 0.5 && to_oklab(&stop("Prism Veil", 2))[0] > 0.95, "{l}"); + } + #[test] fn the_other_libraries_keep_classic_mixing_and_centered_midpoints() { - for b in builtins().filter(|b| b.id != "organic-fades") { + for b in builtins().filter(|b| !["organic-fades", "holographic"].contains(&b.id)) { for s in builtin_library(b.id).unwrap().iter() { if let Paint::Gradient(g) = &s.paint { assert_eq!(g.gradient.method, GradientMethod::Classic, "{}", s.name); diff --git a/crates/engine/src/tests/gradpresets.rs b/crates/engine/src/tests/gradpresets.rs index 807094a1bd..4328361b8b 100644 --- a/crates/engine/src/tests/gradpresets.rs +++ b/crates/engine/src/tests/gradpresets.rs @@ -40,7 +40,16 @@ fn built_in_presets_are_the_gradient_libraries_by_theme_with_unique_names() { libraries.dedup(); assert_eq!( libraries, - ["Spectrum Gradients", "Sky Gradients", "Neutral Gradients", "Organic Fades", "Duotone Gradients", "Radial Glows", "Metallic Gradients"] + [ + "Spectrum Gradients", + "Sky Gradients", + "Neutral Gradients", + "Organic Fades", + "Holographic", + "Duotone Gradients", + "Radial Glows", + "Metallic Gradients" + ] ); let mut seen = std::collections::HashSet::new(); assert!(names(&list).iter().all(|n| seen.insert(n.to_lowercase())), "a name names one preset"); @@ -82,6 +91,42 @@ fn an_organic_fade_applies_with_its_method_midpoints_and_opacities() { assert_eq!(row["gradient"]["method"], "perceptual"); } +#[test] +fn a_holographic_foil_applies_in_place_as_one_undo_step() { + let (mut s, id) = session_with(json!({"gradient": {"angle": 30}})); + let before = paint(&s, id, false); + let undo = s.doc().unwrap().history.undo.len(); + let veil = s.gradient_preset("prism veil").unwrap(); + assert_eq!((veil.library, veil.gradient.method), (Some("Holographic"), GradientMethod::Perceptual)); + let r = s.execute("gradient.presets.apply", &json!({"name": "prism veil"})).unwrap(); + assert_eq!(r["name"].as_str(), Some("Prism Veil")); + let g = paint(&s, id, false); + assert_eq!((g.gradient.kind, g.gradient.method, &g.gradient.stops), (GradientKind::Linear, GradientMethod::Perceptual, &veil.gradient.stops)); + assert_eq!(g.gradient.stops.len(), 6); + assert_eq!(g.angle, before.angle, "the placement stays"); + assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1, "one undo step"); + assert_eq!(s.gradient_preset_name(&g.gradient).as_deref(), Some("Prism Veil")); + s.execute("edit.undo", &json!({})).unwrap(); + assert_eq!(paint(&s, id, false), before); +} + +/// A preset saved as "Mint Halo" before Holographic existed stays listed under a free name. +#[test] +fn a_saved_preset_named_like_a_holographic_glow_gets_a_free_name() { + let (mut s, id) = session_with(json!({"gradient": {"kind": "radial"}})); + let halo = s.gradient_preset("Mint Halo").unwrap().gradient; + let mut own = paint(&s, id, false).gradient; + own.dither = true; + let mut prefs = s.prefs.clone(); + prefs.gradient_presets = vec![crate::cmd::GradientPreset { name: "MINT HALO".into(), gradient: own.clone() }]; + s.apply_prefs(prefs); + assert_eq!(s.prefs.gradient_presets.iter().map(|p| p.name.as_str()).collect::>(), ["MINT HALO 2"]); + s.execute("gradient.presets.apply", &json!({"name": "mint halo 2"})).unwrap(); + assert_eq!(paint(&s, id, false).gradient, own); + s.execute("gradient.presets.apply", &json!({"name": "Mint Halo"})).unwrap(); + assert_eq!(paint(&s, id, false).gradient.stops, halo.stops); +} + #[test] fn a_preset_applies_to_the_stroke_and_to_a_solid_paint() { let (mut s, id) = session_with(json!({"color": "#336699"})); diff --git a/crates/engine/src/tests/swatchlib.rs b/crates/engine/src/tests/swatchlib.rs index 55ab34564a..98585d607e 100644 --- a/crates/engine/src/tests/swatchlib.rs +++ b/crates/engine/src/tests/swatchlib.rs @@ -671,6 +671,44 @@ fn organic_fades_add_with_their_method_midpoints_and_opacities_in_rgb_and_cmyk() } } +#[test] +fn holographic_glows_add_whole_in_rgb_and_cmyk_as_one_undo_step() { + let lib = vectorcraft_color::libraries::builtin_library("holographic").unwrap(); + let Paint::Gradient(want) = &lib.swatch("Mint Halo").unwrap().paint else { panic!("Mint Halo is a gradient") }; + for mode in ["rgb", "cmyk"] { + let mut s = Session::new(); + run(&mut s, "file.new", json!({"width": 100, "height": 100, "colorMode": mode})); + let list = run(&mut s, "swatch.library.list", json!({})); + let row = list["libraries"].as_array().unwrap().iter().find(|l| l["id"] == "holographic").unwrap().clone(); + assert_eq!((row["name"].as_str(), row["category"].as_str(), row["count"].as_u64()), (Some("Holographic"), Some("gradients"), Some(14))); + let got = run(&mut s, "swatch.library.get", json!({"library": "holographic"})); + assert!(got.to_string().contains("Mint Halo"), "{mode}: get lists Mint Halo"); + let id = run(&mut s, "shape.rectangle", json!({"x": 0, "y": 0, "width": 50, "height": 50}))["id"].clone(); + let node = vectorcraft_doc::NodeId(id.as_u64().unwrap()); + let before = undo_len(&s); + let r = run(&mut s, "swatch.library.add", json!({"library": "holographic", "names": ["Mint Halo"], "apply": "fill"})); + assert_eq!((r["added"].clone(), r["applied"].clone()), (json!(["Mint Halo"]), json!("Mint Halo")), "{mode}"); + assert_eq!(undo_len(&s), before + 1, "{mode}: added and applied as one step"); + // The swatch comes as the library has it: kind, stops (RGB colors, opacities, midpoints) and method. + let Paint::Gradient(added) = &doc(&s).swatch("Mint Halo").unwrap().paint else { panic!("{mode}: a gradient swatch") }; + assert_eq!(added.gradient, want.gradient, "{mode}"); + assert!(added.gradient.stops.iter().all(|st| matches!(st.color, Color::Rgb { .. })), "{mode}: the stops stay RGB"); + assert_eq!(added.gradient.stops.last().map(|st| st.opacity), Some(0.0), "{mode}: clear at the edge"); + let Paint::Gradient(fill) = doc(&s).node(node).unwrap().appearance.fill_paint() else { panic!("{mode}: a gradient fill") }; + assert_eq!((fill.gradient.kind, fill.gradient.method), (want.gradient.kind, want.gradient.method), "{mode}"); + assert_eq!(fill.gradient.stops, want.gradient.stops, "{mode}"); + run(&mut s, "edit.undo", json!({})); + assert!(doc(&s).swatch("Mint Halo").is_none(), "{mode}: undo removes the add"); + assert!(!matches!(doc(&s).node(node).unwrap().appearance.fill_paint(), Paint::Gradient(_)), "{mode}: and the fill"); + // A document swatch already named Mint Halo stays; the glow comes in under a number. + run(&mut s, "swatch.new", json!({"name": "Mint Halo", "color": "#123456"})); + let r = run(&mut s, "swatch.library.add", json!({"library": "holographic", "names": ["Mint Halo"]})); + assert_eq!(r["added"], json!(["Mint Halo 2"]), "{mode}"); + assert!(doc(&s).swatch("Mint Halo").unwrap().paint.color().is_some(), "{mode}: the document's swatch is kept"); + assert!(matches!(&doc(&s).swatch("Mint Halo 2").unwrap().paint, Paint::Gradient(g) if g.gradient == want.gradient)); + } +} + #[test] fn pattern_libraries_bring_their_patterns_and_add_each_once() { let mut s = session(); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 6b770ec1bc..1942afa9b7 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2283,6 +2283,7 @@ font-technology Variable Променливи Options: Опции: Options… Опции… Organic Fades Органични преливания + Holographic Холографски or more spaces with a tab или повече интервала с табулация Order: Ред: Ordinals Поредни номера diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index f6029f08ef..9b19437aaf 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -828,6 +828,7 @@ Sky Gradients Gradijenti neba Neutral Gradients Neutralni gradijenti Organic Fades Organski prelazi + Holographic Holografski Duotone Gradients Duoton gradijenti Radial Glows Radijalni sjajevi Save Swatch Library… Sačuvaj biblioteku uzoraka boja… diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index cf0b677271..ec43f19283 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -2918,6 +2918,7 @@ digits Malayalam Malaiàlam Options: Opcions: Options… Opcions… Organic Fades Esvaïments orgànics + Holographic Hologràfics or more spaces with a tab o més espais amb un tabulador Orange Taronja Order: Ordre: diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 9125a4f05f..321e06b088 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -837,6 +837,7 @@ Sky Gradients Přechody oblohy Neutral Gradients Neutrální přechody Organic Fades Organické přechody + Holographic Holografické Duotone Gradients Dvoutónové přechody Radial Glows Kruhové záře Save Swatch Library… Uložit knihovnu vzorníku… diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 00b46b0f22..079e0b94fd 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -774,6 +774,7 @@ Sky Gradients Himmelgradienter Neutral Gradients Neutrale gradienter Organic Fades Organiske toninger + Holographic Holografiske Duotone Gradients Duotonegradienter Radial Glows Radiale gløder Save Swatch Library… Gem bibliotek med farveprøver… diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index ec8d91ad1c..1aa6fe5c36 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2319,6 +2319,7 @@ file-filter Templates Vorlagen Options: Optionen: Options… Optionen… Organic Fades Organische Verläufe + Holographic Holografisch or more spaces with a tab oder mehr Leerzeichen durch einen Tabulator Order: Reihenfolge: Ordinals Ordinalzahlen diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 17527f5871..ef750c939b 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1842,6 +1842,7 @@ font-technology Variable Μεταβλητές Options… Επιλογές… Ordinals Τακτικοί αριθμοί Organic Fades Οργανικές διαβαθμίσεις + Holographic Ολογραφικά Orientation Προσανατολισμός Orientation: Προσανατολισμός: Original Πρωτότυπο diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index f4ea5ecf80..84b2ac15ed 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2357,6 +2357,7 @@ font-technology Variable Variables Options: Opciones: Options… Opciones… Organic Fades Desvanecidos orgánicos + Holographic Holográficos or more spaces with a tab o más espacios por una tabulación Order: Orden: Ordinals Ordinales diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index b7a34058f4..9b9b1bd925 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -828,6 +828,7 @@ Sky Gradients Taivasliukuvärit Neutral Gradients Neutraalit liukuvärit Organic Fades Orgaaniset häivytykset + Holographic Holografiset Duotone Gradients Kaksisävyliukuvärit Radial Glows Säteittäiset hehkut Save Swatch Library… Tallenna värimallikirjasto… diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 36bf674ef2..6a7c71c136 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2349,6 +2349,7 @@ font-technology Variable Variables Options: Options : Options… Options… Organic Fades Fondus organiques + Holographic Holographiques or more spaces with a tab espaces ou plus par une tabulation Order: Ordre : Ordinals Ordinaux diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 7dbeabac1e..2bbb082048 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -3174,6 +3174,7 @@ antiAlias Sharp शार्प Options: ऑप्शन्स: Options… ऑप्शन्स… Organic Fades ऑर्गेनिक फ़ेड + Holographic होलोग्राफ़िक or more spaces with a tab या एक टैब के साथ अधिक रिक्त स्थान Orange नारंगी Order: आदेश: diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index c87d42e6dc..7fd9516069 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -3175,6 +3175,7 @@ mark-style Japanese Japán Radial Blur… Körkörös elmosás… Radial Glows Körkörös ragyogások Organic Fades Organikus áttűnések + Holographic Holografikus Radial Gradient Körkörös színátmenet Conic Gradient Kúpos színátmenet Diamond Gradient Rombusz színátmenet diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 9925f7b0d8..27416fa63c 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -774,6 +774,7 @@ Sky Gradients Gradien langit Neutral Gradients Gradien netral Organic Fades Pudar organik + Holographic Holografik Duotone Gradients Gradien duotone Radial Glows Pendar radial Save Swatch Library… Simpan pustaka sampel warna… diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 36c8820296..b545d2c8fd 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2395,6 +2395,7 @@ file-filter Templates Modelli Options: Opzioni: Options… Opzioni… Organic Fades Dissolvenze organiche + Holographic Olografici or more spaces with a tab o più spazi con una tabulazione Order: Ordine: Ordinals Ordinali diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 6dcabf27e8..490c98afe5 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -860,6 +860,7 @@ digits Malayalam マラヤーラム数字 Sky Gradients 空のグラデーション Neutral Gradients ニュートラルグラデーション Organic Fades オーガニックフェード + Holographic ホログラフィック Duotone Gradients ダブルトーングラデーション Radial Glows 円形グロー Save Swatch Library… スウォッチライブラリを保存… diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 23a18557fa..4e9070fdd3 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -734,6 +734,7 @@ Sky Gradients ცის გრადიენტები Neutral Gradients ნეიტრალური გრადიენტები Organic Fades ორგანული გაქრობები + Holographic ჰოლოგრაფიული Duotone Gradients ორტონიანი გრადიენტები Radial Glows რადიალური ნათებები Save Swatch Library… ფერის ნიმუშების ბიბლიოთეკის შენახვა… diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 5366483cd9..c268835e8a 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -853,6 +853,7 @@ digits Malayalam 말라얄람어 Sky Gradients 하늘 그레이디언트 Neutral Gradients 중간색 그레이디언트 Organic Fades 유기적 페이드 + Holographic 홀로그래픽 Duotone Gradients 듀오톤 그레이디언트 Radial Glows 방사형 광선 Save Swatch Library… 견본 라이브러리 저장… diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 20ea6ac94d..03030a78be 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -788,6 +788,7 @@ Sky Gradients Luchtverlopen Neutral Gradients Neutrale verlopen Organic Fades Organische vervagingen + Holographic Holografisch Duotone Gradients Duotoonverlopen Radial Glows Radiale gloed Save Swatch Library… Staalenbibliotheek opslaan… diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 8d72f58fa4..750886c506 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -784,6 +784,7 @@ Sky Gradients Gradienty nieba Neutral Gradients Gradienty neutralne Organic Fades Organiczne zanikania + Holographic Holograficzne Duotone Gradients Gradienty dwutonowe Radial Glows Blaski promieniste Save Swatch Library… Zapisz bibliotekę próbek… diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index eb2c29947a..974b698d09 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1954,6 +1954,7 @@ antiAlias Sharp Nítido Order: Ordem: Ordinals Ordinais Organic Fades Esmaecimentos Orgânicos + Holographic Holográficos Orientation Orientação Orientation: Orientação: Original Original diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 533192c53a..eb98dbd431 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2382,6 +2382,7 @@ file-filter Templates Шаблоны Options: Параметры: Options… Параметры… Organic Fades Органические затухания + Holographic Голографические or more spaces with a tab или больше пробелов на табуляцию Order: Порядок: Ordinals Порядковые числительные diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 558238e60e..f8acf49c6e 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2296,6 +2296,7 @@ font-technology Variable Variabla Options: Alternativ: Options… Alternativ… Organic Fades Organiska toningar + Holographic Holografiska or more spaces with a tab eller fler blanksteg med en tabb Order: Ordning: Ordinals Ordningstal diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index f88726b3fc..dd003c3cc9 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2168,6 +2168,7 @@ shape Open Açık Options: Seçenekler: Options… Seçenekler… Organic Fades Organik Solmalar + Holographic Holografik or more spaces with a tab veya sekmeli daha fazla alan Order: Sipariş: Ordinals Sıra Sayıları diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 8038529844..2aa9d0bf10 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2387,6 +2387,7 @@ file-filter Templates Шаблони Options: Параметри: Options… Параметри… Organic Fades Органічні згасання + Holographic Голографічні or more spaces with a tab або більше прогалин на табуляцію Order: Порядок: Ordinals Порядкові числівники diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 6178748b4f..0b254f73ab 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2964,6 +2964,7 @@ mark-style Japanese Kiểu Nhật Radial Dividers: Đường chia hướng tâm: Radial Glows Ánh sáng hướng tâm Organic Fades Mờ dần tự nhiên + Holographic Ảnh ba chiều Radial Gradient Chuyển sắc hướng tâm Conic Gradient Chuyển sắc hình nón Diamond Gradient Chuyển sắc hình thoi diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 20ae4298b6..be6cc7ab2c 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2254,6 +2254,7 @@ file-filter Templates 模板 Options: 选项: Options… 选项… Organic Fades 自然渐隐 + Holographic 全息 or more spaces with a tab 或多个空格加一个制表符 Order: 顺序: Ordinals 序数字 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index ee57010551..5d0c0904a9 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -3354,6 +3354,7 @@ file-filter Templates 範本 Options: 選項: Options… 選項… Organic Fades 自然漸隱 + Holographic 全像 or more spaces with a tab 個以上的空格取代為定位字元 Order: 順序: Ordinals 序數字 diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index e8c9393deb..46fde93aad 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -828,6 +828,23 @@ mod gradient_chip_tests { assert!(chip.iter().all(|c| c.a() == 0), "{chip:?}"); } + /// A Holographic glow's chip is clear at the corners and nearly opaque at the center; a foil's + /// chip is opaque everywhere. + #[test] + fn holographic_chips_show_a_glow_clear_at_its_edge_and_a_foil_opaque() { + use vectorcraft_color::Paint; + let lib = vectorcraft_color::libraries::builtin_library("holographic").unwrap(); + let chip = |name: &str| match &lib.swatch(name).unwrap().paint { + Paint::Gradient(g) => gradient_chip_colors(g, 20.0, 20.0, 4, 4), + p => panic!("{name}: {p:?}"), + }; + let glow = chip("Mint Halo"); + assert!([0, 4, 20, 24].iter().all(|&i| glow[i].a() == 0), "{glow:?}"); + assert!(glow[2 * 5 + 2].a() >= 240, "{glow:?}"); + let foil = chip("Prism Veil"); + assert!(foil.iter().all(|c| c.a() == 255), "{foil:?}"); + } + #[test] fn freeform_chip_mixes_points_in_the_model_they_share() { use vectorcraft_color::{Color, Freeform, FreeformPoint}; diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 9c58bcaa8c..738039e9ae 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1288,7 +1288,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur Double-clicking the Objects on Path tool opens Objects on Path Tool Options ([Objects on Path](https://helpx.adobe.com/illustrator/using/objects-on-path.html)): the nine-point Pivot grid and the Rotate field set the attachment point and angle of the objects attached next, and groups already attached keep theirs; Reset puts the center and 0° back. Save keeps the options across tool switches and saves them with the preferences (`tool.setOption {tool: "objectsOnPath"}`). - **Paint and appearance (M3):** - **CSS swatch libraries:** fractional linear and radial gradient stop positions are preserved, so nearby stops such as 12.5% and 12.890625% remain distinct instead of collapsing to 13%. Reference: [CSS Images 3 color stops](https://www.w3.org/TR/css-images-3/#color-stop-syntax). The original synthetic regression is `palette_io::tests::css_preserves_fractional_gradient_stop_positions` in `crates/color/src/palette_io.rs`. Scores and hours are unchanged. - - **Swatches:** names follow the colour model and are unique; Swatch Options (process/spot, Global, Gray/RGB/HSB/CMYK/Web, live preview); editing a global or spot swatch recolours every linked fill, stroke and text run in one undo step; multi-select and delete with confirmation; New Swatch and New Color Group dialogs (a group from the selected artwork's colours); swatches in colour groups convert, separate and count like any other. The Swatches panel selects ranges and whole groups, has a find field, and drags swatches to reorder, regroup or paint art; Add Used Colors, Select All Unused, Merge, Ungroup and Sort by Kind; a CMYK document keeps applied colours in CMYK. Swatch libraries: nine generated colour libraries, six gradient libraries (Organic Fades among them: soft, perceptually mixed multi-stop fades that thin out to transparent) and a pattern library (Geometric Patterns, whose swatches bring their patterns) in a library panel, saved as `.vcswatches`, `.gpl`, swatch exchange (`.ase`) or CSS, and loaded from `.vcswatches`, `.gpl` and `.ase` files (`.ase` both ways: RGB, CMYK, Lab and Gray; global, spot and process; groups) and from color books (`.acb`, read only: the RGB, CMYK or Lab colors of a book such as a Pantone book a user owns, named with its prefix and suffix, spot unless it says process, #831); `swatch.library.copyToUser` copies a loaded library into the User Defined folder without replacing a file, and a file with the same extension and bytes as a User Defined library opens as that library. Tints of global and spot colours ("Name 40%") stay linked and separate as a percentage of their plate. Spot colours can be defined in Lab (Spot Colors options). A built-in [Registration] swatch prints on every plate. + - **Swatches:** names follow the colour model and are unique; Swatch Options (process/spot, Global, Gray/RGB/HSB/CMYK/Web, live preview); editing a global or spot swatch recolours every linked fill, stroke and text run in one undo step; multi-select and delete with confirmation; New Swatch and New Color Group dialogs (a group from the selected artwork's colours); swatches in colour groups convert, separate and count like any other. The Swatches panel selects ranges and whole groups, has a find field, and drags swatches to reorder, regroup or paint art; Add Used Colors, Select All Unused, Merge, Ungroup and Sort by Kind; a CMYK document keeps applied colours in CMYK. Swatch libraries: nine generated colour libraries, seven gradient libraries (among them Organic Fades, soft perceptually mixed multi-stop fades that thin out to transparent, and Holographic, seven opaque linear foils of narrow pastel and pearl bands and seven radial glows that fade out to a clear edge) and a pattern library (Geometric Patterns, whose swatches bring their patterns) in a library panel, saved as `.vcswatches`, `.gpl`, swatch exchange (`.ase`) or CSS, and loaded from `.vcswatches`, `.gpl` and `.ase` files (`.ase` both ways: RGB, CMYK, Lab and Gray; global, spot and process; groups) and from color books (`.acb`, read only: the RGB, CMYK or Lab colors of a book such as a Pantone book a user owns, named with its prefix and suffix, spot unless it says process, #831); `swatch.library.copyToUser` copies a loaded library into the User Defined folder without replacing a file, and a file with the same extension and bytes as a User Defined library opens as that library. Tints of global and spot colours ("Name 40%") stay linked and separate as a percentage of their plate. Spot colours can be defined in Lab (Spot Colors options). A built-in [Registration] swatch prints on every plate. Editing, renaming, deleting or merging a global swatch also changes the colors linked to it in graph designs, the art of brushes and the tile that pattern editing's Cancel puts back, in the same undo step as the rest of the art; Default Swatches with replace unlinks them there too. Add Used Colors (`swatch.addUsedColors`) and Select All Unused (`swatch.unused`) look colors, swatch names, links, tints and gradients up in hash sets instead of searching every swatch and color for each color or linked paint (New Color Group from artwork too), and New Swatch with several colors (`swatch.new`), New Color Group (`swatch.newGroup`) and Edit Color Group (`swatch.editGroup`) name their new swatches from one set of the names already taken. The new swatches keep their names, order and links, and Select All Unused lists the same swatches. Evidence: `add_used_colors_adds_each_new_color_once_with_free_names`, `add_used_colors_global_links_every_paint_of_a_new_color`, `unused_matches_links_tints_colors_gradients_and_patterns_exactly`, `new_swatches_from_colors_are_numbered_after_names_already_taken`, `new_color_group_numbers_its_colors_after_the_group_and_its_moved_swatches`, `new_color_group_from_artwork_lists_each_color_once_with_free_names` and `edit_color_group_numbers_extra_colors_after_the_names_it_keeps` in `crates/engine/src/tests/swatchcmds.rs`; `free_swatch_names_match_naming_each_swatch_as_it_is_added` and `free_swatch_names_match_new_swatch_names_as_swatches_are_added` in `crates/doc/src/swatches.rs`. - **Colorized images (#1466):** a fill color applied to a selected grayscale or 1-bit image (`paint.setFill`) colorizes it with black, white, a process color or a spot color, and refuses a process color when the image has an alpha channel ([Convert color modes](https://web.archive.org/web/20251220223850/https://helpx.adobe.com/illustrator/desktop/manage-colors/select-and-adjust-colors/convert-color-modes.html), "Colorize Grayscale or 1-bit images"); black and white are accepted there too. The canvas draws each pixel in the color as far as the pixel is dark, with white pixels transparent; PDF export writes the image in the color (a spot color as its ink, Registration in every ink) with the image as its soft mask; Separations Preview and printed separations show it on its color's plates; native files keep the color. SVG writes the fill color through a coverage mask (or a colorized PNG in Tiny 1.2); EPS writes binary coverage as an image mask in the color's ink, and a grayscale image as ink samples scaled by coverage, with white pixels left out at Level 3 and gray midtones written over white.