diff --git a/ROADMAP.md b/ROADMAP.md index 3e8cfb9d34..b1f75b2735 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -126,6 +126,7 @@ Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gap - 2026-11-04: New Document: the units chosen in Preset Details now belong to the chosen document, as the pane's options are listed for the selected preset; the chosen card shows them, every other Recent and Saved card keeps the units its document or preset was made in, and the built-in tabs still follow the chosen units, so switching between inches and millimetres no longer restyles every recent template. Scores and estimates unchanged. - 2026-11-02: Mouse wheel over a closed font-family field in Character, Properties or Control changes selected text through the family list using saved font filters (excluding search text), one undo step per notch, stopping at the ends (#5253). Scores and estimates unchanged. - 2026-10-31: Pattern libraries: Window › Swatch Libraries › Patterns and the Swatches panel's library menu open Geometric Patterns, a built-in library of eleven pattern swatches (dots, stripes, checks, a grid and shapes); clicking one adds its pattern and swatch to the document, once, and applies it to the active fill or stroke (`swatch.library.add`). +- 2026-11-11: Pattern libraries: Patterns › Grit Textures, a second built-in pattern library, holds eight seamless black textures (Fine Grain, Coarse Grit, Sand, Speckle, Stipple, Scratches, Sparse Flecks and Dense Flecks) on tiles of 18 to 36 pt, each drawn the same way every time; marks that reach past a tile edge continue on the next tile, so the tiles join without a seam (`swatch.library.add`). Scores and estimates unchanged. - 2026-11-01: Find Nearest Swatch joins the Color Effects add-on (Object › Plug-ins › Color Effects): for each colour of the selection, the closest swatches of the document, a built-in library or a library or colour book the user loaded, with their CIEDE2000 difference; Apply gives each colour its nearest swatch as one undo step, linking global and spot swatches and adding a library's to the document first, so a process colour becomes the closest spot colour (`addon.color.nearestSwatch`, `addon.color.applyNearestSwatch`, #4923). - 2026-11-01: Expand Dashes into Paths joins the Path Precision Tools add-on (Object › Plug-ins › Path Precision Tools): each selected path's dashed stroke becomes separate open paths, one per dash, laid out exactly as the stroke draws them (pattern, offset, Align Dashes to Corners and Path Ends), each keeping the stroke with the dashes off, so the gaps are real gaps for cutters and plotters, as one undo step (`addon.pathtools.expandDashes`, #4922). - 2026-11-01: Randomize Opacity joins the Arrange Tools add-on (Object › Plug-ins › Arrange Tools): each selected object gets its own opacity at random between a minimum and a maximum percent, with a seed that repeats the result, as one undo step (`addon.arrange.randomizeOpacity`, #4925). diff --git a/crates/engine/src/cmd/patternlib.rs b/crates/engine/src/cmd/patternlib.rs index 011dfc131c..8fd2a0ea5e 100644 --- a/crates/engine/src/cmd/patternlib.rs +++ b/crates/engine/src/cmd/patternlib.rs @@ -23,8 +23,10 @@ pub struct BuiltinPatternLibrary { } /// The built-in pattern libraries (Window → Swatch Libraries → Patterns), in menu order. -pub const PATTERN_LIBRARIES: &[BuiltinPatternLibrary] = - &[BuiltinPatternLibrary { id: "geometric-patterns", name: "Geometric Patterns", make: geometric_patterns }]; +pub const PATTERN_LIBRARIES: &[BuiltinPatternLibrary] = &[ + BuiltinPatternLibrary { id: "geometric-patterns", name: "Geometric Patterns", make: geometric_patterns }, + BuiltinPatternLibrary { id: "grit-textures", name: "Grit Textures", make: grit_textures }, +]; /// The library category of the built-in pattern libraries. pub const CATEGORY: &str = "patterns"; @@ -154,6 +156,106 @@ fn geometric_patterns(d: &mut Document) { add(d, "Zigzag", 10.0, 8.0, TileType::Grid, vec![polygon(&[(0.0, 5.0), (5.0, 1.0), (10.0, 5.0), (10.0, 7.0), (5.0, 3.0), (0.0, 7.0)])]); } +/// SplitMix64, a small generator with a fixed seed per pattern, so a grit pattern draws the same +/// marks every time it is made. +struct SplitMix64(u64); + +impl SplitMix64 { + fn next(&mut self) -> u64 { + self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut z = self.0; + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + z ^ (z >> 31) + } + + /// A number from `lo` up to, but not including, `hi`. + fn range(&mut self, lo: f64, hi: f64) -> f64 { + lo + (self.next() >> 11) as f64 / (1u64 << 53) as f64 * (hi - lo) + } +} + +/// A round dot of radius `r` at (`x`, `y`). +fn dot(x: f64, y: f64, r: f64) -> PathData { + shapes::ellipse(Rect::new(x - r, y - r, x + r, y + r)) +} + +/// An irregular fleck of four to seven corners about (`x`, `y`), at most `r` from it. +fn fleck(rng: &mut SplitMix64, x: f64, y: f64, r: f64) -> PathData { + use std::f64::consts::TAU; + let n = 4 + (rng.next() % 4) as usize; + let turn = rng.range(0.0, TAU); + let points: Vec<(f64, f64)> = (0..n) + .map(|k| { + let a = turn + TAU * (k as f64 + rng.range(-0.3, 0.3)) / n as f64; + let reach = r * rng.range(0.55, 1.0); + (x + reach * a.cos(), y + reach * a.sin()) + }) + .collect(); + polygon(&points) +} + +/// A thin straight scratch `len` long and `width` wide, centered on (`x`, `y`) at a random angle. +fn scratch(rng: &mut SplitMix64, x: f64, y: f64, len: f64, width: f64) -> PathData { + let (s, c) = rng.range(0.0, std::f64::consts::PI).sin_cos(); + let (hx, hy, wx, wy) = (c * len / 2.0, s * len / 2.0, -s * width / 2.0, c * width / 2.0); + polygon(&[(x - hx - wx, y - hy - wy), (x + hx - wx, y + hy - wy), (x + hx + wx, y + hy + wy), (x - hx + wx, y - hy + wy)]) +} + +/// A grit pattern of `count` marks scattered over a square grid tile of side `size`, drawn from +/// `seed` (`mark` draws one at a point). A mark that reaches past an edge is kept whole: the tiling +/// draws the part outside the tile on the next tile, so the tiles join without a seam. +fn grit(d: &mut Document, name: &str, size: f64, seed: u64, count: usize, mark: impl Fn(&mut SplitMix64, f64, f64) -> PathData) { + let mut rng = SplitMix64(seed); + let marks = (0..count) + .map(|_| { + let (x, y) = (rng.range(0.0, size), rng.range(0.0, size)); + mark(&mut rng, x, y) + }) + .collect(); + add(d, name, size, size, TileType::Grid, marks); +} + +/// Grit Textures: seamless black grain, grit, sand, speckle, stipple, scratch and fleck textures on +/// a clear background, each drawn from its own fixed seed. +fn grit_textures(d: &mut Document) { + grit(d, "Fine Grain", 24.0, 1, 320, |rng, x, y| dot(x, y, rng.range(0.15, 0.35))); + grit(d, "Coarse Grit", 30.0, 2, 150, |rng, x, y| { + let r = rng.range(0.45, 1.1); + fleck(rng, x, y, r) + }); + grit(d, "Sand", 24.0, 3, 280, |rng, x, y| { + let r = rng.range(0.2, 0.45); + fleck(rng, x, y, r) + }); + grit(d, "Speckle", 30.0, 4, 90, |rng, x, y| dot(x, y, rng.range(0.3, 0.9))); + // Stipple: one dot in each cell of a 12 × 12 grid, moved a little off the cell's center. + let mut rng = SplitMix64(5); + let (size, cells) = (24.0, 12); + let step = size / f64::from(cells); + let dots = (0..cells) + .flat_map(|row| (0..cells).map(move |col| (col, row))) + .map(|(col, row)| { + let x = (f64::from(col) + 0.5) * step + rng.range(-0.3, 0.3) * step; + let y = (f64::from(row) + 0.5) * step + rng.range(-0.3, 0.3) * step; + dot(x, y, rng.range(0.3, 0.45)) + }) + .collect(); + add(d, "Stipple", size, size, TileType::Grid, dots); + grit(d, "Scratches", 36.0, 6, 45, |rng, x, y| { + let (len, width) = (rng.range(2.0, 7.0), rng.range(0.15, 0.35)); + scratch(rng, x, y, len, width) + }); + grit(d, "Sparse Flecks", 36.0, 7, 32, |rng, x, y| { + let r = rng.range(0.35, 0.8); + fleck(rng, x, y, r) + }); + grit(d, "Dense Flecks", 18.0, 8, 220, |rng, x, y| { + let r = rng.range(0.2, 0.5); + fleck(rng, x, y, r) + }); +} + #[cfg(test)] mod tests { use super::*; @@ -168,4 +270,60 @@ mod tests { assert_eq!(existing_pattern(&d, &lib, "Dots").as_deref(), expected, "{other}"); } } + + fn grit_doc() -> Document { + let mut d = Document::new(100.0, 100.0); + grit_textures(&mut d); + d + } + + #[test] + fn grit_textures_draw_the_same_marks_every_time() { + let (a, b) = (grit_doc(), grit_doc()); + assert_eq!(a.patterns.len(), 8); + assert_eq!(a.patterns, b.patterns); + } + + /// Every grit tile is 18 to 36 pt with at most 600 marks, each with an area, touching the tile + /// and near it. Marks reach past the edges, and the tiling draws a tile's art on the next tile + /// across each edge a mark crosses. No mark is another mark moved by a whole tile, which the tiling would draw + /// twice in one place. + #[test] + fn grit_tiles_join_without_a_seam() { + let mut crossing = 0; + for p in &grit_doc().patterns { + let (w, h) = (p.width(), p.height()); + assert!((18.0..=36.0).contains(&w) && w == h && p.tile.x0 == 0.0 && p.tile.y0 == 0.0, "{}", p.name); + assert!(!p.art.is_empty() && p.art.len() <= 600, "{}: {} marks", p.name, p.art.len()); + let bounds: Vec = p + .art + .iter() + .map(|n| match &n.kind { + vectorcraft_doc::NodeKind::Path { path, .. } => path.bounds().unwrap_or_else(|| panic!("{}: an empty mark", p.name)), + _ => panic!("{}: a mark that is not a path", p.name), + }) + .collect(); + let near = |a: &Rect, b: Rect| { + (a.x0 - b.x0).abs() < 1e-9 && (a.y0 - b.y0).abs() < 1e-9 && (a.x1 - b.x1).abs() < 1e-9 && (a.y1 - b.y1).abs() < 1e-9 + }; + let band = p.tile.inflate(w / 4.0, h / 4.0); + for b in &bounds { + assert!(b.width() > 0.0 && b.height() > 0.0, "{}: {b:?}", p.name); + assert!(b.x1 > 0.0 && b.y1 > 0.0 && b.x0 < w && b.y0 < h, "{}: {b:?} does not touch the tile", p.name); + assert!(b.x0 >= band.x0 && b.y0 >= band.y0 && b.x1 <= band.x1 && b.y1 <= band.y1, "{}: {b:?} is too far out", p.name); + // The tile past each edge the mark crosses draws this tile's art. + let (dx, dy) = (f64::from(i8::from(b.x1 > w) - i8::from(b.x0 < 0.0)), f64::from(i8::from(b.y1 > h) - i8::from(b.y0 < 0.0))); + for (x, y) in [(dx, 0.0), (0.0, dy), (dx, dy)] { + let next = p.tile + kurbo::Vec2::new(x * w, y * h); + assert!(p.offsets_covering(next).contains(&kurbo::Vec2::ZERO), "{}: {b:?} is cut off at {next:?}", p.name); + } + crossing += usize::from(dx != 0.0 || dy != 0.0); + for (dx, dy) in [(w, 0.0), (-w, 0.0), (0.0, h), (0.0, -h), (w, h), (w, -h), (-w, h), (-w, -h)] { + let moved = *b + kurbo::Vec2::new(dx, dy); + assert!(!bounds.iter().any(|o| near(o, moved)), "{}: {b:?} is drawn twice by the tiling", p.name); + } + } + } + assert!(crossing > 0, "no mark reaches past an edge"); + } } diff --git a/crates/engine/src/tests/swatchlib.rs b/crates/engine/src/tests/swatchlib.rs index 55ab34564a..57d6c23d06 100644 --- a/crates/engine/src/tests/swatchlib.rs +++ b/crates/engine/src/tests/swatchlib.rs @@ -798,6 +798,39 @@ fn a_pattern_library_adds_only_the_picked_pattern_and_it_undoes_and_saves() { assert_eq!(back.swatch("Checkerboard").map(|w| &w.paint), doc(&s).swatch("Checkerboard").map(|w| &w.paint)); } +/// Grit Textures is listed after Geometric Patterns. One of its patterns comes in with its swatch +/// once, as one undo step that also takes both away, and both stay after a save and reopen. +#[test] +fn a_grit_texture_adds_once_undoes_and_saves() { + let mut s = session(); + let list = run(&mut s, "swatch.library.list", json!({})); + let ids: Vec<&str> = + list["libraries"].as_array().unwrap().iter().filter(|l| l["category"] == "patterns").map(|l| l["id"].as_str().unwrap()).collect(); + assert_eq!(ids, ["geometric-patterns", "grit-textures"]); + let got = run(&mut s, "swatch.library.get", json!({"library": "Grit Textures"})); + let names: Vec<&str> = got["swatches"].as_array().unwrap().iter().map(|w| w["name"].as_str().unwrap()).collect(); + assert_eq!(names, ["Fine Grain", "Coarse Grit", "Sand", "Speckle", "Stipple", "Scratches", "Sparse Flecks", "Dense Flecks"]); + let counts = |d: &vectorcraft_doc::Document| (d.patterns.len(), d.swatches_iter().count()); + let empty = counts(doc(&s)); + let undo = undo_len(&s); + let r = run(&mut s, "swatch.library.add", json!({"library": "grit-textures", "names": ["Stipple"]})); + assert_eq!(r["added"], json!(["Stipple"])); + assert_eq!((counts(doc(&s)), undo_len(&s)), ((empty.0 + 1, empty.1 + 1), undo + 1)); + let def = doc(&s).pattern("Stipple").expect("the pattern comes with its swatch").clone(); + assert!(def.art.len() >= 144, "{} marks", def.art.len()); + assert!(matches!(doc(&s).swatch("Stipple").map(|w| &w.paint), Some(Paint::Pattern { pattern, .. }) if pattern == "Stipple")); + let r = run(&mut s, "swatch.library.add", json!({"library": "grit-textures", "names": ["Stipple"]})); + assert_eq!((r["added"].clone(), r["existing"].clone()), (json!([]), json!(["Stipple"]))); + assert_eq!((counts(doc(&s)), undo_len(&s)), ((empty.0 + 1, empty.1 + 1), undo + 1), "added once"); + run(&mut s, "edit.undo", json!({})); + assert_eq!(counts(doc(&s)), empty); + assert!(doc(&s).pattern("Stipple").is_none() && doc(&s).swatch("Stipple").is_none()); + run(&mut s, "edit.redo", json!({})); + let back = vectorcraft_format::load(&vectorcraft_format::save(doc(&s), false)).unwrap(); + assert_eq!(back.pattern("Stipple"), Some(&def)); + assert_eq!(back.swatch("Stipple").map(|w| &w.paint), doc(&s).swatch("Stipple").map(|w| &w.paint)); +} + /// A device name is saved with a `-` after it, which can be another library's own name: neither /// library replaces the other, and saving either again replaces only itself. #[test] diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 57c345505a..2c7632032c 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -5844,6 +5844,7 @@ theme Appearance Външен вид Ruby Руби Geometric Guides Геометрични водачи Geometric Patterns Геометрични мостри + Grit Textures Зърнести текстури Ease In Плавно начало Ease Out Плавен край Ease In Out Плавно начало и край diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index e28312f750..4068608d55 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -5861,6 +5861,7 @@ mark-style Japanese Japanski Ruby Ruby Geometric Guides Geometrijske vodilice Geometric Patterns Geometrijski uzorci + Grit Textures Zrnaste teksture Ease In Ublaži ulaz Ease Out Ublaži izlaz Ease In Out Ublaži ulaz i izlaz diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index f3d701139c..92c8ee72f7 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -1882,6 +1882,7 @@ digits Malayalam Malaiàlam Gridline every Línia de quadrícula cada Gridline every: Línia de quadrícula cada: Grids In Back Quadrícules al darrere + Grit Textures Textures granulades Group Agrupa Group Selection Selecció de grup Group Selection Tool Eina de selecció de grup diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index d61db843e8..f82b69c364 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -6026,6 +6026,7 @@ mark-style Japanese Japonské Ruby Ruby Geometric Guides Geometrická vodítka Geometric Patterns Geometrické vzorky + Grit Textures Zrnité textury Ease In Pozvolný začátek Ease Out Pozvolný konec Ease In Out Pozvolný začátek a konec diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 64c8e60dad..3b4d8b7dd1 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -5859,6 +5859,7 @@ swatch Bright Violet Kraftig violet Ruby Ruby Geometric Guides Geometriske hjælpelinjer Geometric Patterns Geometriske mønstre + Grit Textures Kornede teksturer Ease In Langsom start Ease Out Langsom slutning Ease In Out Langsom start og slutning diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 7c2f446ff4..c06f55f1f7 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -5615,6 +5615,7 @@ effect Soft Weich Ruby Ruby Geometric Guides Geometrische Hilfslinien Geometric Patterns Geometrische Muster + Grit Textures Körnige Texturen Ease In Langsam starten Ease Out Langsam enden Ease In Out Langsam starten und enden diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 1dc7281b2c..b35359e5a9 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -5977,6 +5977,7 @@ effect Points Σημεία Ruby Ruby Geometric Guides Γεωμετρικοί οδηγοί Geometric Patterns Γεωμετρικά μοτίβα + Grit Textures Κοκκώδεις υφές Ease In Ομαλή αρχή Ease Out Ομαλό τέλος Ease In Out Ομαλή αρχή και τέλος diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 40dc19cf8f..1625e212b4 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -5968,6 +5968,7 @@ mark-style Japanese Japonesas Ruby Ruby Geometric Guides Guías geométricas Geometric Patterns Motivos geométricos + Grit Textures Texturas granuladas Ease In Entrada suave Ease Out Salida suave Ease In Out Entrada y salida suaves diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 7d05378fa4..9222b7a476 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -6056,6 +6056,7 @@ swatch Bright Violet Kirkas violetti Ruby Ruby Geometric Guides Geometriset apuviivat Geometric Patterns Geometriset kuviot + Grit Textures Rakeiset tekstuurit Ease In Hidas alku Ease Out Hidas loppu Ease In Out Hidas alku ja loppu diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index daa43db14e..751d14e475 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -6021,6 +6021,7 @@ theme Appearance Apparence Ruby Ruby Geometric Guides Repères géométriques Geometric Patterns Motifs géométriques + Grit Textures Textures granuleuses Ease In Accélération progressive Ease Out Décélération progressive Ease In Out Accélération et décélération diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 6a21fb024d..281352f29e 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -2073,6 +2073,7 @@ antiAlias Sharp शार्प Gridline every ग्रिडलाइन हर Gridline every: ग्रिडलाइन हर: Grids In Back ग्रिड में वापस + Grit Textures दानेदार टेक्सचर Group समूह Group Overlapping Objects समूह ओवरलैपिंग ऑब्जेक्ट Group Selection समूह सिलेक्शन diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 5c7abf4b52..312261e069 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -6009,6 +6009,7 @@ imageTrace Outline Körvonal Ruby Ruby Geometric Guides Geometriai segédvonalak Geometric Patterns Geometriai mintázatok + Grit Textures Szemcsés textúrák Ease In Lassú kezdés Ease Out Lassú befejezés Ease In Out Lassú kezdés és befejezés diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 2be4052fc1..cbfcba5b4b 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -5859,6 +5859,7 @@ swatch Bright Violet Violet cerah Ruby Ruby Geometric Guides Panduan Geometris Geometric Patterns Pola Geometris + Grit Textures Tekstur Butiran Ease In Awal Lambat Ease Out Akhir Lambat Ease In Out Awal dan Akhir Lambat diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 8cf28b15d0..ea0b33b050 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5649,6 +5649,7 @@ lighting Light Luce Ruby Ruby Geometric Guides Guide geometriche Geometric Patterns Motivi geometrici + Grit Textures Texture granulose Ease In Inizio graduale Ease Out Fine graduale Ease In Out Inizio e fine graduali diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 19c379a84c..a9ecf561ba 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5699,6 +5699,7 @@ lighting Light ライト Ruby ルビ Geometric Guides 幾何ガイド Geometric Patterns 幾何パターン + Grit Textures 粒状テクスチャ Ease In イーズイン Ease Out イーズアウト Ease In Out イーズインアウト diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 4ece97cb96..db1b6dc4bd 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -5966,6 +5966,7 @@ imageTrace Outline კონტური Ruby რუბი Geometric Guides გეომეტრიული მიმმართველები Geometric Patterns გეომეტრიული პატერნები + Grit Textures მარცვლოვანი ტექსტურები Ease In ნელი დასაწყისი Ease Out ნელი დასასრული Ease In Out ნელი დასაწყისი და დასასრული diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index e8849fb1a8..673e289635 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -5610,6 +5610,7 @@ lighting Light 광원 Ruby 루비 Geometric Guides 기하학적 안내선 Geometric Patterns 기하학적 패턴 + Grit Textures 입자 텍스처 Ease In 서서히 시작 Ease Out 서서히 종료 Ease In Out 서서히 시작 및 종료 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 9d71bc2932..893a715b9d 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -5910,6 +5910,7 @@ swatch Bright Violet Felviolet Ruby Ruby Geometric Guides Geometrische hulplijnen Geometric Patterns Geometrische patronen + Grit Textures Korrelige texturen Ease In Langzaam beginnen Ease Out Langzaam eindigen Ease In Out Langzaam beginnen en eindigen diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index e3d39e33e0..5d06e8ac14 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -5880,6 +5880,7 @@ mark-style Japanese Japońskie Ruby Ruby Geometric Guides Geometryczne linie pomocnicze Geometric Patterns Wzorki geometryczne + Grit Textures Tekstury ziarniste Ease In Łagodny początek Ease Out Łagodny koniec Ease In Out Łagodny początek i koniec diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index e870f39fcb..6714865899 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -5941,6 +5941,7 @@ mark-style Japanese Japonesas Ruby Ruby Geometric Guides Guias geométricas Geometric Patterns Padrões geométricos + Grit Textures Texturas granuladas Ease In Entrada suave Ease Out Saída suave Ease In Out Entrada e saída suaves diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 20baacc5f5..0a96cd5243 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -5635,6 +5635,7 @@ lighting Light Свет Ruby Руби Geometric Guides Геометрические направляющие Geometric Patterns Геометрические узоры + Grit Textures Зернистые текстуры Ease In Плавное начало Ease Out Плавное окончание Ease In Out Плавное начало и окончание diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 3eca0248c5..1ea5962866 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -6048,6 +6048,7 @@ font-script Japanese Japanska Ruby Ruby Geometric Guides Geometriska stödlinjer Geometric Patterns Geometriska mönster + Grit Textures Korniga texturer Ease In Långsam start Ease Out Långsamt slut Ease In Out Långsam start och slut diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 45ae89ed7f..f8951a65ba 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -6021,6 +6021,7 @@ axis Y Axis Y ekseni Snap to Perpendicular Dikeye Yasla Geometric Guides Geometrik Kılavuzlar Geometric Patterns Geometrik Desenler + Grit Textures Grenli Dokular @msg `method` must be classic or perceptual `method` classic veya perceptual olmalıdır # Heavy plug-in filters (plugin.inspect, plug-in runs) Install “{name}”, which asks for a heavy budget? Büyük bir hesaplama bütçesi isteyen “{name}” yüklensin mi? diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 9801460d15..053cff5bf3 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -5629,6 +5629,7 @@ lighting Light Світло Ruby Рубі Geometric Guides Геометричні напрямні Geometric Patterns Геометричні візерунки + Grit Textures Зернисті текстури Ease In Плавний початок Ease Out Плавне завершення Ease In Out Плавний початок і завершення diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 1a642bb616..9acef06d82 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -5630,6 +5630,7 @@ theme Appearance Giao diện Ruby Ruby Geometric Guides Đường dẫn hình học Geometric Patterns Họa tiết hình học + Grit Textures Kết cấu hạt Ease In Chậm lúc đầu Ease Out Chậm lúc cuối Ease In Out Chậm lúc đầu và cuối diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 0cd303e144..b5c751c202 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -5863,6 +5863,7 @@ lighting Light 光源 Ruby 注音 Geometric Guides 几何参考线 Geometric Patterns 几何图案 + Grit Textures 颗粒纹理 Ease In 缓入 Ease Out 缓出 Ease In Out 缓入缓出 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 5ddd837a9f..169f070e52 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -5667,6 +5667,7 @@ lighting Light 光源 Ruby 注音 Geometric Guides 幾何參考線 Geometric Patterns 幾何圖樣 + Grit Textures 顆粒紋理 Ease In 緩入 Ease Out 緩出 Ease In Out 緩入緩出 diff --git a/crates/ui-egui/src/panels/library_panel/tests.rs b/crates/ui-egui/src/panels/library_panel/tests.rs index 31976746e3..f28e7e5540 100644 --- a/crates/ui-egui/src/panels/library_panel/tests.rs +++ b/crates/ui-egui/src/panels/library_panel/tests.rs @@ -155,8 +155,9 @@ fn find_filters_list_view_and_arrows_step_through_the_libraries() { assert!(ctx.read_response(tile_id(panel, "#00FF00")).is_none()); let first = SWATCH_IDS[0]; let last = SwatchLibraries::list(&app).pop().unwrap().id; - assert_eq!(last, "geometric-patterns", "gradient and then pattern libraries come after the swatch libraries"); + assert_eq!(last, "grit-textures", "gradient and then pattern libraries come after the swatch libraries"); assert_eq!(neighbour::(&app, "radial-glows", 1).as_deref(), Some("geometric-patterns")); + assert_eq!(neighbour::(&app, "geometric-patterns", 1).as_deref(), Some("grit-textures")); assert_eq!(neighbour::(&app, first, -1), Some(last), "wraps around"); assert_eq!(neighbour::(&app, "web-safe-216", 1).as_deref(), Some(SWATCH_IDS[1])); } diff --git a/docs/mcp.md b/docs/mcp.md index 8f264e1cb5..1c5f9e3187 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3352,8 +3352,9 @@ in the `swatch.list` shape (`library` is an id or a name). `swatch.library.add { into the document as one undo step: a colour group's name brings the whole group, a swatch's name the swatch alone, no names the whole library; swatches the document already has (same name and paint) are reported under `existing` and not added again. `apply: "fill"|"stroke"` also applies the first one, in the same undo step (what clicking a -swatch in the library panel does). The pattern library Geometric Patterns (`geometric-patterns`, category -`patterns`) holds pattern swatches drawn in code; adding one also adds its pattern, as pasting art painted with it does +swatch in the library panel does). The pattern libraries Geometric Patterns (`geometric-patterns`) and Grit Textures +(`grit-textures`: seamless black grain, grit, sand, speckle, stipple, scratch and fleck tiles), category `patterns`, +hold pattern swatches drawn in code; adding one also adds its pattern, as pasting art painted with it does (the same pattern the document has is reused, and a name another pattern or swatch has gets a number). `swatch.resetDefaults {replace?}` brings back the missing default swatches. diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 02cdb1babe..707cc7f5bb 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1292,7 +1292,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, six gradient libraries (Organic Fades among them: soft, perceptually mixed multi-stop fades that thin out to transparent) and two pattern libraries (Geometric Patterns, and Grit Textures with eight seamless grain, speckle, stipple, scratch and fleck tiles; their 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.