use vello_common::tile::Tile;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[doc(hidden)]
pub struct Span {
x: u16,
width: u16,
}
impl Span {
pub fn new(x: u16, width: u16) -> Self {
Self { x, width }
}
pub fn tile_aligned(self) -> TileAlignedSpan {
let start = self.tile_x();
let count = if self.width == 0 {
0
} else {
self.tile_end() - start
};
TileAlignedSpan::from_tiles(start, count)
}
pub fn tile_x(self) -> u16 {
self.x / Tile::WIDTH
}
pub fn tile_end(self) -> u16 {
self.pixel_end().div_ceil(Tile::WIDTH)
}
pub fn extend(&mut self, other: Self) {
let x = self.x.min(other.x);
let end = self.pixel_end().max(other.pixel_end());
*self = Self::new(x, end.saturating_sub(x));
}
pub fn intersect(self, other: Self) -> Option<Self> {
let x = self.x.max(other.x);
let end = self.pixel_end().min(other.pixel_end());
(x < end).then(|| Self::new(x, end - x))
}
pub fn pixel_x(self) -> u16 {
self.x
}
pub fn pixel_width(self) -> u16 {
self.width
}
pub fn pixel_end(self) -> u16 {
self.pixel_x().saturating_add(self.pixel_width())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[doc(hidden)]
pub struct TileAlignedSpan(Span);
impl TileAlignedSpan {
pub fn from_tiles(start: u16, count: u16) -> Self {
let x = start.checked_mul(Tile::WIDTH).unwrap();
let width = count.checked_mul(Tile::WIDTH).unwrap();
x.checked_add(width).unwrap();
Self(Span::new(x, width))
}
pub fn as_span(self) -> Span {
self.0
}
pub fn pixel_x(self) -> u16 {
self.0.pixel_x()
}
pub fn pixel_width(self) -> u16 {
self.0.pixel_width()
}
pub fn pixel_end(self) -> u16 {
self.0.pixel_end()
}
pub fn tile_x(self) -> u16 {
self.pixel_x() / Tile::WIDTH
}
pub fn tile_end(self) -> u16 {
self.pixel_end() / Tile::WIDTH
}
pub fn extend(&mut self, other: Self) {
self.0.extend(other.0);
}
pub fn intersect(self, other: Self) -> Option<Self> {
self.0.intersect(other.0).map(Self)
}
}
impl TryFrom<Span> for TileAlignedSpan {
type Error = &'static str;
fn try_from(span: Span) -> Result<Self, Self::Error> {
if !span.x.is_multiple_of(Tile::WIDTH) || !span.width.is_multiple_of(Tile::WIDTH) {
return Err("span is not tile-aligned");
}
if span.x.checked_add(span.width).is_none() {
return Err("tile span end overflow");
}
Ok(Self::from_tiles(
span.x / Tile::WIDTH,
span.width / Tile::WIDTH,
))
}
}
#[cfg(test)]
mod tests {
use crate::span::{Span, TileAlignedSpan};
use vello_common::tile::Tile;
#[test]
fn tile_aligned_rounds_outward() {
let tile = Tile::WIDTH;
for (span, expected) in [
(Span::new(tile, tile), Span::new(tile, tile)),
(Span::new(tile + 1, tile - 1), Span::new(tile, tile)),
(Span::new(tile, tile + 1), Span::new(tile, 2 * tile)),
(Span::new(tile + 1, tile), Span::new(tile, 2 * tile)),
] {
assert_eq!(span.tile_aligned().as_span(), expected, "{span:?}");
}
}
#[test]
fn tile_aligned_keeps_empty_spans_empty() {
for x in [0, Tile::WIDTH, Tile::WIDTH + 1] {
let aligned = Span::new(x, 0).tile_aligned();
assert_eq!(aligned.pixel_width(), 0, "x = {x}");
}
}
#[test]
fn try_from_preserves_aligned_bounds() {
let max_aligned = u16::MAX / Tile::WIDTH * Tile::WIDTH;
for span in [
Span::new(Tile::WIDTH, 3 * Tile::WIDTH),
Span::new(0, max_aligned),
Span::new(max_aligned, 0),
] {
assert_eq!(TileAlignedSpan::try_from(span).unwrap().as_span(), span);
}
}
#[test]
fn try_from_rejects_unaligned_bounds() {
for span in [
Span::new(Tile::WIDTH + 1, Tile::WIDTH),
Span::new(Tile::WIDTH, Tile::WIDTH + 1),
Span::new(Tile::WIDTH + 1, 0),
] {
assert!(TileAlignedSpan::try_from(span).is_err(), "{span:?}");
}
}
}