use std::f64::consts::*;
use std::ops::Deref;
use cssparser::Parser;
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use crate::angle::Angle;
use crate::aspect_ratio::*;
use crate::bbox::BoundingBox;
use crate::borrow_element_as;
use crate::document::AcquiredNodes;
use crate::drawing_ctx::{DrawingCtx, Viewport};
use crate::element::{set_attribute, ElementTrait};
use crate::error::*;
use crate::float_eq_cairo::ApproxEqCairo;
use crate::layout::{self, Shape, StackingContext};
use crate::length::*;
use crate::node::{CascadedValues, Node, NodeBorrow, NodeDraw};
use crate::parse_identifiers;
use crate::parsers::{Parse, ParseValue};
use crate::path_builder::{arc_segment, ArcParameterization, CubicBezierCurve, Path, PathCommand};
use crate::rect::Rect;
use crate::rsvg_log;
use crate::session::Session;
use crate::transform::Transform;
use crate::viewbox::*;
use crate::xml::Attributes;
#[derive(Debug, Default, Copy, Clone, PartialEq)]
enum MarkerUnits {
UserSpaceOnUse,
#[default]
StrokeWidth,
}
impl Parse for MarkerUnits {
fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<MarkerUnits, ParseError<'i>> {
Ok(parse_identifiers!(
parser,
"userSpaceOnUse" => MarkerUnits::UserSpaceOnUse,
"strokeWidth" => MarkerUnits::StrokeWidth,
)?)
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
enum MarkerOrient {
Auto,
AutoStartReverse,
Angle(Angle),
}
impl Default for MarkerOrient {
#[inline]
fn default() -> MarkerOrient {
MarkerOrient::Angle(Angle::new(0.0))
}
}
impl Parse for MarkerOrient {
fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<MarkerOrient, ParseError<'i>> {
if parser
.try_parse(|p| p.expect_ident_matching("auto"))
.is_ok()
{
return Ok(MarkerOrient::Auto);
}
if parser
.try_parse(|p| p.expect_ident_matching("auto-start-reverse"))
.is_ok()
{
Ok(MarkerOrient::AutoStartReverse)
} else {
Angle::parse(parser).map(MarkerOrient::Angle)
}
}
}
pub struct Marker {
units: MarkerUnits,
ref_x: Length<Horizontal>,
ref_y: Length<Vertical>,
width: ULength<Horizontal>,
height: ULength<Vertical>,
orient: MarkerOrient,
aspect: AspectRatio,
vbox: Option<ViewBox>,
}
impl Default for Marker {
fn default() -> Marker {
Marker {
units: MarkerUnits::default(),
ref_x: Default::default(),
ref_y: Default::default(),
width: ULength::<Horizontal>::parse_str("3").unwrap(),
height: ULength::<Vertical>::parse_str("3").unwrap(),
orient: MarkerOrient::default(),
aspect: AspectRatio::default(),
vbox: None,
}
}
}
impl Marker {
fn render(
&self,
node: &Node,
acquired_nodes: &mut AcquiredNodes<'_>,
viewport: &Viewport,
draw_ctx: &mut DrawingCtx,
xpos: f64,
ypos: f64,
computed_angle: Angle,
line_width: f64,
clipping: bool,
marker_type: MarkerType,
marker: &layout::Marker,
) -> Result<BoundingBox, InternalRenderingError> {
let mut cascaded = CascadedValues::new_from_node(node);
cascaded.context_fill = Some(marker.context_fill.clone());
cascaded.context_stroke = Some(marker.context_stroke.clone());
let values = cascaded.get();
let params = NormalizeParams::new(values, viewport);
let marker_width = self.width.to_user(¶ms);
let marker_height = self.height.to_user(¶ms);
if marker_width.approx_eq_cairo(0.0) || marker_height.approx_eq_cairo(0.0) {
return Ok(draw_ctx.empty_bbox());
}
let rotation = match self.orient {
MarkerOrient::Auto => computed_angle,
MarkerOrient::AutoStartReverse => {
if marker_type == MarkerType::Start {
computed_angle.flip()
} else {
computed_angle
}
}
MarkerOrient::Angle(a) => a,
};
let mut transform = Transform::new_translate(xpos, ypos).pre_rotate(rotation);
if self.units == MarkerUnits::StrokeWidth {
transform = transform.pre_scale(line_width, line_width);
}
let content_viewport = if let Some(vbox) = self.vbox {
if vbox.is_empty() {
return Ok(draw_ctx.empty_bbox());
}
let r = self
.aspect
.compute(&vbox, &Rect::from_size(marker_width, marker_height));
let (vb_width, vb_height) = vbox.size();
transform = transform.pre_scale(r.width() / vb_width, r.height() / vb_height);
viewport.with_view_box(vb_width, vb_height)
} else {
viewport.with_view_box(marker_width, marker_height)
};
let content_params = NormalizeParams::new(values, &content_viewport);
transform = transform.pre_translate(
-self.ref_x.to_user(&content_params),
-self.ref_y.to_user(&content_params),
);
let clip_rect = if values.is_overflow() {
None
} else if let Some(vbox) = self.vbox {
Some(*vbox)
} else {
Some(Rect::from_size(marker_width, marker_height))
};
let elt = node.borrow_element();
let stacking_ctx = StackingContext::new(
draw_ctx.session(),
acquired_nodes,
&elt,
transform,
clip_rect,
values,
);
draw_ctx.with_discrete_layer(
&stacking_ctx,
acquired_nodes,
&content_viewport,
None,
clipping,
&mut |an, dc, new_viewport| {
node.draw_children(an, &cascaded, new_viewport, dc, clipping)
}, )
}
}
impl ElementTrait for Marker {
fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
for (attr, value) in attrs.iter() {
match attr.expanded() {
expanded_name!("", "markerUnits") => {
set_attribute(&mut self.units, attr.parse(value), session)
}
expanded_name!("", "refX") => {
set_attribute(&mut self.ref_x, attr.parse(value), session)
}
expanded_name!("", "refY") => {
set_attribute(&mut self.ref_y, attr.parse(value), session)
}
expanded_name!("", "markerWidth") => {
set_attribute(&mut self.width, attr.parse(value), session)
}
expanded_name!("", "markerHeight") => {
set_attribute(&mut self.height, attr.parse(value), session)
}
expanded_name!("", "orient") => {
set_attribute(&mut self.orient, attr.parse(value), session)
}
expanded_name!("", "preserveAspectRatio") => {
set_attribute(&mut self.aspect, attr.parse(value), session)
}
expanded_name!("", "viewBox") => {
set_attribute(&mut self.vbox, attr.parse(value), session)
}
_ => (),
}
}
}
}
#[derive(Debug, PartialEq)]
enum Segment {
Degenerate {
x: f64,
y: f64,
},
LineOrCurve {
x1: f64,
y1: f64,
x2: f64,
y2: f64,
x3: f64,
y3: f64,
x4: f64,
y4: f64,
},
}
impl Segment {
fn degenerate(x: f64, y: f64) -> Segment {
Segment::Degenerate { x, y }
}
fn curve(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> Segment {
Segment::LineOrCurve {
x1,
y1,
x2,
y2,
x3,
y3,
x4,
y4,
}
}
fn line(x1: f64, y1: f64, x2: f64, y2: f64) -> Segment {
Segment::curve(x1, y1, x2, y2, x1, y1, x2, y2)
}
fn get_directionalities(&self) -> Option<(f64, f64, f64, f64)> {
match *self {
Segment::Degenerate { .. } => None,
Segment::LineOrCurve {
x1,
y1,
x2,
y2,
x3,
y3,
x4,
y4,
} => {
let coincide_1_and_2 = points_equal(x1, y1, x2, y2);
let coincide_1_and_3 = points_equal(x1, y1, x3, y3);
let coincide_1_and_4 = points_equal(x1, y1, x4, y4);
let coincide_2_and_3 = points_equal(x2, y2, x3, y3);
let coincide_2_and_4 = points_equal(x2, y2, x4, y4);
let coincide_3_and_4 = points_equal(x3, y3, x4, y4);
if coincide_1_and_2 && coincide_1_and_3 && coincide_1_and_4 {
None
} else if coincide_1_and_2 && coincide_1_and_3 {
Some((x4 - x1, y4 - y1, x4 - x3, y4 - y3))
} else if coincide_1_and_2 && coincide_3_and_4 {
Some((x4 - x1, y4 - y1, x4 - x1, y4 - y1))
} else if coincide_2_and_3 && coincide_2_and_4 {
Some((x2 - x1, y2 - y1, x4 - x1, y4 - y1))
} else if coincide_1_and_2 {
Some((x3 - x1, y3 - y1, x4 - x3, y4 - y3))
} else if coincide_3_and_4 {
Some((x2 - x1, y2 - y1, x4 - x2, y4 - y2))
} else {
Some((x2 - x1, y2 - y1, x4 - x3, y4 - y3))
}
}
}
}
}
fn points_equal(x1: f64, y1: f64, x2: f64, y2: f64) -> bool {
x1.approx_eq_cairo(x2) && y1.approx_eq_cairo(y2)
}
enum SegmentState {
Initial,
NewSubpath,
InSubpath,
ClosedSubpath,
}
#[derive(Debug, PartialEq)]
struct Segments(Vec<Segment>);
impl Deref for Segments {
type Target = [Segment];
fn deref(&self) -> &[Segment] {
&self.0
}
}
impl From<&Path> for Segments {
fn from(path: &Path) -> Segments {
let mut last_x: f64;
let mut last_y: f64;
let mut cur_x: f64 = 0.0;
let mut cur_y: f64 = 0.0;
let mut subpath_start_x: f64 = 0.0;
let mut subpath_start_y: f64 = 0.0;
let mut segments = Vec::new();
let mut state = SegmentState::Initial;
for path_command in path.iter() {
last_x = cur_x;
last_y = cur_y;
match path_command {
PathCommand::MoveTo(x, y) => {
cur_x = x;
cur_y = y;
subpath_start_x = cur_x;
subpath_start_y = cur_y;
match state {
SegmentState::Initial | SegmentState::InSubpath => {
state = SegmentState::NewSubpath;
}
SegmentState::NewSubpath => {
segments.push(Segment::degenerate(last_x, last_y));
state = SegmentState::NewSubpath;
}
SegmentState::ClosedSubpath => {
state = SegmentState::Initial;
}
}
}
PathCommand::LineTo(x, y) => {
cur_x = x;
cur_y = y;
segments.push(Segment::line(last_x, last_y, cur_x, cur_y));
state = SegmentState::InSubpath;
}
PathCommand::CurveTo(curve) => {
let CubicBezierCurve {
pt1: (x2, y2),
pt2: (x3, y3),
to,
} = curve;
cur_x = to.0;
cur_y = to.1;
segments.push(Segment::curve(last_x, last_y, x2, y2, x3, y3, cur_x, cur_y));
state = SegmentState::InSubpath;
}
PathCommand::Arc(arc) => {
cur_x = arc.to.0;
cur_y = arc.to.1;
match arc.center_parameterization() {
ArcParameterization::CenterParameters {
center,
radii,
theta1,
delta_theta,
} => {
let rot = arc.x_axis_rotation;
let theta2 = theta1 + delta_theta;
let n_segs = (delta_theta / (PI * 0.5 + 0.001)).abs().ceil() as u32;
let d_theta = delta_theta / f64::from(n_segs);
let segment1 =
arc_segment(center, radii, rot, theta1, theta1 + d_theta);
let segment2 =
arc_segment(center, radii, rot, theta2 - d_theta, theta2);
let (x2, y2) = segment1.pt1;
let (x3, y3) = segment2.pt2;
segments
.push(Segment::curve(last_x, last_y, x2, y2, x3, y3, cur_x, cur_y));
state = SegmentState::InSubpath;
}
ArcParameterization::LineTo => {
segments.push(Segment::line(last_x, last_y, cur_x, cur_y));
state = SegmentState::InSubpath;
}
ArcParameterization::Omit => {}
}
}
PathCommand::ClosePath => {
cur_x = subpath_start_x;
cur_y = subpath_start_y;
segments.push(Segment::line(last_x, last_y, cur_x, cur_y));
state = SegmentState::ClosedSubpath;
}
}
}
if let SegmentState::NewSubpath = state {
segments.push(Segment::degenerate(cur_x, cur_y));
};
Segments(segments)
}
}
impl Segments {
fn find_incoming_angle_backwards(&self, start_index: usize) -> Option<Angle> {
for segment in self[..=start_index].iter().rev() {
match *segment {
Segment::Degenerate { .. } => {
return None; }
Segment::LineOrCurve { .. } => match segment.get_directionalities() {
Some((_, _, v2x, v2y)) => {
return Some(Angle::from_vector(v2x, v2y));
}
None => {
continue;
}
},
}
}
None
}
fn find_outgoing_angle_forwards(&self, start_index: usize) -> Option<Angle> {
for segment in &self[start_index..] {
match *segment {
Segment::Degenerate { .. } => {
return None; }
Segment::LineOrCurve { .. } => match segment.get_directionalities() {
Some((v1x, v1y, _, _)) => {
return Some(Angle::from_vector(v1x, v1y));
}
None => {
continue;
}
},
}
}
None
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
enum MarkerType {
Start,
Middle,
End,
}
fn emit_marker_by_node(
viewport: &Viewport,
draw_ctx: &mut DrawingCtx,
acquired_nodes: &mut AcquiredNodes<'_>,
marker: &layout::Marker,
xpos: f64,
ypos: f64,
computed_angle: Angle,
line_width: f64,
clipping: bool,
marker_type: MarkerType,
) -> Result<BoundingBox, InternalRenderingError> {
match acquired_nodes.acquire_ref(marker.node_ref.as_ref().unwrap()) {
Ok(acquired) => {
let node = acquired.get();
let marker_elt = borrow_element_as!(node, Marker);
marker_elt.render(
node,
acquired_nodes,
viewport,
draw_ctx,
xpos,
ypos,
computed_angle,
line_width,
clipping,
marker_type,
marker,
)
}
Err(e) => {
rsvg_log!(draw_ctx.session(), "could not acquire marker: {}", e);
Ok(draw_ctx.empty_bbox())
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
enum MarkerEndpoint {
Start,
End,
}
fn emit_marker<E>(
segment: &Segment,
endpoint: MarkerEndpoint,
marker_type: MarkerType,
orient: Angle,
emit_fn: &mut E,
) -> Result<BoundingBox, InternalRenderingError>
where
E: FnMut(MarkerType, f64, f64, Angle) -> Result<BoundingBox, InternalRenderingError>,
{
let (x, y) = match *segment {
Segment::Degenerate { x, y } => (x, y),
Segment::LineOrCurve { x1, y1, x4, y4, .. } => match endpoint {
MarkerEndpoint::Start => (x1, y1),
MarkerEndpoint::End => (x4, y4),
},
};
emit_fn(marker_type, x, y, orient)
}
pub fn render_markers_for_shape(
shape: &Shape,
viewport: &Viewport,
draw_ctx: &mut DrawingCtx,
acquired_nodes: &mut AcquiredNodes<'_>,
clipping: bool,
) -> Result<BoundingBox, InternalRenderingError> {
if shape.stroke.width.approx_eq_cairo(0.0) {
return Ok(draw_ctx.empty_bbox());
}
if shape.marker_start.node_ref.is_none()
&& shape.marker_mid.node_ref.is_none()
&& shape.marker_end.node_ref.is_none()
{
return Ok(draw_ctx.empty_bbox());
}
emit_markers_for_path(
&shape.path,
draw_ctx.empty_bbox(),
&mut |marker_type: MarkerType, x: f64, y: f64, computed_angle: Angle| {
let marker = match marker_type {
MarkerType::Start => &shape.marker_start,
MarkerType::Middle => &shape.marker_mid,
MarkerType::End => &shape.marker_end,
};
if marker.node_ref.is_some() {
emit_marker_by_node(
viewport,
draw_ctx,
acquired_nodes,
marker,
x,
y,
computed_angle,
shape.stroke.width,
clipping,
marker_type,
)
} else {
Ok(draw_ctx.empty_bbox())
}
},
)
}
fn emit_markers_for_path<E>(
path: &Path,
empty_bbox: BoundingBox,
emit_fn: &mut E,
) -> Result<BoundingBox, InternalRenderingError>
where
E: FnMut(MarkerType, f64, f64, Angle) -> Result<BoundingBox, InternalRenderingError>,
{
enum SubpathState {
NoSubpath,
InSubpath,
}
let mut bbox = empty_bbox;
let segments = Segments::from(path);
let mut subpath_state = SubpathState::NoSubpath;
for (i, segment) in segments.iter().enumerate() {
match *segment {
Segment::Degenerate { .. } => {
if let SubpathState::InSubpath = subpath_state {
assert!(i > 0);
let angle = segments
.find_incoming_angle_backwards(i - 1)
.unwrap_or_else(|| Angle::new(0.0));
let marker_bbox = emit_marker(
&segments[i - 1],
MarkerEndpoint::End,
MarkerType::End,
angle,
emit_fn,
)?;
bbox.insert(&marker_bbox);
}
let marker_bbox = emit_marker(
segment,
MarkerEndpoint::Start,
MarkerType::Middle,
Angle::new(0.0),
emit_fn,
)?;
bbox.insert(&marker_bbox);
subpath_state = SubpathState::NoSubpath;
}
Segment::LineOrCurve { .. } => {
match subpath_state {
SubpathState::NoSubpath => {
let angle = segments
.find_outgoing_angle_forwards(i)
.unwrap_or_else(|| Angle::new(0.0));
let marker_bbox = emit_marker(
segment,
MarkerEndpoint::Start,
MarkerType::Start,
angle,
emit_fn,
)?;
bbox.insert(&marker_bbox);
subpath_state = SubpathState::InSubpath;
}
SubpathState::InSubpath => {
assert!(i > 0);
let incoming = segments.find_incoming_angle_backwards(i - 1);
let outgoing = segments.find_outgoing_angle_forwards(i);
let angle = match (incoming, outgoing) {
(Some(incoming), Some(outgoing)) => incoming.bisect(outgoing),
(Some(incoming), _) => incoming,
(_, Some(outgoing)) => outgoing,
_ => Angle::new(0.0),
};
let marker_bbox = emit_marker(
segment,
MarkerEndpoint::Start,
MarkerType::Middle,
angle,
emit_fn,
)?;
bbox.insert(&marker_bbox);
}
}
}
}
}
if !segments.is_empty() {
let segment = &segments[segments.len() - 1];
if let Segment::LineOrCurve { .. } = *segment {
let incoming = segments
.find_incoming_angle_backwards(segments.len() - 1)
.unwrap_or_else(|| Angle::new(0.0));
let angle = {
if let PathCommand::ClosePath = path.iter().nth(segments.len()).unwrap() {
let outgoing = segments
.find_outgoing_angle_forwards(0)
.unwrap_or_else(|| Angle::new(0.0));
incoming.bisect(outgoing)
} else {
incoming
}
};
let marker_bbox = emit_marker(
segment,
MarkerEndpoint::End,
MarkerType::End,
angle,
emit_fn,
)?;
bbox.insert(&marker_bbox);
}
}
Ok(bbox)
}
#[cfg(test)]
mod parser_tests {
use super::*;
#[test]
fn parsing_invalid_marker_units_yields_error() {
assert!(MarkerUnits::parse_str("").is_err());
assert!(MarkerUnits::parse_str("foo").is_err());
}
#[test]
fn parses_marker_units() {
assert_eq!(
MarkerUnits::parse_str("userSpaceOnUse").unwrap(),
MarkerUnits::UserSpaceOnUse
);
assert_eq!(
MarkerUnits::parse_str("strokeWidth").unwrap(),
MarkerUnits::StrokeWidth
);
}
#[test]
fn parsing_invalid_marker_orient_yields_error() {
assert!(MarkerOrient::parse_str("").is_err());
assert!(MarkerOrient::parse_str("blah").is_err());
assert!(MarkerOrient::parse_str("45blah").is_err());
}
#[test]
fn parses_marker_orient() {
assert_eq!(MarkerOrient::parse_str("auto").unwrap(), MarkerOrient::Auto);
assert_eq!(
MarkerOrient::parse_str("auto-start-reverse").unwrap(),
MarkerOrient::AutoStartReverse
);
assert_eq!(
MarkerOrient::parse_str("0").unwrap(),
MarkerOrient::Angle(Angle::new(0.0))
);
assert_eq!(
MarkerOrient::parse_str("180").unwrap(),
MarkerOrient::Angle(Angle::from_degrees(180.0))
);
assert_eq!(
MarkerOrient::parse_str("180deg").unwrap(),
MarkerOrient::Angle(Angle::from_degrees(180.0))
);
assert_eq!(
MarkerOrient::parse_str("-400grad").unwrap(),
MarkerOrient::Angle(Angle::from_degrees(-360.0))
);
assert_eq!(
MarkerOrient::parse_str("1rad").unwrap(),
MarkerOrient::Angle(Angle::new(1.0))
);
}
}
#[cfg(test)]
mod directionality_tests {
use super::*;
use crate::path_builder::PathBuilder;
fn setup_open_path() -> Segments {
let mut builder = PathBuilder::default();
builder.move_to(10.0, 10.0);
builder.line_to(20.0, 10.0);
builder.line_to(20.0, 20.0);
Segments::from(&builder.into_path())
}
#[test]
fn path_to_segments_handles_open_path() {
let expected_segments: Segments = Segments(vec![
Segment::line(10.0, 10.0, 20.0, 10.0),
Segment::line(20.0, 10.0, 20.0, 20.0),
]);
assert_eq!(setup_open_path(), expected_segments);
}
fn setup_multiple_open_subpaths() -> Segments {
let mut builder = PathBuilder::default();
builder.move_to(10.0, 10.0);
builder.line_to(20.0, 10.0);
builder.line_to(20.0, 20.0);
builder.move_to(30.0, 30.0);
builder.line_to(40.0, 30.0);
builder.curve_to(50.0, 35.0, 60.0, 60.0, 70.0, 70.0);
builder.line_to(80.0, 90.0);
Segments::from(&builder.into_path())
}
#[test]
fn path_to_segments_handles_multiple_open_subpaths() {
let expected_segments: Segments = Segments(vec![
Segment::line(10.0, 10.0, 20.0, 10.0),
Segment::line(20.0, 10.0, 20.0, 20.0),
Segment::line(30.0, 30.0, 40.0, 30.0),
Segment::curve(40.0, 30.0, 50.0, 35.0, 60.0, 60.0, 70.0, 70.0),
Segment::line(70.0, 70.0, 80.0, 90.0),
]);
assert_eq!(setup_multiple_open_subpaths(), expected_segments);
}
fn setup_closed_subpath() -> Segments {
let mut builder = PathBuilder::default();
builder.move_to(10.0, 10.0);
builder.line_to(20.0, 10.0);
builder.line_to(20.0, 20.0);
builder.close_path();
Segments::from(&builder.into_path())
}
#[test]
fn path_to_segments_handles_closed_subpath() {
let expected_segments: Segments = Segments(vec![
Segment::line(10.0, 10.0, 20.0, 10.0),
Segment::line(20.0, 10.0, 20.0, 20.0),
Segment::line(20.0, 20.0, 10.0, 10.0),
]);
assert_eq!(setup_closed_subpath(), expected_segments);
}
fn setup_multiple_closed_subpaths() -> Segments {
let mut builder = PathBuilder::default();
builder.move_to(10.0, 10.0);
builder.line_to(20.0, 10.0);
builder.line_to(20.0, 20.0);
builder.close_path();
builder.move_to(30.0, 30.0);
builder.line_to(40.0, 30.0);
builder.curve_to(50.0, 35.0, 60.0, 60.0, 70.0, 70.0);
builder.line_to(80.0, 90.0);
builder.close_path();
Segments::from(&builder.into_path())
}
#[test]
fn path_to_segments_handles_multiple_closed_subpaths() {
let expected_segments: Segments = Segments(vec![
Segment::line(10.0, 10.0, 20.0, 10.0),
Segment::line(20.0, 10.0, 20.0, 20.0),
Segment::line(20.0, 20.0, 10.0, 10.0),
Segment::line(30.0, 30.0, 40.0, 30.0),
Segment::curve(40.0, 30.0, 50.0, 35.0, 60.0, 60.0, 70.0, 70.0),
Segment::line(70.0, 70.0, 80.0, 90.0),
Segment::line(80.0, 90.0, 30.0, 30.0),
]);
assert_eq!(setup_multiple_closed_subpaths(), expected_segments);
}
fn setup_no_moveto_after_closepath() -> Segments {
let mut builder = PathBuilder::default();
builder.move_to(10.0, 10.0);
builder.line_to(20.0, 10.0);
builder.line_to(20.0, 20.0);
builder.close_path();
builder.line_to(40.0, 30.0);
Segments::from(&builder.into_path())
}
#[test]
fn path_to_segments_handles_no_moveto_after_closepath() {
let expected_segments: Segments = Segments(vec![
Segment::line(10.0, 10.0, 20.0, 10.0),
Segment::line(20.0, 10.0, 20.0, 20.0),
Segment::line(20.0, 20.0, 10.0, 10.0),
Segment::line(10.0, 10.0, 40.0, 30.0),
]);
assert_eq!(setup_no_moveto_after_closepath(), expected_segments);
}
#[test]
fn degenerate_segment_has_no_directionality() {
let s = Segment::degenerate(1.0, 2.0);
assert!(s.get_directionalities().is_none());
}
#[test]
fn line_segment_has_directionality() {
let s = Segment::line(1.0, 2.0, 3.0, 4.0);
let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
assert_eq!((2.0, 2.0), (v1x, v1y));
assert_eq!((2.0, 2.0), (v2x, v2y));
}
#[test]
fn line_segment_with_coincident_ends_has_no_directionality() {
let s = Segment::line(1.0, 2.0, 1.0, 2.0);
assert!(s.get_directionalities().is_none());
}
#[test]
fn curve_has_directionality() {
let s = Segment::curve(1.0, 2.0, 3.0, 5.0, 8.0, 13.0, 20.0, 33.0);
let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
assert_eq!((2.0, 3.0), (v1x, v1y));
assert_eq!((12.0, 20.0), (v2x, v2y));
}
#[test]
fn curves_with_loops_and_coincident_ends_have_directionality() {
let s = Segment::curve(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 1.0, 2.0);
let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
assert_eq!((2.0, 2.0), (v1x, v1y));
assert_eq!((-4.0, -4.0), (v2x, v2y));
let s = Segment::curve(1.0, 2.0, 1.0, 2.0, 3.0, 4.0, 1.0, 2.0);
let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
assert_eq!((2.0, 2.0), (v1x, v1y));
assert_eq!((-2.0, -2.0), (v2x, v2y));
let s = Segment::curve(1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 1.0, 2.0);
let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
assert_eq!((2.0, 2.0), (v1x, v1y));
assert_eq!((-2.0, -2.0), (v2x, v2y));
}
#[test]
fn curve_with_coincident_control_points_has_no_directionality() {
let s = Segment::curve(1.0, 2.0, 1.0, 2.0, 1.0, 2.0, 1.0, 2.0);
assert!(s.get_directionalities().is_none());
}
#[test]
fn curve_with_123_coincident_has_directionality() {
let s = Segment::curve(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 40.0);
let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
assert_eq!((20.0, 40.0), (v1x, v1y));
assert_eq!((20.0, 40.0), (v2x, v2y));
}
#[test]
fn curve_with_234_coincident_has_directionality() {
let s = Segment::curve(20.0, 40.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
assert_eq!((-20.0, -40.0), (v1x, v1y));
assert_eq!((-20.0, -40.0), (v2x, v2y));
}
#[test]
fn curve_with_12_34_coincident_has_directionality() {
let s = Segment::curve(20.0, 40.0, 20.0, 40.0, 60.0, 70.0, 60.0, 70.0);
let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
assert_eq!((40.0, 30.0), (v1x, v1y));
assert_eq!((40.0, 30.0), (v2x, v2y));
}
}
#[cfg(test)]
mod marker_tests {
use super::*;
use crate::path_builder::PathBuilder;
#[test]
fn emits_for_open_subpath() {
let mut builder = PathBuilder::default();
builder.move_to(0.0, 0.0);
builder.line_to(1.0, 0.0);
builder.line_to(1.0, 1.0);
builder.line_to(0.0, 1.0);
let mut v = Vec::new();
assert!(emit_markers_for_path(
&builder.into_path(),
BoundingBox::new(),
&mut |marker_type: MarkerType,
x: f64,
y: f64,
computed_angle: Angle|
-> Result<BoundingBox, InternalRenderingError> {
v.push((marker_type, x, y, computed_angle));
Ok(BoundingBox::new())
}
)
.is_ok());
assert_eq!(
v,
vec![
(MarkerType::Start, 0.0, 0.0, Angle::new(0.0)),
(MarkerType::Middle, 1.0, 0.0, Angle::from_vector(1.0, 1.0)),
(MarkerType::Middle, 1.0, 1.0, Angle::from_vector(-1.0, 1.0)),
(MarkerType::End, 0.0, 1.0, Angle::from_vector(-1.0, 0.0)),
]
);
}
#[test]
fn emits_for_closed_subpath() {
let mut builder = PathBuilder::default();
builder.move_to(0.0, 0.0);
builder.line_to(1.0, 0.0);
builder.line_to(1.0, 1.0);
builder.line_to(0.0, 1.0);
builder.close_path();
let mut v = Vec::new();
assert!(emit_markers_for_path(
&builder.into_path(),
BoundingBox::new(),
&mut |marker_type: MarkerType,
x: f64,
y: f64,
computed_angle: Angle|
-> Result<BoundingBox, InternalRenderingError> {
v.push((marker_type, x, y, computed_angle));
Ok(BoundingBox::new())
}
)
.is_ok());
assert_eq!(
v,
vec![
(MarkerType::Start, 0.0, 0.0, Angle::new(0.0)),
(MarkerType::Middle, 1.0, 0.0, Angle::from_vector(1.0, 1.0)),
(MarkerType::Middle, 1.0, 1.0, Angle::from_vector(-1.0, 1.0)),
(MarkerType::Middle, 0.0, 1.0, Angle::from_vector(-1.0, -1.0)),
(MarkerType::End, 0.0, 0.0, Angle::from_vector(1.0, -1.0)),
]
);
}
}