use super::block::BlockPaint;
use crate::brush::Brush;
use crate::color::Color;
use crate::geometry::Affine;
use crate::pick::PickId;
use crate::scene::SceneBuilder;
pub(crate) fn emit_block_paint(
scene: &mut dyn SceneBuilder,
paint: &BlockPaint,
offsets: super::anchor::AnchorOffsets,
outer: Affine,
dpi: f64,
pick_id: PickId,
) {
let rect = crate::geometry::Rect::new(
paint.outer_rect.x0 - offsets.ref_x as f64,
paint.outer_rect.y0 - offsets.ref_y as f64,
paint.outer_rect.x1 - offsets.ref_x as f64,
paint.outer_rect.y1 - offsets.ref_y as f64,
);
let path = if paint.corner_radius > 0.0 {
crate::primitives::rounded_rect(rect, paint.corner_radius as f64)
} else {
crate::primitives::rect(rect)
};
if let Some(color) = paint.background {
scene.fill(
crate::path::FillRule::NonZero,
outer,
&Brush::Solid(color),
None,
&path,
pick_id,
);
}
if let Some(border) = paint.border.as_ref() {
let has_markers = border
.linetype_pt
.as_ref()
.map(|p| !crate::linetype::is_marker_free(p))
.unwrap_or(false);
let dashes_px: Option<Vec<f64>> = border
.linetype_pt
.as_ref()
.filter(|_| !has_markers)
.and_then(|pattern| crate::linetype::to_kurbo_dashes(pattern))
.map(|pts| pts.into_iter().map(|pt| pt * dpi / 72.0).collect());
let border_stroke = |w_px: f32| {
let s = crate::stroke::Stroke::new(w_px as f64)
.with_caps(crate::stroke::Cap::Butt)
.with_join(crate::stroke::Join::Miter);
if let (Some(pattern_px), false) = (dashes_px.as_ref(), has_markers) {
s.with_dashes(0.0_f64, pattern_px.clone())
} else {
s
}
};
if border.is_uniform() {
let w = border.widths_px[0];
if w > 0.0 {
if has_markers {
stroke_markered_perimeter(
scene,
pick_id,
&rect,
w,
border.color,
border
.linetype_pt
.as_ref()
.expect("has_markers implies Some"),
outer,
dpi,
);
} else {
scene.stroke(
&border_stroke(w),
outer,
&Brush::Solid(border.color),
None,
&path,
pick_id,
);
}
}
} else {
let brush = Brush::Solid(border.color);
let widths = border.widths_px;
let (x0, y0, x1, y1) = (rect.x0, rect.y0, rect.x1, rect.y1);
let corners = [
crate::geometry::Point::new(x0, y0),
crate::geometry::Point::new(x1, y0),
crate::geometry::Point::new(x1, y1),
crate::geometry::Point::new(x0, y1),
];
for chain in group_border_sides_cw(widths, corners) {
if has_markers {
let sampler = crate::primitives::PolylineSampler::from_polyline(&chain.points);
let color = border.color;
let solid = crate::stroke::Stroke::new(chain.width as f64)
.with_caps(crate::stroke::Cap::Butt)
.with_join(crate::stroke::Join::Miter);
let shapes = crate::shape::ShapeRegistry::shared_builtins();
crate::linetype::draw_linetype_with_markers(
scene,
std::slice::from_ref(&sampler),
border
.linetype_pt
.as_ref()
.expect("has_markers implies Some"),
0.0,
chain.width as f64,
color,
color,
0.0,
&solid,
outer,
shapes,
dpi,
pick_id,
false,
);
continue;
}
let mut path = crate::path::Path::new();
let mut pts = chain.points.iter();
if let Some(&p) = pts.next() {
path.move_to(p);
for &p in pts {
path.line_to(p);
}
}
scene.stroke(
&border_stroke(chain.width),
outer,
&brush,
None,
&path,
pick_id,
);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn stroke_markered_perimeter(
scene: &mut dyn SceneBuilder,
pick_id: PickId,
rect: &crate::geometry::Rect,
width_px: f32,
color: Color,
pattern_pt: &[crate::scales::value::LinetypeStep],
outer: Affine,
dpi: f64,
) {
let corners = [
crate::geometry::Point::new(rect.x0, rect.y0),
crate::geometry::Point::new(rect.x1, rect.y0),
crate::geometry::Point::new(rect.x1, rect.y1),
crate::geometry::Point::new(rect.x0, rect.y1),
crate::geometry::Point::new(rect.x0, rect.y0),
];
let sampler = crate::primitives::PolylineSampler::from_polyline(&corners);
let solid = crate::stroke::Stroke::new(width_px as f64)
.with_caps(crate::stroke::Cap::Butt)
.with_join(crate::stroke::Join::Miter);
let shapes = crate::shape::ShapeRegistry::shared_builtins();
crate::linetype::draw_linetype_with_markers(
scene,
std::slice::from_ref(&sampler),
pattern_pt,
0.0,
width_px as f64,
color,
color,
0.0,
&solid,
outer,
shapes,
dpi,
pick_id,
false,
);
}
struct BorderChain {
points: Vec<crate::geometry::Point>,
width: f32,
}
fn group_border_sides_cw(
widths: [f32; 4],
corners: [crate::geometry::Point; 4],
) -> Vec<BorderChain> {
let side = |i: usize| -> (crate::geometry::Point, crate::geometry::Point) {
(corners[i], corners[(i + 1) % 4])
};
let present = |i: usize| widths[i] > 0.0;
let same_width = |i: usize, j: usize| (widths[i] - widths[j]).abs() < 1e-3;
let start = (0..4).find(|&i| {
let prev = (i + 3) % 4;
present(i) && !(present(prev) && same_width(prev, i))
});
let Some(start) = start else {
let mut points: Vec<crate::geometry::Point> = corners.to_vec();
points.push(corners[0]);
return vec![BorderChain {
points,
width: widths[0],
}];
};
let mut chains: Vec<BorderChain> = Vec::new();
let mut cur: Vec<crate::geometry::Point> = Vec::new();
let mut cur_w = 0.0f32;
for step in 0..4 {
let idx = (start + step) % 4;
let w = widths[idx];
let (a, b) = side(idx);
if w <= 0.0 {
if !cur.is_empty() {
chains.push(BorderChain {
points: std::mem::take(&mut cur),
width: cur_w,
});
}
continue;
}
if cur.is_empty() {
cur.push(a);
cur.push(b);
cur_w = w;
} else if (w - cur_w).abs() < 1e-3 {
cur.push(b);
} else {
chains.push(BorderChain {
points: std::mem::take(&mut cur),
width: cur_w,
});
cur.push(a);
cur.push(b);
cur_w = w;
}
}
if !cur.is_empty() {
chains.push(BorderChain {
points: cur,
width: cur_w,
});
}
chains
}