use tiny_skia::{FillRule, Mask, Path, PathBuilder, Rect, Transform};
use zenith_scene::StrokeAlign;
pub(super) fn clip_mask(
effective_clip: (f64, f64, f64, f64),
width: u32,
height: u32,
) -> Option<Option<Mask>> {
let pixmap_bounds = (0.0, 0.0, f64::from(width), f64::from(height));
let (cx, cy, cx2, cy2) = intersect_rects(effective_clip, pixmap_bounds)?; if cx <= 0.0 && cy <= 0.0 && cx2 >= f64::from(width) && cy2 >= f64::from(height) {
return Some(None);
}
let mut mask = Mask::new(width, height)?;
let rect = Rect::from_xywh(cx as f32, cy as f32, (cx2 - cx) as f32, (cy2 - cy) as f32)?;
let clip_path = PathBuilder::from_rect(rect);
mask.fill_path(&clip_path, FillRule::Winding, false, Transform::identity());
Some(Some(mask))
}
pub(super) fn build_align_mask(
points: &[f64],
align: StrokeAlign,
fill_even_odd: bool,
effective_clip: (f64, f64, f64, f64),
width: u32,
height: u32,
device_ts: Transform,
) -> Option<Mask> {
let invert = match align {
StrokeAlign::Inside => false,
StrokeAlign::Outside => true,
StrokeAlign::Center => return None,
};
let fill_path = build_poly_path(points, true)?;
let mut mask = Mask::new(width, height)?;
let fill_rule = if fill_even_odd {
FillRule::EvenOdd
} else {
FillRule::Winding
};
mask.fill_path(&fill_path, fill_rule, true, device_ts);
if invert {
mask.invert();
}
let pixmap_bounds = (0.0, 0.0, f64::from(width), f64::from(height));
if let Some((cx, cy, cx2, cy2)) = intersect_rects(effective_clip, pixmap_bounds) {
let full_page =
cx <= 0.0 && cy <= 0.0 && cx2 >= f64::from(width) && cy2 >= f64::from(height);
if !full_page
&& let Some(rect) =
Rect::from_xywh(cx as f32, cy as f32, (cx2 - cx) as f32, (cy2 - cy) as f32)
{
let clip_path = PathBuilder::from_rect(rect);
mask.intersect_path(&clip_path, FillRule::Winding, false, Transform::identity());
}
}
Some(mask)
}
pub(super) fn intersect_rects(
(ax, ay, ax2, ay2): (f64, f64, f64, f64),
(bx, by, bx2, by2): (f64, f64, f64, f64),
) -> Option<(f64, f64, f64, f64)> {
let ix = ax.max(bx);
let iy = ay.max(by);
let ix2 = ax2.min(bx2);
let iy2 = ay2.min(by2);
if ix < ix2 && iy < iy2 {
Some((ix, iy, ix2, iy2))
} else {
None
}
}
pub(super) fn build_poly_path(points: &[f64], closed: bool) -> Option<Path> {
let mut pb = PathBuilder::new();
let (x0, y0) = match (points.first(), points.get(1)) {
(Some(&x), Some(&y)) => (x as f32, y as f32),
_ => return None,
};
pb.move_to(x0, y0);
let mut i = 2;
while i + 1 < points.len() {
let (px, py) = match (points.get(i), points.get(i + 1)) {
(Some(&x), Some(&y)) => (x as f32, y as f32),
_ => break,
};
pb.line_to(px, py);
i += 2;
}
if closed {
pb.close();
}
pb.finish()
}
pub(super) fn build_rounded_rect_path(
x: f32,
y: f32,
w: f32,
h: f32,
radii: [f32; 4],
) -> Option<Path> {
if !w.is_finite() || !h.is_finite() || w <= 0.0 || h <= 0.0 {
return None;
}
let half_min = (w / 2.0).min(h / 2.0);
let tl = radii[0].max(0.0).min(half_min);
let tr = radii[1].max(0.0).min(half_min);
let br = radii[2].max(0.0).min(half_min);
let bl = radii[3].max(0.0).min(half_min);
const K: f32 = 0.552_284_8_f32; let ktl = K * tl;
let ktr = K * tr;
let kbr = K * br;
let kbl = K * bl;
let mut pb = PathBuilder::new();
pb.move_to(x + tl, y);
pb.line_to(x + w - tr, y);
if tr > 0.0 {
pb.cubic_to(x + w - tr + ktr, y, x + w, y + tr - ktr, x + w, y + tr);
}
pb.line_to(x + w, y + h - br);
if br > 0.0 {
pb.cubic_to(
x + w,
y + h - br + kbr,
x + w - br + kbr,
y + h,
x + w - br,
y + h,
);
}
pb.line_to(x + bl, y + h);
if bl > 0.0 {
pb.cubic_to(x + bl - kbl, y + h, x, y + h - bl + kbl, x, y + h - bl);
}
pb.line_to(x, y + tl);
if tl > 0.0 {
pb.cubic_to(x, y + tl - ktl, x + tl - ktl, y, x + tl, y);
}
pb.close();
pb.finish()
}
pub(super) struct GlyphOutlinePen {
pub(super) builder: PathBuilder,
origin_x: f32,
baseline_y: f32,
scale: f32,
}
impl GlyphOutlinePen {
pub(super) fn new(origin_x: f32, baseline_y: f32, scale: f32) -> Self {
Self {
builder: PathBuilder::new(),
origin_x,
baseline_y,
scale,
}
}
#[inline]
fn to_px(&self, fx: f32, fy: f32) -> (f32, f32) {
let px = self.origin_x + fx * self.scale;
let py = self.baseline_y - fy * self.scale;
(px, py)
}
}
impl ttf_parser::OutlineBuilder for GlyphOutlinePen {
fn move_to(&mut self, x: f32, y: f32) {
let (px, py) = self.to_px(x, y);
self.builder.move_to(px, py);
}
fn line_to(&mut self, x: f32, y: f32) {
let (px, py) = self.to_px(x, y);
self.builder.line_to(px, py);
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
let (px1, py1) = self.to_px(x1, y1);
let (px, py) = self.to_px(x, y);
self.builder.quad_to(px1, py1, px, py);
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
let (px1, py1) = self.to_px(x1, y1);
let (px2, py2) = self.to_px(x2, y2);
let (px, py) = self.to_px(x, y);
self.builder.cubic_to(px1, py1, px2, py2, px, py);
}
fn close(&mut self) {
self.builder.close();
}
}