diff --git a/ROADMAP.md b/ROADMAP.md index 327036c371..bbc61c3df9 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 3ecb5e68e1..8271cfa564 100644 --- a/crates/engine/src/tests/swatchlib.rs +++ b/crates/engine/src/tests/swatchlib.rs @@ -765,6 +765,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 24f16e1794..8c3f0ad02b 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -5810,6 +5810,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 83290bdbd9..bb8ba457ed 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -5827,6 +5827,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 717c2b59d9..01d16f3d51 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -1872,6 +1872,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 216579443f..1554110e28 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -5980,6 +5980,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 2339ca27ed..07a9d73cd1 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -5825,6 +5825,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 e5ad28a0c2..4c669c24e4 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -5561,6 +5561,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 c87ecb5713..7642033e06 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -5931,6 +5931,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 db8da4cc35..5e1fdfb1c7 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -5922,6 +5922,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 c3d7fcfb91..4f9413ff4f 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -6010,6 +6010,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 e557d273c9..c81852fcdc 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -5975,6 +5975,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 129dfeea3c..3a40dc706b 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -2064,6 +2064,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 78c096b113..7de4f671c9 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -5963,6 +5963,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 002740e114..91ad56ef22 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -5825,6 +5825,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 7f230b10f4..2710544ddb 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5595,6 +5595,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 88c9013b2e..ddced9ea96 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5645,6 +5645,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 4b7dfdcac7..cd3c1ece1a 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -5920,6 +5920,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 ac3f3d0cf6..c4c32f6a67 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -5556,6 +5556,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 0622156953..ab2017cd55 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -5876,6 +5876,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 11dd0dc99a..6d5d9f8eaf 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -5834,6 +5834,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 62b2fa772a..ffa02858e2 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -5895,6 +5895,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 87c359b83b..ea39a33867 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -5589,6 +5589,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 92de527ff1..1095a88d12 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -6003,6 +6003,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 e2ec9c6851..4569d6777c 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -5975,6 +5975,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 235a4d389e..a92b04b758 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -5575,6 +5575,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 87d2c22625..2e5deb16b2 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -5576,6 +5576,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 eea8526b99..d3e0952825 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -5817,6 +5817,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 60cc2d1d43..0a9112879a 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -5613,6 +5613,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 da260b6823..8f726e5d31 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3290,8 +3290,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 18d50447ff..86cd65e34a 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1274,7 +1274,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, five gradient libraries 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, five gradient libraries 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.