Merge pull request #3889 from dylanmtaylor/fix/audit-effects-color

Security: Scribble rows, Outer Glow dilation and CSS export names
This commit is contained in:
BFlat
2026-10-24 04:50:23 +07:00
committed by GitHub
3 changed files with 71 additions and 17 deletions
+15 -3
View File
@@ -181,7 +181,9 @@ fn css_comment(s: &str) -> String {
fn write_css(lib: &SwatchLibrary, keep: impl Fn(&&Swatch) -> bool) -> String {
let mut out = format!("/* {} */\n:root {{\n", css_comment(&lib.name));
let mut taken: Vec<String> = vec![];
let mut taken: HashSet<String> = HashSet::new();
// The next number to try for each base, so repeated names don't search the ones used before.
let mut next: HashMap<String, usize> = HashMap::new();
let mut props = |out: &mut String, list: &[Swatch]| {
for w in list.iter().filter(&keep) {
let value = match &w.paint {
@@ -198,8 +200,18 @@ fn write_css(lib: &SwatchLibrary, keep: impl Fn(&&Swatch) -> bool) -> String {
};
// Names differing only in case or punctuation map to one property: number the others.
let base = css_property(&w.name);
let name = (1..).map(|i| if i == 1 { base.clone() } else { format!("{base}-{i}") }).find(|n| !taken.contains(n)).unwrap_or(base);
taken.push(name.clone());
let name = if taken.insert(base.clone()) {
base
} else {
let n = next.entry(base.clone()).or_insert(1);
loop {
*n += 1;
let name = format!("{base}-{n}");
if taken.insert(name.clone()) {
break name;
}
}
};
let note = if w.spot { " /* spot */" } else { "" };
out.push_str(&format!(" {name}: {value};{note}\n"));
}
+49 -11
View File
@@ -932,22 +932,41 @@ pub fn dilate_plane(a: &mut Vec<f32>, w: usize, h: usize, radius: f64) {
/// One horizontal pass of [`dilate_plane`] over the rows of `w` values of a plane: the largest
/// value within `whole`, blended by `frac` towards the largest within `whole + 1`.
fn max_plane(src: &[f32], dst: &mut [f32], w: usize, whole: usize, frac: f32) {
let (mut inner, mut ring) = (vec![0.0f32; w], vec![0.0f32; w]);
for (row, out) in src.chunks_exact(w).zip(dst.chunks_exact_mut(w)) {
for (x, o) in out.iter_mut().enumerate() {
let lo = x.saturating_sub(whole + 1);
let hi = (x + whole + 1).min(w - 1);
let (mut inner, mut ring) = (row[x], row[x]);
for (j, v) in row[lo..=hi].iter().enumerate() {
if (lo + j).abs_diff(x) <= whole {
inner = inner.max(*v);
}
ring = ring.max(*v);
}
*o = inner + (ring - inner) * frac;
window_max(row, whole, &mut inner);
window_max(row, whole + 1, &mut ring);
for ((o, i), r) in out.iter_mut().zip(&inner).zip(&ring) {
*o = i + (r - i) * frac;
}
}
}
/// For each `x`, the largest of `row[x - radius..=x + radius]` (cut at the row's ends) into `out`,
/// in time linear in the row (a sliding window, not a scan of the window for each `x`).
fn window_max(row: &[f32], radius: usize, out: &mut [f32]) {
use std::collections::VecDeque;
// Indices of the window's values that can still be its largest, falling in value.
let mut keep: VecDeque<usize> = VecDeque::new();
let mut next = 0;
for (x, o) in out.iter_mut().enumerate().take(row.len()) {
let hi = x.saturating_add(radius).min(row.len().saturating_sub(1));
while next <= hi {
let v = row[next];
while keep.back().is_some_and(|&b| row[b] <= v) {
keep.pop_back();
}
keep.push_back(next);
next += 1;
}
let lo = x.saturating_sub(radius);
while keep.front().is_some_and(|&f| f < lo) {
keep.pop_front();
}
*o = keep.front().map_or(row[x], |&f| row[f]);
}
}
/// Premultiplied RGBA at 16 bits (×257): blurs and averages that keep their tones.
pub(super) type Px16 = [u16; 4];
@@ -1061,3 +1080,22 @@ mod tests;
mod tests_paint;
#[cfg(test)]
mod tests_sketch;
#[cfg(test)]
mod window_max_tests {
use super::window_max;
#[test]
fn the_sliding_maximum_matches_a_scan_of_each_window() {
let row: Vec<f32> = (0..40u32).map(|i| ((i * 7919) % 31) as f32).collect();
for radius in [0, 1, 2, 5, 39, 100] {
let mut out = vec![0.0; row.len()];
window_max(&row, radius, &mut out);
for (x, got) in out.iter().enumerate() {
let (lo, hi) = (x.saturating_sub(radius), (x + radius).min(row.len() - 1));
let want = row[lo..=hi].iter().copied().fold(f32::MIN, f32::max);
assert_eq!(*got, want, "radius {radius} at {x}");
}
}
}
}
+7 -3
View File
@@ -184,9 +184,14 @@ pub fn scribble(path: &PathData, b: Rect, p: &Value) -> PathData {
row: u64,
}
let mut chains: Vec<Chain> = vec![];
let mut y = y0 + step / 2.0;
let mut row = 0u64;
while y < y1 {
// Rows are counted, not summed: adding a step that is below the spacing of the numbers at `y`
// would never move it.
while row <= 2001 {
let y = y0 + step / 2.0 + row as f64 * step;
if y.partial_cmp(&y1) != Some(std::cmp::Ordering::Less) {
break;
}
let mut xs: Vec<f64> = vec![];
for pl in &polys {
for w in pl.windows(2) {
@@ -225,7 +230,6 @@ pub fn scribble(path: &PathData, b: Rect, p: &Value) -> PathData {
}
}
row += 1;
y += step;
}
let lines: Vec<SubPath> = chains
.into_iter()