| 1 | // Copyright 2018 the Resvg Authors |
| 2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
| 3 | |
| 4 | use std::sync::Arc; |
| 5 | |
| 6 | use svgtypes::Length; |
| 7 | use tiny_skia_path::Path; |
| 8 | |
| 9 | use super::svgtree::{AId, EId, SvgNode}; |
| 10 | use super::{converter, units}; |
| 11 | use crate::{ApproxEqUlps, IsValidLength, Rect}; |
| 12 | |
| 13 | pub(crate) fn convert(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> { |
| 14 | match node.tag_name()? { |
| 15 | EId::Rect => convert_rect(node, state), |
| 16 | EId::Circle => convert_circle(node, state), |
| 17 | EId::Ellipse => convert_ellipse(node, state), |
| 18 | EId::Line => convert_line(node, state), |
| 19 | EId::Polyline => convert_polyline(node), |
| 20 | EId::Polygon => convert_polygon(node), |
| 21 | EId::Path => convert_path(node), |
| 22 | _ => None, |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | pub(crate) fn convert_path(node: SvgNode) -> Option<Arc<Path>> { |
| 27 | let value: &str = node.attribute(AId::D)?; |
| 28 | let mut builder = tiny_skia_path::PathBuilder::new(); |
| 29 | for segment in svgtypes::SimplifyingPathParser::from(value) { |
| 30 | let segment = match segment { |
| 31 | Ok(v) => v, |
| 32 | Err(_) => break, |
| 33 | }; |
| 34 | |
| 35 | match segment { |
| 36 | svgtypes::SimplePathSegment::MoveTo { x, y } => { |
| 37 | builder.move_to(x as f32, y as f32); |
| 38 | } |
| 39 | svgtypes::SimplePathSegment::LineTo { x, y } => { |
| 40 | builder.line_to(x as f32, y as f32); |
| 41 | } |
| 42 | svgtypes::SimplePathSegment::Quadratic { x1, y1, x, y } => { |
| 43 | builder.quad_to(x1 as f32, y1 as f32, x as f32, y as f32); |
| 44 | } |
| 45 | svgtypes::SimplePathSegment::CurveTo { |
| 46 | x1, |
| 47 | y1, |
| 48 | x2, |
| 49 | y2, |
| 50 | x, |
| 51 | y, |
| 52 | } => { |
| 53 | builder.cubic_to( |
| 54 | x1 as f32, y1 as f32, x2 as f32, y2 as f32, x as f32, y as f32, |
| 55 | ); |
| 56 | } |
| 57 | svgtypes::SimplePathSegment::ClosePath => { |
| 58 | builder.close(); |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | builder.finish().map(Arc::new) |
| 64 | } |
| 65 | |
| 66 | fn convert_rect(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> { |
| 67 | // 'width' and 'height' attributes must be positive and non-zero. |
| 68 | let width = node.convert_user_length(AId::Width, state, Length::zero()); |
| 69 | let height = node.convert_user_length(AId::Height, state, Length::zero()); |
| 70 | if !width.is_valid_length() { |
| 71 | log::warn!( |
| 72 | "Rect ' {}' has an invalid 'width' value. Skipped." , |
| 73 | node.element_id() |
| 74 | ); |
| 75 | return None; |
| 76 | } |
| 77 | if !height.is_valid_length() { |
| 78 | log::warn!( |
| 79 | "Rect ' {}' has an invalid 'height' value. Skipped." , |
| 80 | node.element_id() |
| 81 | ); |
| 82 | return None; |
| 83 | } |
| 84 | |
| 85 | let x = node.convert_user_length(AId::X, state, Length::zero()); |
| 86 | let y = node.convert_user_length(AId::Y, state, Length::zero()); |
| 87 | |
| 88 | let (mut rx, mut ry) = resolve_rx_ry(node, state); |
| 89 | |
| 90 | // Clamp rx/ry to the half of the width/height. |
| 91 | // |
| 92 | // Should be done only after resolving. |
| 93 | if rx > width / 2.0 { |
| 94 | rx = width / 2.0; |
| 95 | } |
| 96 | if ry > height / 2.0 { |
| 97 | ry = height / 2.0; |
| 98 | } |
| 99 | |
| 100 | // Conversion according to https://www.w3.org/TR/SVG11/shapes.html#RectElement |
| 101 | let path = if rx.approx_eq_ulps(&0.0, 4) { |
| 102 | tiny_skia_path::PathBuilder::from_rect(Rect::from_xywh(x, y, width, height)?) |
| 103 | } else { |
| 104 | let mut builder = tiny_skia_path::PathBuilder::new(); |
| 105 | builder.move_to(x + rx, y); |
| 106 | |
| 107 | builder.line_to(x + width - rx, y); |
| 108 | builder.arc_to(rx, ry, 0.0, false, true, x + width, y + ry); |
| 109 | |
| 110 | builder.line_to(x + width, y + height - ry); |
| 111 | builder.arc_to(rx, ry, 0.0, false, true, x + width - rx, y + height); |
| 112 | |
| 113 | builder.line_to(x + rx, y + height); |
| 114 | builder.arc_to(rx, ry, 0.0, false, true, x, y + height - ry); |
| 115 | |
| 116 | builder.line_to(x, y + ry); |
| 117 | builder.arc_to(rx, ry, 0.0, false, true, x + rx, y); |
| 118 | |
| 119 | builder.close(); |
| 120 | |
| 121 | builder.finish()? |
| 122 | }; |
| 123 | |
| 124 | Some(Arc::new(path)) |
| 125 | } |
| 126 | |
| 127 | fn resolve_rx_ry(node: SvgNode, state: &converter::State) -> (f32, f32) { |
| 128 | let mut rx_opt = node.attribute::<Length>(AId::Rx); |
| 129 | let mut ry_opt = node.attribute::<Length>(AId::Ry); |
| 130 | |
| 131 | // Remove negative values first. |
| 132 | if let Some(v) = rx_opt { |
| 133 | if v.number.is_sign_negative() { |
| 134 | rx_opt = None; |
| 135 | } |
| 136 | } |
| 137 | if let Some(v) = ry_opt { |
| 138 | if v.number.is_sign_negative() { |
| 139 | ry_opt = None; |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | // Resolve. |
| 144 | match (rx_opt, ry_opt) { |
| 145 | (None, None) => (0.0, 0.0), |
| 146 | (Some(rx), None) => { |
| 147 | let rx = units::convert_user_length(rx, node, AId::Rx, state); |
| 148 | (rx, rx) |
| 149 | } |
| 150 | (None, Some(ry)) => { |
| 151 | let ry = units::convert_user_length(ry, node, AId::Ry, state); |
| 152 | (ry, ry) |
| 153 | } |
| 154 | (Some(rx), Some(ry)) => { |
| 155 | let rx = units::convert_user_length(rx, node, AId::Rx, state); |
| 156 | let ry = units::convert_user_length(ry, node, AId::Ry, state); |
| 157 | (rx, ry) |
| 158 | } |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | fn convert_line(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> { |
| 163 | let x1: f32 = node.convert_user_length(AId::X1, state, def:Length::zero()); |
| 164 | let y1: f32 = node.convert_user_length(AId::Y1, state, def:Length::zero()); |
| 165 | let x2: f32 = node.convert_user_length(AId::X2, state, def:Length::zero()); |
| 166 | let y2: f32 = node.convert_user_length(AId::Y2, state, def:Length::zero()); |
| 167 | |
| 168 | let mut builder: PathBuilder = tiny_skia_path::PathBuilder::new(); |
| 169 | builder.move_to(x:x1, y:y1); |
| 170 | builder.line_to(x:x2, y:y2); |
| 171 | builder.finish().map(Arc::new) |
| 172 | } |
| 173 | |
| 174 | fn convert_polyline(node: SvgNode) -> Option<Arc<Path>> { |
| 175 | let builder: PathBuilder = points_to_path(node, eid:"Polyline" )?; |
| 176 | builder.finish().map(Arc::new) |
| 177 | } |
| 178 | |
| 179 | fn convert_polygon(node: SvgNode) -> Option<Arc<Path>> { |
| 180 | let mut builder: PathBuilder = points_to_path(node, eid:"Polygon" )?; |
| 181 | builder.close(); |
| 182 | builder.finish().map(Arc::new) |
| 183 | } |
| 184 | |
| 185 | fn points_to_path(node: SvgNode, eid: &str) -> Option<tiny_skia_path::PathBuilder> { |
| 186 | use svgtypes::PointsParser; |
| 187 | |
| 188 | let mut builder = tiny_skia_path::PathBuilder::new(); |
| 189 | match node.attribute::<&str>(AId::Points) { |
| 190 | Some(text) => { |
| 191 | for (x, y) in PointsParser::from(text) { |
| 192 | if builder.is_empty() { |
| 193 | builder.move_to(x as f32, y as f32); |
| 194 | } else { |
| 195 | builder.line_to(x as f32, y as f32); |
| 196 | } |
| 197 | } |
| 198 | } |
| 199 | _ => { |
| 200 | log::warn!( |
| 201 | " {} ' {}' has an invalid 'points' value. Skipped." , |
| 202 | eid, |
| 203 | node.element_id() |
| 204 | ); |
| 205 | return None; |
| 206 | } |
| 207 | }; |
| 208 | |
| 209 | // 'polyline' and 'polygon' elements must contain at least 2 points. |
| 210 | if builder.len() < 2 { |
| 211 | log::warn!( |
| 212 | " {} ' {}' has less than 2 points. Skipped." , |
| 213 | eid, |
| 214 | node.element_id() |
| 215 | ); |
| 216 | return None; |
| 217 | } |
| 218 | |
| 219 | Some(builder) |
| 220 | } |
| 221 | |
| 222 | fn convert_circle(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> { |
| 223 | let cx: f32 = node.convert_user_length(AId::Cx, state, def:Length::zero()); |
| 224 | let cy: f32 = node.convert_user_length(AId::Cy, state, def:Length::zero()); |
| 225 | let r: f32 = node.convert_user_length(AId::R, state, def:Length::zero()); |
| 226 | |
| 227 | if !r.is_valid_length() { |
| 228 | log::warn!( |
| 229 | "Circle ' {}' has an invalid 'r' value. Skipped." , |
| 230 | node.element_id() |
| 231 | ); |
| 232 | return None; |
| 233 | } |
| 234 | |
| 235 | ellipse_to_path(cx, cy, rx:r, ry:r) |
| 236 | } |
| 237 | |
| 238 | fn convert_ellipse(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> { |
| 239 | let cx: f32 = node.convert_user_length(AId::Cx, state, def:Length::zero()); |
| 240 | let cy: f32 = node.convert_user_length(AId::Cy, state, def:Length::zero()); |
| 241 | let (rx: f32, ry: f32) = resolve_rx_ry(node, state); |
| 242 | |
| 243 | if !rx.is_valid_length() { |
| 244 | log::warn!( |
| 245 | "Ellipse ' {}' has an invalid 'rx' value. Skipped." , |
| 246 | node.element_id() |
| 247 | ); |
| 248 | return None; |
| 249 | } |
| 250 | |
| 251 | if !ry.is_valid_length() { |
| 252 | log::warn!( |
| 253 | "Ellipse ' {}' has an invalid 'ry' value. Skipped." , |
| 254 | node.element_id() |
| 255 | ); |
| 256 | return None; |
| 257 | } |
| 258 | |
| 259 | ellipse_to_path(cx, cy, rx, ry) |
| 260 | } |
| 261 | |
| 262 | fn ellipse_to_path(cx: f32, cy: f32, rx: f32, ry: f32) -> Option<Arc<Path>> { |
| 263 | let mut builder: PathBuilder = tiny_skia_path::PathBuilder::new(); |
| 264 | builder.move_to(x:cx + rx, y:cy); |
| 265 | builder.arc_to(rx, ry, x_axis_rotation:0.0, large_arc:false, sweep:true, x:cx, y:cy + ry); |
| 266 | builder.arc_to(rx, ry, x_axis_rotation:0.0, large_arc:false, sweep:true, x:cx - rx, y:cy); |
| 267 | builder.arc_to(rx, ry, x_axis_rotation:0.0, large_arc:false, sweep:true, x:cx, y:cy - ry); |
| 268 | builder.arc_to(rx, ry, x_axis_rotation:0.0, large_arc:false, sweep:true, x:cx + rx, y:cy); |
| 269 | builder.close(); |
| 270 | builder.finish().map(Arc::new) |
| 271 | } |
| 272 | |
| 273 | trait PathBuilderExt { |
| 274 | fn arc_to( |
| 275 | &mut self, |
| 276 | rx: f32, |
| 277 | ry: f32, |
| 278 | x_axis_rotation: f32, |
| 279 | large_arc: bool, |
| 280 | sweep: bool, |
| 281 | x: f32, |
| 282 | y: f32, |
| 283 | ); |
| 284 | } |
| 285 | |
| 286 | impl PathBuilderExt for tiny_skia_path::PathBuilder { |
| 287 | fn arc_to( |
| 288 | &mut self, |
| 289 | rx: f32, |
| 290 | ry: f32, |
| 291 | x_axis_rotation: f32, |
| 292 | large_arc: bool, |
| 293 | sweep: bool, |
| 294 | x: f32, |
| 295 | y: f32, |
| 296 | ) { |
| 297 | let prev = match self.last_point() { |
| 298 | Some(v) => v, |
| 299 | None => return, |
| 300 | }; |
| 301 | |
| 302 | let svg_arc = kurbo::SvgArc { |
| 303 | from: kurbo::Point::new(prev.x as f64, prev.y as f64), |
| 304 | to: kurbo::Point::new(x as f64, y as f64), |
| 305 | radii: kurbo::Vec2::new(rx as f64, ry as f64), |
| 306 | x_rotation: (x_axis_rotation as f64).to_radians(), |
| 307 | large_arc, |
| 308 | sweep, |
| 309 | }; |
| 310 | |
| 311 | match kurbo::Arc::from_svg_arc(&svg_arc) { |
| 312 | Some(arc) => { |
| 313 | arc.to_cubic_beziers(0.1, |p1, p2, p| { |
| 314 | self.cubic_to( |
| 315 | p1.x as f32, |
| 316 | p1.y as f32, |
| 317 | p2.x as f32, |
| 318 | p2.y as f32, |
| 319 | p.x as f32, |
| 320 | p.y as f32, |
| 321 | ); |
| 322 | }); |
| 323 | } |
| 324 | None => { |
| 325 | self.line_to(x, y); |
| 326 | } |
| 327 | } |
| 328 | } |
| 329 | } |
| 330 | |