use std::sync::Arc;
use crate::bitmap::AaBuf;
use crate::scanner::XPathScanner;
use crate::types::{AA_SIZE, splash_ceil, splash_floor};
use crate::xpath::XPath;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ClipResult {
AllInside,
AllOutside,
Partial,
}
pub struct Clip {
pub antialias: bool,
pub x_min: f64,
pub y_min: f64,
pub x_max: f64,
pub y_max: f64,
pub x_min_i: i32,
pub y_min_i: i32,
pub x_max_i: i32,
pub y_max_i: i32,
scanners: Vec<Arc<XPathScanner>>,
}
impl Clip {
#[must_use]
pub fn new(x0: f64, y0: f64, x1: f64, y1: f64, antialias: bool) -> Self {
let mut clip = Self {
antialias,
x_min: 0.0,
y_min: 0.0,
x_max: 0.0,
y_max: 0.0,
x_min_i: 0,
y_min_i: 0,
x_max_i: 0,
y_max_i: 0,
scanners: Vec::new(),
};
clip.set_rect(x0, y0, x1, y1);
clip
}
#[must_use]
pub fn clone_shared(&self) -> Self {
Self {
antialias: self.antialias,
x_min: self.x_min,
y_min: self.y_min,
x_max: self.x_max,
y_max: self.y_max,
x_min_i: self.x_min_i,
y_min_i: self.y_min_i,
x_max_i: self.x_max_i,
y_max_i: self.y_max_i,
scanners: self.scanners.clone(), }
}
pub fn reset_to_rect(&mut self, x0: f64, y0: f64, x1: f64, y1: f64) {
self.set_rect(x0, y0, x1, y1);
self.scanners.clear();
}
pub fn clip_to_rect(&mut self, x0: f64, y0: f64, x1: f64, y1: f64) {
let (lx, rx) = (x0.min(x1), x0.max(x1));
let (ly, ry) = (y0.min(y1), y0.max(y1));
self.x_min = self.x_min.max(lx);
self.x_max = self.x_max.min(rx);
self.y_min = self.y_min.max(ly);
self.y_max = self.y_max.min(ry);
self.recompute_int_bounds();
}
pub fn clip_to_path(&mut self, xpath: &XPath, eo: bool) {
if xpath.segs.is_empty() {
self.x_max = self.x_min - 1.0;
self.y_max = self.y_min - 1.0;
self.recompute_int_bounds();
return;
}
if let Some((rx0, ry0, rx1, ry1)) = detect_rect(xpath) {
self.clip_to_rect(rx0, ry0, rx1, ry1);
return;
}
let (y_lo, y_hi) = if self.antialias {
let lo = self
.y_min_i
.checked_mul(AA_SIZE)
.expect("AA y_lo overflows i32: y_min_i is unreasonably large");
let hi = self
.y_max_i
.checked_add(1)
.and_then(|v| v.checked_mul(AA_SIZE))
.map(|v| v - 1)
.expect("AA y_hi overflows i32: y_max_i is unreasonably large");
(lo, hi)
} else {
(self.y_min_i, self.y_max_i)
};
let scanner = XPathScanner::new(xpath, eo, y_lo, y_hi);
self.scanners.push(Arc::new(scanner));
}
#[inline]
#[must_use]
pub fn test(&self, x: i32, y: i32) -> bool {
if x < self.x_min_i || x > self.x_max_i || y < self.y_min_i || y > self.y_max_i {
return false;
}
self.test_clip_paths(x, y)
}
#[must_use]
pub fn test_rect(&self, left: i32, top: i32, right: i32, bottom: i32) -> ClipResult {
if f64::from(right + 1) <= self.x_min
|| f64::from(left) >= self.x_max
|| f64::from(bottom + 1) <= self.y_min
|| f64::from(top) >= self.y_max
{
return ClipResult::AllOutside;
}
if f64::from(left) >= self.x_min
&& f64::from(right + 1) <= self.x_max
&& f64::from(top) >= self.y_min
&& f64::from(bottom + 1) <= self.y_max
&& self.scanners.is_empty()
{
return ClipResult::AllInside;
}
ClipResult::Partial
}
#[must_use]
pub fn test_span(&self, x0: i32, x1: i32, y: i32) -> ClipResult {
let result = self.test_rect(x0, y, x1, y);
if result != ClipResult::AllInside {
return result;
}
for scanner in &self.scanners {
let (sx0, sx1, sy) = aa_coords(x0, x1, y, self.antialias);
if !scanner.test_span(sx0, sx1, sy) {
return ClipResult::Partial;
}
}
ClipResult::AllInside
}
pub fn clip_aa_line(&self, aa_buf: &mut AaBuf, x0: &mut i32, x1: &mut i32, y: i32) {
for scanner in &self.scanners {
scanner.render_aa_line(aa_buf, x0, x1, y);
}
*x0 = (*x0).max(self.x_min_i);
*x1 = (*x1).min(self.x_max_i);
}
fn set_rect(&mut self, x0: f64, y0: f64, x1: f64, y1: f64) {
self.x_min = x0.min(x1);
self.x_max = x0.max(x1);
self.y_min = y0.min(y1);
self.y_max = y0.max(y1);
self.recompute_int_bounds();
}
fn recompute_int_bounds(&mut self) {
self.x_min_i = splash_floor(self.x_min);
self.y_min_i = splash_floor(self.y_min);
self.x_max_i = splash_ceil(self.x_max) - 1;
self.y_max_i = splash_ceil(self.y_max) - 1;
}
fn test_clip_paths(&self, x: i32, y: i32) -> bool {
let (tx, ty, _) = aa_coords(x, x, y, self.antialias);
self.scanners.iter().all(|s| s.test(tx, ty))
}
}
#[inline]
fn aa_coords(x0: i32, x1: i32, y: i32, antialias: bool) -> (i32, i32, i32) {
if antialias {
let sx0 = x0
.checked_mul(AA_SIZE)
.expect("aa_coords: x0 * AA_SIZE overflows i32");
let sx1 = x1
.checked_mul(AA_SIZE)
.and_then(|v| v.checked_add(AA_SIZE - 1))
.expect("aa_coords: x1 * AA_SIZE + (AA_SIZE-1) overflows i32");
let sy = y
.checked_mul(AA_SIZE)
.expect("aa_coords: y * AA_SIZE overflows i32");
(sx0, sx1, sy)
} else {
(x0, x1, y)
}
}
fn detect_rect(xpath: &XPath) -> Option<(f64, f64, f64, f64)> {
use crate::xpath::XPathFlags;
if xpath.segs.len() != 4 {
return None;
}
let segs = &xpath.segs;
let verts = segs
.iter()
.filter(|s| s.flags.contains(XPathFlags::VERT))
.count();
let horizs = segs
.iter()
.filter(|s| s.flags.contains(XPathFlags::HORIZ))
.count();
if verts != 2 || horizs != 2 {
return None;
}
let vert_xs = segs
.iter()
.filter(|s| s.flags.contains(XPathFlags::VERT))
.flat_map(|s| [s.x0, s.x1]);
let horiz_ys = segs
.iter()
.filter(|s| s.flags.contains(XPathFlags::HORIZ))
.flat_map(|s| [s.y0, s.y1]);
let (x0, x1) = vert_xs.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), v| {
(lo.min(v), hi.max(v))
});
let (y0, y1) = horiz_ys.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), v| {
(lo.min(v), hi.max(v))
});
Some((x0, y0, x1, y1))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_clip_rect_bounds() {
let c = Clip::new(1.5, 2.5, 10.5, 8.5, false);
assert_eq!(c.x_min_i, 1); assert_eq!(c.y_min_i, 2); assert_eq!(c.x_max_i, 10); assert_eq!(c.y_max_i, 8); }
#[test]
fn test_inside() {
let c = Clip::new(0.0, 0.0, 10.0, 10.0, false);
assert!(c.test(5, 5));
}
#[test]
fn test_outside() {
let c = Clip::new(0.0, 0.0, 10.0, 10.0, false);
assert!(!c.test(15, 5));
assert!(!c.test(5, 15));
}
#[test]
fn clip_to_rect_shrinks() {
let mut c = Clip::new(0.0, 0.0, 10.0, 10.0, false);
c.clip_to_rect(2.0, 3.0, 8.0, 7.0);
assert_eq!(c.x_min_i, 2);
assert_eq!(c.y_min_i, 3);
}
#[test]
fn test_rect_all_inside() {
let c = Clip::new(0.0, 0.0, 20.0, 20.0, false);
assert_eq!(c.test_rect(1, 1, 5, 5), ClipResult::AllInside);
}
#[test]
fn test_rect_all_outside() {
let c = Clip::new(0.0, 0.0, 10.0, 10.0, false);
assert_eq!(c.test_rect(15, 15, 20, 20), ClipResult::AllOutside);
}
#[test]
fn clone_shares_scanners() {
let c = Clip::new(0.0, 0.0, 10.0, 10.0, false);
let c2 = c.clone_shared();
assert_eq!(c2.x_min_i, c.x_min_i);
assert_eq!(c.scanners.len(), c2.scanners.len());
}
#[test]
fn aa_coords_non_aa_passthrough() {
assert_eq!(aa_coords(3, 7, 5, false), (3, 7, 5));
}
#[test]
fn aa_coords_aa_scales() {
assert_eq!(aa_coords(3, 7, 5, true), (12, 31, 20));
}
}