Merge pull request #6796 from dotCooCoo/feat/patterns-grit-textures

Swatch libraries: Patterns › Grit Textures adds seamless grain, speckle and stipple patterns
This commit is contained in:
BFlat
2026-11-12 15:13:22 +07:00
committed by GitHub
33 changed files with 227 additions and 6 deletions
+1
View File
@@ -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).
+160 -2
View File
@@ -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<Rect> = 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");
}
}
+33
View File
@@ -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]
+1
View File
@@ -5844,6 +5844,7 @@ theme Appearance Външен вид
Ruby Руби
Geometric Guides Геометрични водачи
Geometric Patterns Геометрични мостри
Grit Textures Зърнести текстури
Ease In Плавно начало
Ease Out Плавен край
Ease In Out Плавно начало и край
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5977,6 +5977,7 @@ effect Points Σημεία
Ruby Ruby
Geometric Guides Γεωμετρικοί οδηγοί
Geometric Patterns Γεωμετρικά μοτίβα
Grit Textures Κοκκώδεις υφές
Ease In Ομαλή αρχή
Ease Out Ομαλό τέλος
Ease In Out Ομαλή αρχή και τέλος
Can't render this file because it is too large.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2073,6 +2073,7 @@ antiAlias Sharp शार्प
Gridline every ग्रिडलाइन हर
Gridline every: ग्रिडलाइन हर:
Grids In Back ग्रिड में वापस
Grit Textures दानेदार टेक्सचर
Group समूह
Group Overlapping Objects समूह ओवरलैपिंग ऑब्जेक्ट
Group Selection समूह सिलेक्शन
Can't render this file because it is too large.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2372 and column 5.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5699,6 +5699,7 @@ lighting Light ライト
Ruby ルビ
Geometric Guides 幾何ガイド
Geometric Patterns 幾何パターン
Grit Textures 粒状テクスチャ
Ease In イーズイン
Ease Out イーズアウト
Ease In Out イーズインアウト
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5966,6 +5966,7 @@ imageTrace Outline კონტური
Ruby რუბი
Geometric Guides გეომეტრიული მიმმართველები
Geometric Patterns გეომეტრიული პატერნები
Grit Textures მარცვლოვანი ტექსტურები
Ease In ნელი დასაწყისი
Ease Out ნელი დასასრული
Ease In Out ნელი დასაწყისი და დასასრული
Can't render this file because it is too large.
+1
View File
@@ -5610,6 +5610,7 @@ lighting Light 광원
Ruby 루비
Geometric Guides 기하학적 안내선
Geometric Patterns 기하학적 패턴
Grit Textures 입자 텍스처
Ease In 서서히 시작
Ease Out 서서히 종료
Ease In Out 서서히 시작 및 종료
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -5635,6 +5635,7 @@ lighting Light Свет
Ruby Руби
Geometric Guides Геометрические направляющие
Geometric Patterns Геометрические узоры
Grit Textures Зернистые текстуры
Ease In Плавное начало
Ease Out Плавное окончание
Ease In Out Плавное начало и окончание
Can't render this file because it is too large.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -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?
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -5629,6 +5629,7 @@ lighting Light Світло
Ruby Рубі
Geometric Guides Геометричні напрямні
Geometric Patterns Геометричні візерунки
Grit Textures Зернисті текстури
Ease In Плавний початок
Ease Out Плавне завершення
Ease In Out Плавний початок і завершення
Can't render this file because it contains an unexpected character in line 182 and column 5.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 204 and column 5.
+1
View File
@@ -5863,6 +5863,7 @@ lighting Light 光源
Ruby 注音
Geometric Guides 几何参考线
Geometric Patterns 几何图案
Grit Textures 颗粒纹理
Ease In 缓入
Ease Out 缓出
Ease In Out 缓入缓出
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5667,6 +5667,7 @@ lighting Light 光源
Ruby 注音
Geometric Guides 幾何參考線
Geometric Patterns 幾何圖樣
Grit Textures 顆粒紋理
Ease In 緩入
Ease Out 緩出
Ease In Out 緩入緩出
Can't render this file because it contains an unexpected character in line 6 and column 47.
@@ -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::<SwatchLibraries>(&app, "radial-glows", 1).as_deref(), Some("geometric-patterns"));
assert_eq!(neighbour::<SwatchLibraries>(&app, "geometric-patterns", 1).as_deref(), Some("grit-textures"));
assert_eq!(neighbour::<SwatchLibraries>(&app, first, -1), Some(last), "wraps around");
assert_eq!(neighbour::<SwatchLibraries>(&app, "web-safe-216", 1).as_deref(), Some(SWATCH_IDS[1]));
}
+3 -2
View File
@@ -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.
+1 -1
View File
@@ -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.