Merge pull request #3056 from nrq6993-a11y/stream/vc-feat1-g5-art-brush-overlap

G5: Art brush Overlap option in Brush Options
This commit is contained in:
BFlat
2026-10-20 05:17:39 +07:00
committed by GitHub
28 changed files with 106 additions and 23 deletions
+3
View File
@@ -287,6 +287,8 @@ pub struct ArtBrush {
pub width: f64,
pub flip_along: bool,
pub flip_across: bool,
/// When true, art is placed on each run between sharp corners so folds do not overlap.
pub overlap: bool,
pub colorization: Colorization,
}
@@ -304,6 +306,7 @@ impl Default for ArtBrush {
width: 100.0,
flip_along: false,
flip_across: false,
overlap: false,
colorization: Colorization::Tints,
}
}
+22
View File
@@ -152,6 +152,28 @@ fn art_brush_between_guides_keeps_ends() {
assert!(near(h.x0, 280.0, 1e-6) && near(h.x1, 300.0, 1e-6), "{h:?}");
}
#[test]
fn art_brush_overlap_splits_at_corners() {
let mut bp = BezPath::new();
bp.move_to((0.0, 0.0));
bp.line_to((100.0, 0.0));
bp.line_to((100.0, 100.0));
let brush = |overlap: bool| Brush {
name: "a".into(),
kind: BrushKind::Art(ArtBrush {
art: rect_art(10.0, 4.0),
scale: ArtScale::Stretch,
overlap,
colorization: Colorization::None,
..Default::default()
}),
};
let one = stroke_pieces(&Document::new(1.0, 1.0), &brush(false), &bp, &stroke(1.0));
let two = stroke_pieces(&Document::new(1.0, 1.0), &brush(true), &bp, &stroke(1.0));
assert_eq!(one.len(), 1, "one bent copy along the whole path");
assert_eq!(two.len(), 2, "one copy per run between corners");
}
#[test]
fn art_brush_follows_curves() {
let bp = shapes::ellipse(Rect::new(0.0, 0.0, 200.0, 200.0)).to_bezpath();
+51 -21
View File
@@ -154,35 +154,65 @@ fn bend(t: &Track, art: &Node, b: Rect, along: AlongMap, cross: f64) -> Option<N
warp_node(art, &f, flat_tol, MAX_SEG / along.max_scale(), cross.abs())
}
/// Arc-length spans to place art on when [`ArtBrush::overlap`] is on: one run between each sharp
/// corner (the whole path when it has none).
fn art_runs(t: &Track) -> Vec<(f64, f64)> {
let l = t.len();
let corners = t.corner_positions();
if corners.is_empty() {
return vec![(0.0, l)];
}
if t.closed {
let first = corners[0].1;
let mut bounds = corners.iter().map(|c| c.1).collect::<Vec<_>>();
bounds.push(first + l);
return bounds.windows(2).map(|w| (w[0], w[1])).collect();
}
let mut bounds = vec![0.0];
bounds.extend(corners.iter().map(|c| c.1));
bounds.push(l);
bounds.windows(2).map(|w| (w[0], w[1])).collect()
}
fn art_on_run(a: &ArtBrush, t: &Track, s0: f64, s1: f64, art: &Node, b: Rect, cross: f64) -> Option<Node> {
let l = s1 - s0;
if l <= 1e-9 {
return None;
}
let w = b.width();
let (along, cross) = match a.scale {
ArtScale::Stretch => (AlongMap::linear(w, s0, s1), cross),
ArtScale::Proportional => {
let k = l / w;
(AlongMap::linear(w, s0, s1), k * a.width / 100.0)
}
ArtScale::BetweenGuides { start, end } => {
let (g0, g1) = (start.clamp(0.0, 1.0) * w, end.clamp(0.0, 1.0) * w);
let (g0, g1) = (g0.min(g1), g0.max(g1));
let fixed = (g0 + (w - g1)) * cross;
if fixed >= l || g1 - g0 < 1e-9 {
(AlongMap::linear(w, s0, s1), cross)
} else {
(AlongMap { xs: [0.0, g0, g1, w], ss: [s0, s0 + g0 * cross, s1 - (w - g1) * cross, s1] }, cross)
}
}
};
bend(t, art, b, along, cross)
}
pub(crate) fn art(a: &ArtBrush, bp: &BezPath, weight: f64) -> Vec<Node> {
let Some((art, b)) = oriented(&a.art, a.direction, a.flip_along, a.flip_across) else { return vec![] };
let cross = weight * a.width / 100.0;
let mut out = vec![];
for t in tracks(bp, 0.05) {
let l = t.len();
if l <= 1e-9 {
if t.len() <= 1e-9 {
continue;
}
let w = b.width();
let (along, cross) = match a.scale {
ArtScale::Stretch => (AlongMap::linear(w, 0.0, l), cross),
ArtScale::Proportional => {
let k = l / w;
(AlongMap::linear(w, 0.0, l), k * a.width / 100.0)
let runs = if a.overlap { art_runs(&t) } else { vec![(0.0, t.len())] };
for (s0, s1) in runs {
if let Some(n) = art_on_run(a, &t, s0, s1, &art, b, cross) {
out.push(n);
}
ArtScale::BetweenGuides { start, end } => {
let (g0, g1) = (start.clamp(0.0, 1.0) * w, end.clamp(0.0, 1.0) * w);
let (g0, g1) = (g0.min(g1), g0.max(g1));
let fixed = (g0 + (w - g1)) * cross;
if fixed >= l || g1 - g0 < 1e-9 {
(AlongMap::linear(w, 0.0, l), cross)
} else {
(AlongMap { xs: [0.0, g0, g1, w], ss: [0.0, g0 * cross, l - (w - g1) * cross, l] }, cross)
}
}
};
if let Some(n) = bend(&t, &art, b, along, cross) {
out.push(n);
}
}
out
+1 -1
View File
@@ -89,7 +89,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Brush Options…",
[],
None,
"{name, params?: {…fields to change (brush.get shows them; values outside their ranges are clamped). calligraphic: angle −180..180°, roundness 0..100%, size 0..1296 pt, variation: [angle°, roundness%, size pt], modes: [fixed|random|pressure for angle, roundness, size] (pressure goes from value − variation at the lightest pen pressure to value + variation at the heaviest). scatter: size [a, b] 1..10000%, spacing [a, b] 1..10000%, scatter [a, b] −1000..1000%, rotation [a, b] −180..180°, modes: [fixed|random|pressure for size, spacing, scatter, rotation] (fixed: a; random: between a and b; pressure: a at the lightest pen pressure to b at the heaviest), rotation_relative_to_path. art: width 1..1000%, scale: {mode: proportional|stretch|betweenGuides, start, end: 0..1 of the art length}, direction: leftToRight|rightToLeft|topToBottom|bottomToTop, flip_along, flip_across. pattern: scale 1..10000%, spacing 0..10000%, fit: stretch|addSpace|approximate, flip_along, flip_across. scatter, art and pattern: colorization: {method: none|tints|tintsAndShades|hueShift, key: colour (hueShift: the art colour that becomes the stroke colour)}}, newName? (trimmed; blank: the name is kept)} edit a brush definition (strokes using it update) → {name}",
"{name, params?: {…fields to change (brush.get shows them; values outside their ranges are clamped). calligraphic: angle −180..180°, roundness 0..100%, size 0..1296 pt, variation: [angle°, roundness%, size pt], modes: [fixed|random|pressure for angle, roundness, size] (pressure goes from value − variation at the lightest pen pressure to value + variation at the heaviest). scatter: size [a, b] 1..10000%, spacing [a, b] 1..10000%, scatter [a, b] −1000..1000%, rotation [a, b] −180..180°, modes: [fixed|random|pressure for size, spacing, scatter, rotation] (fixed: a; random: between a and b; pressure: a at the lightest pen pressure to b at the heaviest), rotation_relative_to_path. art: width 1..1000%, scale: {mode: proportional|stretch|betweenGuides, start, end: 0..1 of the art length}, direction: leftToRight|rightToLeft|topToBottom|bottomToTop, flip_along, flip_across, overlap. pattern: scale 1..10000%, spacing 0..10000%, fit: stretch|addSpace|approximate, flip_along, flip_across. scatter, art and pattern: colorization: {method: none|tints|tintsAndShades|hueShift, key: colour (hueShift: the art colour that becomes the stroke colour)}}, newName? (trimmed; blank: the name is kept)} edit a brush definition (strokes using it update) → {name}",
has_doc,
brush_options
),
@@ -8,7 +8,8 @@
//! Scatter: per value (`size`, `spacing`, `scatter` %, `rotation` °) `…Min`, `…Max` and `…Mode`
//! (`fixed` | `random` | `pressure`), `rotationRelativeTo` (`page` | `path`).
//! Art: `width` (%), `scaleMode` (`proportional` | `stretch` | `betweenGuides`), `guideStart` and
//! `guideEnd` (% of the art's length), `direction` (`leftToRight` …), `flipAlong`, `flipAcross`.
//! `guideEnd` (% of the art's length), `direction` (`leftToRight` …), `flipAlong`, `flipAcross`,
//! `overlap` (bend on each run between sharp corners).
//! Pattern: `scale`, `spacing` (%), `fit` (`stretch` | `addSpace` | `approximate`), `flipAlong`,
//! `flipAcross`, `__tiles` (slot → preview key, for the tiles the brush has).
@@ -96,6 +97,7 @@ pub(super) fn fields(b: &Brush, def: Value, f: &mut Map<String, Value>) {
f.insert("direction".into(), json!(id_of(&a.direction)));
f.insert("flipAlong".into(), json!(a.flip_along));
f.insert("flipAcross".into(), json!(a.flip_across));
f.insert("overlap".into(), json!(a.overlap));
}
BrushKind::Pattern(p) => {
f.insert("scale".into(), json!(p.scale));
@@ -168,6 +170,7 @@ pub(super) fn params(d: &Dialog) -> Value {
"direction": id_of(&parse_field::<Direction>(d, "direction")),
"flip_along": d.bool("flipAlong"),
"flip_across": d.bool("flipAcross"),
"overlap": d.bool("overlap"),
})
}
_ => json!({
@@ -284,6 +287,8 @@ fn art_body(ui: &mut egui::Ui, d: &mut Dialog) {
}
});
flips(ui, d);
ui.add_space(4.0);
super::form::check(ui, d, "overlap", tl!("Overlap"));
}
/// A toggle button showing an arrow pointing the way `dir` runs. Whether it was clicked.
+1
View File
@@ -2112,6 +2112,7 @@
Output White Изходно бяло
Output › Mode Изход › Режим
Output: Изход:
Overlap Застъпване
Overlap: Застъпване:
Overlapping Застъпващи се
Overlay Прекриване
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1788,6 +1788,7 @@ lighting Light Světlo
Left in Front Levý vpředu
No document Žádný dokument
Open a document to edit its patterns. Otevřete dokument, abyste mohli upravit jeho vzorky.
Overlap Překrytí
Overlap: Překrytí:
Pattern options are available in pattern editing mode. Možnosti vzorku jsou dostupné v režimu úprav vzorku.
Right in Front Pravý vpředu
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1607,6 +1607,7 @@ lighting Light Lys
Left in Front Venstre forrest
No document Intet dokument
Open a document to edit its patterns. Åbn et dokument for at redigere dets mønstre.
Overlap Overlap
Overlap: Overlap:
Pattern options are available in pattern editing mode. Mønsterindstillinger er tilgængelige i mønsterredigeringstilstand.
Right in Front Højre forrest
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2130,6 +2130,7 @@ file-filter Templates Vorlagen
Output White Ausgabe Weiß
Output › Mode Ausgabe › Modus
Output: Ausgabe:
Overlap Überlappung
Overlap: Überlappung:
Overlapping Überlappend
Overlay Ineinanderkopieren
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2771,6 +2771,7 @@ theme Appearance Εμφάνιση
Output intent profile: Προφίλ προορισμού εξόδου:
Output White Λευκό εξόδου
Output › Mode Έξοδος › Λειτουργία
Overlap Επικάλυψη
Overlap: Επικάλυψη:
Overlapping Επικαλυπτόμενα
Overprint Υπερεκτύπωση
Can't render this file because it contains an unexpected character in line 2016 and column 19.
+1
View File
@@ -2155,6 +2155,7 @@
Output White Blanco de salida
Output › Mode Salida › Modo
Output: Salida:
Overlap Superposición
Overlap: Superposición:
Overlapping Superpuesto
Overlay Superponer
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1751,6 +1751,7 @@ lighting Light Valo
Left in Front Vasen edessä
No document Ei asiakirjaa
Open a document to edit its patterns. Avaa asiakirja, niin voit muokata sen kuvioita.
Overlap Limitys
Overlap: Limitys:
Pattern options are available in pattern editing mode. Kuvioasetukset ovat käytettävissä kuvion muokkaustilassa.
Right in Front Oikea edessä
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2152,6 +2152,7 @@
Output White Blanc de sortie
Output › Mode Sortie › Mode
Output: Sortie :
Overlap Chevauchement
Overlap: Chevauchement :
Overlapping Superposés
Overlay Incrustation
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2640,6 +2640,7 @@ file-filter Templates Sablonok
Output › Mode Kimenet › Mód
Output: Kimenet:
Oval Ovális
Overlap Átfedés
Overlap: Átfedés:
Overlapping Átfedő
Overlay Átfedés
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1607,6 +1607,7 @@ lighting Light Cahaya
Left in Front Kiri di depan
No document Tidak ada dokumen
Open a document to edit its patterns. Buka dokumen untuk mengedit polanya.
Overlap Tumpang tindih
Overlap: Tumpang tindih:
Pattern options are available in pattern editing mode. Opsi pola tersedia dalam mode pengeditan pola.
Right in Front Kanan di depan
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
1607
1608
1609
1610
1611
1612
1613
+1
View File
@@ -2181,6 +2181,7 @@ file-filter Templates Modelli
Output White Bianco di output
Output › Mode Output › Modalità
Output: Output:
Overlap Sovrapposizione
Overlap: Sovrapposizione:
Overlapping Sovrapposti
Overlay Sovrapponi
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2245,6 +2245,7 @@
Output condition: 出力条件:
Output intent profile: 出力インテントプロファイル:
Output: 出力:
Overlap 重なり
Overlap: 重なり:
Overlapping 重なり
Overlay オーバーレイ
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1509,6 +1509,7 @@ lighting Light სინათლე
Left in Front მარცხენა წინ
No document დოკუმენტი არ არის
Open a document to edit its patterns. გახსენით დოკუმენტი მისი პატერნების რედაქტირებისთვის.
Overlap გადაფარვა
Overlap: გადაფარვა:
Pattern options are available in pattern editing mode. პატერნის პარამეტრები ხელმისაწვდომია პატერნის რედაქტირების რეჟიმში.
Right in Front მარჯვენა წინ
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2240,6 +2240,7 @@
Output condition: 출력 조건:
Output intent profile: 출력 의도 프로파일:
Output: 출력:
Overlap 겹침
Overlap: 겹침:
Overlapping 겹침
Overlay 오버레이
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1621,6 +1621,7 @@ lighting Light Licht
Left in Front Linkerkant vooraan
No document Geen document
Open a document to edit its patterns. Open een document om de patronen te bewerken.
Overlap Overlap
Overlap: Overlap:
Pattern options are available in pattern editing mode. Patroonopties zijn beschikbaar in de patroonbewerkingsmodus.
Right in Front Rechterkant vooraan
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1708,6 +1708,7 @@ lighting Light Światło
Left in Front Lewy na wierzchu
No document Brak dokumentu
Open a document to edit its patterns. Otwórz dokument, aby edytować jego wzorki.
Overlap Nakładanie
Overlap: Nakładanie:
Pattern options are available in pattern editing mode. Opcje wzorka są dostępne w trybie edycji wzorka.
Right in Front Prawy na wierzchu
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1770,6 +1770,7 @@
Output condition: Condição de saída:
Output intent profile: Perfil de intenção de saída:
Output: Saída:
Overlap Sobreposição
Overlap: Sobreposição:
Overlapping Sobreposto
Overlay Sobrepor
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -2177,6 +2177,7 @@ file-filter Templates Шаблоны
Output White Выходной белый
Output › Mode Вывод › Режим
Output: Вывод:
Overlap Перекрытие
Overlap: Перекрытие:
Overlapping Перекрывающийся
Overlay Перекрытие
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2112,6 +2112,7 @@
Output White Vitt i utgång
Output › Mode Utgång › Läge
Output: Utgång:
Overlap Överlappning
Overlap: Överlappning:
Overlapping Överlappande
Overlay Överlägg
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -2173,6 +2173,7 @@ file-filter Templates Шаблони
Output White Вихідний білий
Output › Mode Виведення › Режим
Output: Виведення:
Overlap Перекриття
Overlap: Перекриття:
Overlapping Перекривається
Overlay Накладання
Can't render this file because it contains an unexpected character in line 725 and column 40.
+1
View File
@@ -2448,6 +2448,7 @@ file-filter Templates Mẫu
Output › Mode Đầu ra › Chế độ
Output: Đầu ra:
Outside Bên ngoài
Overlap Chồng lấp
Overlap: Chồng lấp:
Overlapping Chồng lấp
Overlay Phủ lên
1 # Vietnamese (vi) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
2448
2449
2450
2451
2452
2453
2454
+1
View File
@@ -2070,6 +2070,7 @@ file-filter Templates 模板
Output White 输出白色
Output › Mode 输出 › 模式
Output: 输出:
Overlap 重叠
Overlap: 重叠:
Overlapping 重叠
Overlay 叠加
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2898,6 +2898,7 @@ file-filter Templates 範本
Output White 輸出白色
Output › Mode 輸出 › 模式
Output: 輸出:
Overlap 重疊
Overlap: 重疊:
Overlapping 重疊
Overlay 覆蓋
Can't render this file because it contains an unexpected character in line 6 and column 47.