use crate::elements::PathContent;
use crate::geometry::Rect;
use crate::layout::{TextChar, TextLine, TextSpan, Word};
#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub enum RectFilterMode {
#[default]
#[serde(rename = "intersects")]
Intersects,
#[serde(rename = "contained")]
FullyContained,
#[serde(rename = "overlap")]
MinOverlap(f32),
}
pub trait LayoutObjectSpatial {
fn bbox(&self) -> Rect;
fn intersects_rect(&self, rect: &Rect) -> bool {
self.bbox().intersects(rect)
}
fn contained_in_rect(&self, rect: &Rect) -> bool {
rect.contains_rect(&self.bbox())
}
fn overlap_with_rect(&self, rect: &Rect) -> f32 {
let bbox = self.bbox();
let intersection = bbox.intersection(rect);
match intersection {
Some(inter) => {
let area = bbox.width * bbox.height;
if area > 0.0 {
(inter.width * inter.height) / area
} else {
1.0
}
},
None => 0.0,
}
}
fn matches_filter(&self, rect: &Rect, mode: RectFilterMode) -> bool {
match mode {
RectFilterMode::Intersects => self.intersects_rect(rect),
RectFilterMode::FullyContained => self.contained_in_rect(rect),
RectFilterMode::MinOverlap(threshold) => self.overlap_with_rect(rect) >= threshold,
}
}
}
impl LayoutObjectSpatial for TextChar {
fn bbox(&self) -> Rect {
self.bbox
}
}
impl LayoutObjectSpatial for TextSpan {
fn bbox(&self) -> Rect {
self.bbox
}
}
impl LayoutObjectSpatial for Word {
fn bbox(&self) -> Rect {
self.bbox
}
}
impl LayoutObjectSpatial for TextLine {
fn bbox(&self) -> Rect {
self.bbox
}
}
impl LayoutObjectSpatial for PathContent {
fn bbox(&self) -> Rect {
self.bbox
}
}
pub trait SpatialCollectionFiltering<T: LayoutObjectSpatial> {
fn filter_by_rect(&self, rect: &Rect, mode: RectFilterMode) -> Vec<T>;
fn exclude_rects(&self, rects: &[Rect], mode: RectFilterMode) -> Vec<T>;
}
impl<T: LayoutObjectSpatial + Clone> SpatialCollectionFiltering<T> for [T] {
fn filter_by_rect(&self, rect: &Rect, mode: RectFilterMode) -> Vec<T> {
self.iter()
.filter(|obj| obj.matches_filter(rect, mode))
.cloned()
.collect()
}
fn exclude_rects(&self, rects: &[Rect], mode: RectFilterMode) -> Vec<T> {
self.iter()
.filter(|obj| !rects.iter().any(|r| obj.matches_filter(r, mode)))
.cloned()
.collect()
}
}
impl<T: LayoutObjectSpatial + Clone> SpatialCollectionFiltering<T> for Vec<T> {
fn filter_by_rect(&self, rect: &Rect, mode: RectFilterMode) -> Vec<T> {
self.as_slice().filter_by_rect(rect, mode)
}
fn exclude_rects(&self, rects: &[Rect], mode: RectFilterMode) -> Vec<T> {
self.as_slice().exclude_rects(rects, mode)
}
}