1pub use {
2 std::{
3 rc::Rc,
4 sync::Arc,
5 cell::RefCell,
6 io::prelude::*,
7 fs::File,
8 collections::HashMap,
9 },
10 makepad_html::*,
11 crate::{
12 draw_list_2d::DrawListExt,
13 shader::draw_trapezoid::DrawTrapezoidVector,
14 makepad_platform::*,
15 cx_draw::CxDraw,
16 turtle::{Walk, Layout},
17 draw_list_2d::{ManyInstances, DrawList2d, RedrawingApi},
18 geometry::GeometryQuad2D,
19 makepad_vector::trapezoidator::Trapezoidator,
20 makepad_vector::geometry::{AffineTransformation, Transform, Vector, Point},
21 makepad_vector::internal_iter::*,
22 makepad_vector::path::{PathIterator, PathCommand},
23 }
24};
25
26#[derive(Clone, Copy)]
27pub struct CxIconSlot {
28 pub t1: Vec2,
29 pub t2: Vec2,
30 pub chan: f32
31}
32
33#[derive(Clone)]
34pub struct CxIconEntry {
35 path_hash: CxIconPathHash,
36 pos: DVec2,
37 slot: CxIconSlot,
38 args: CxIconArgs,
39}
40
41struct CxIconPathCommands {
42 bounds: Rect,
43 path: Vec<PathCommand>
44}
45
46impl<'a> InternalIterator for &CxIconPathCommands {
47 type Item = PathCommand;
48 fn for_each<F>(self, f: &mut F) -> bool
49 where
50 F: FnMut(PathCommand) -> bool,
51 {
52 for item in &self.path {
53 if !f(item.clone()) {
54 return false
55 }
56 }
57 true
58 }
59}
60
61#[derive(Clone, Copy, Hash, PartialEq, Eq)]
62pub struct CxIconPathHash(LiveId);
63
64#[derive(Clone, Copy, Hash, PartialEq, Eq)]
65pub struct CxIconEntryHash(LiveId);
66
67pub struct CxIconAtlas {
68 pub texture: Texture,
69 pub clear_buffer: bool,
70 svg_deps: HashMap<String, CxIconPathHash>,
71 paths: HashMap<CxIconPathHash, Vec<CxIconPathCommands>>,
72 entries: HashMap<CxIconEntryHash, CxIconEntry>,
73 alloc: CxIconAtlasAlloc
74}
75
76#[derive(Default)]
77pub struct CxIconAtlasAlloc {
78 pub texture_size: DVec2,
79 pub xpos: f64,
80 pub ypos: f64,
81 pub hmax: f64,
82 pub todo: Vec<CxIconEntryHash>,
83}
84
85#[derive(Clone, Debug)]
86pub struct CxIconArgs {
87 pub linearize: f64,
88 pub size: DVec2,
89 pub translate: DVec2,
90 pub subpixel: DVec2,
91 pub scale: f64,
92}
93
94impl CxIconArgs {
95 fn hash(&self) -> LiveId {
96 LiveId::seeded()
97 .bytes_append(&self.linearize.to_be_bytes())
98 .bytes_append(&self.translate.x.to_be_bytes())
99 .bytes_append(&self.translate.y.to_be_bytes())
100 .bytes_append(&self.subpixel.x.to_be_bytes())
101 .bytes_append(&self.subpixel.y.to_be_bytes())
102 .bytes_append(&self.scale.to_be_bytes())
103 .bytes_append(&self.size.x.to_be_bytes())
104 .bytes_append(&self.size.y.to_be_bytes())
105 }
106}
107
108impl CxIconAtlas {
109 pub fn new(texture: Texture) -> Self {
110 Self {
111 texture,
112 clear_buffer: false,
113 entries: HashMap::new(),
114 svg_deps: HashMap::new(),
115 paths: HashMap::new(),
116 alloc: CxIconAtlasAlloc {
117 texture_size: DVec2 {x: 2048.0, y: 2048.0},
118 xpos: 0.0,
119 ypos: 0.0,
120 hmax: 0.0,
121 todo: Vec::new(),
122 }
123 }
124 }
125
126 pub fn parse_and_cache_path(&mut self, path_hash: CxIconPathHash, path: &[u8]) -> Option<(CxIconPathHash, Rect)> {
127 match parse_svg_path(path) {
128 Ok(path) => {
129 let mut min = dvec2(f64::INFINITY, f64::INFINITY);
130 let mut max = dvec2(-f64::INFINITY, -f64::INFINITY);
131 fn bound(p: &Point, min: &mut DVec2, max: &mut DVec2) {
132 if p.x < min.x {min.x = p.x}
133 if p.y < min.y {min.y = p.y}
134 if p.x > max.x {max.x = p.x}
135 if p.y > max.y {max.y = p.y}
136 }
137 for cmd in &path {
138 match cmd {
139 PathCommand::MoveTo(p) => {bound(p, &mut min, &mut max)},
140 PathCommand::LineTo(p) => {bound(p, &mut min, &mut max)},
141 PathCommand::ArcTo(e, r, _, _, _) => {
142 bound(&Point{x: e.x + r.x, y: e.y + r.y}, &mut min, &mut max);
144 bound(&Point{x: e.x - r.x, y: e.y - r.y}, &mut min, &mut max);
145 },
146 PathCommand::QuadraticTo(p1, p) => {
147 bound(p1, &mut min, &mut max);
148 bound(p, &mut min, &mut max);
149 },
150 PathCommand::CubicTo(p1, p2, p) => {
151 bound(p1, &mut min, &mut max);
152 bound(p2, &mut min, &mut max);
153 bound(p, &mut min, &mut max);
154 },
155 PathCommand::Close => ()
156 }
157 }
158 let bounds = Rect {pos: min, size: max - min};
159 if let Some( foundpath) = self.paths.get_mut(&path_hash) {
160 foundpath.push(CxIconPathCommands {
161 bounds,
162 path
163 })
164 }
165 else
166 {
167
168
169 self.paths.insert(path_hash,vec![ CxIconPathCommands {
170 bounds,
171 path
172 }]);
173 }
174 return Some((path_hash, bounds));
175 }
176 Err(e) => {
177 log!("Error in SVG Path {}", e);
178 return None
179 }
180 }
181 }
182
183
184 pub fn get_icon_bounds(&mut self, cx: &Cx, path_str: &Arc<String>, svg_dep: &Arc<String>) -> Option<(CxIconPathHash, Rect)> {
185 if svg_dep.len() != 0 {
186 if let Some(path_hash) = self.svg_deps.get(svg_dep.as_str()) {
188 if let Some(path) = self.paths.get(&path_hash) {
189 let mut bounds:Rect = path[0].bounds;
190 for i in 1..path.len(){
191 bounds = bounds.hull(path[i].bounds);
192 }
193 return Some((*path_hash, bounds))
194 }
195 return None
196 }
197 let path_hash = CxIconPathHash(LiveId(self.svg_deps.len() as u64));
198 self.svg_deps.insert(svg_dep.as_str().to_string(), path_hash);
199 match cx.get_dependency(svg_dep.as_str()) {
201 Ok(data)=>{
202
203 let mut errors = Some(Vec::new());
204 let svg_string = std::str::from_utf8(&data).unwrap();
205 let doc = parse_html(svg_string, &mut errors, InternLiveId::No);
206
207 if errors.as_ref().unwrap().len()>0{
208 log!("SVG parser returned errors {:?}", errors)
209 }
210 let mut node = doc.new_walker();
211
212 while !node.done(){
213 match node.open_tag_lc()
214 {
215 some_id!(g)=>{
216 }
218 some_id!(path)=>{
219 self.parse_and_cache_path(path_hash, node.find_attr_lc(live_id!(d)).unwrap().as_bytes());
220 }
221
222 _=>()
223 }
224 match node.close_tag_lc()
225 {
226 some_id!(g)=>
227 {
228
229 }
230 _=>()
231 }
232 node.walk();
233 }
234
235
236 if let Some(path) = self.paths.get(&path_hash) {
237 let mut bounds:Rect = path[0].bounds;
238 for i in 1..path.len(){
239 bounds = bounds.hull(path[i].bounds);
240 }
241 return Some((path_hash, bounds));
242 }
243
244 error!("No SVG path tag found in svg file {}",path_str);
245 return None
246
247 }
248 Err(_err)=>{
249 error!("Error in SVG file {}: {}",path_str, _err);
250 return None
251 }
252 }
253 }
254 if path_str.len() == 0 {
255 return None
256 }
257 let path_hash = CxIconPathHash(LiveId(Arc::as_ptr(path_str) as u64));
258 if let Some(path) = self.paths.get(&path_hash) {
259 let mut bounds:Rect = path[0].bounds;
260 for i in 1..path.len(){
261 bounds = bounds.hull(path[i].bounds);
262 }
263 return Some((path_hash,bounds))
264 }
265 self.parse_and_cache_path(path_hash, path_str.as_str().as_bytes())
266 }
267
268 pub fn get_icon_slot(&mut self, args: CxIconArgs, path_hash: CxIconPathHash) -> CxIconSlot {
269 let entry_hash = CxIconEntryHash(path_hash.0.id_append(args.hash()));
270
271 if let Some(entry) = self.entries.get(&entry_hash) {
272 return entry.slot
273 }
274 let (slot,pos) = self.alloc.alloc_icon_slot(args.size.x as f64, args.size.y as f64);
275 self.entries.insert(
276 entry_hash,
277 CxIconEntry {
278 path_hash,
279 slot,
280 pos,
281 args
282 }
283 );
284 self.alloc.todo.push(entry_hash);
285
286 return slot
287 }
288
289}
290impl CxIconAtlasAlloc {
291 pub fn alloc_icon_slot(&mut self, w: f64, h: f64) -> (CxIconSlot,DVec2) {
292 if w + self.xpos >= self.texture_size.x {
293 self.xpos = 0.0;
294 self.ypos += self.hmax + 1.0;
295 self.hmax = 0.0;
296 }
297 if h + self.ypos >= self.texture_size.y {
298 error!("ICON ATLAS FULL, TODO FIX THIS {} > {},", h + self.ypos, self.texture_size.y);
299 }
300 if h > self.hmax {
301 self.hmax = h;
302 }
303
304 let px = self.xpos;
305 let py = self.ypos;
306
307 let tx1 = px / self.texture_size.x;
308 let ty1 = py / self.texture_size.y;
309
310 self.xpos += w + 1.0;
311
312 (CxIconSlot {
313 chan: 0.0,
314 t1: dvec2(tx1, ty1).into(),
315 t2: dvec2(tx1 + (w / self.texture_size.x), ty1 + (h / self.texture_size.y)).into()
316 },dvec2(px, py).into())
317 }
318}
319
320#[derive(Clone)]
321pub struct CxIconAtlasRc(pub Rc<RefCell<CxIconAtlas >>);
322
323impl CxIconAtlas {
324 pub fn reset_icon_atlas(&mut self) {
325 self.entries.clear();
326 self.alloc.xpos = 0.;
327 self.alloc.ypos = 0.;
328 self.alloc.hmax = 0.;
329 self.clear_buffer = true;
330 }
331
332 pub fn get_internal_atlas_texture(&self) -> &Texture {
333 &self.texture
334 }
335}
336
337
338impl DrawTrapezoidVector {
339 fn draw_vector(&mut self, entry: &CxIconEntry, path: &CxIconPathCommands, many: &mut ManyInstances) {
341 let trapezoids = {
342 let mut trapezoids = Vec::new();
343 let trapezoidate = self.trapezoidator.trapezoidate(
345 path.map({
346 move | cmd | {
348 let cmd = cmd.transform(
349 &AffineTransformation::identity()
350 .translate(Vector::new(entry.args.translate.x, entry.args.translate.y))
351 .uniform_scale(entry.args.scale)
352 .translate(Vector::new(entry.pos.x + entry.args.subpixel.x, entry.pos.y + entry.args.subpixel.y))
353 );
354 cmd
355 }
356 }).linearize(entry.args.linearize)
357 );
358 if let Some(trapezoidate) = trapezoidate {
359 trapezoids.extend_from_internal_iter(
360 trapezoidate
361 );
362 }
363 trapezoids
364 };
365
366 for trapezoid in trapezoids {
367 self.a_xs = Vec2 {x: trapezoid.xs[0], y: trapezoid.xs[1]};
368 self.a_ys = Vec4 {x: trapezoid.ys[0], y: trapezoid.ys[1], z: trapezoid.ys[2], w: trapezoid.ys[3]};
369 self.chan = 0.0 as f32;
370 many.instances.extend_from_slice(self.draw_vars.as_slice());
371 }
372 }
373}
374
375#[derive(Clone)]
376pub struct CxDrawIconAtlasRc(pub Rc<RefCell<CxDrawIconAtlas >>);
377
378pub struct CxDrawIconAtlas {
379 pub draw_trapezoid: DrawTrapezoidVector,
380 pub atlas_pass: Pass,
381 pub atlas_draw_list: DrawList2d,
382 pub atlas_texture: Texture,
383}
384
385impl CxDrawIconAtlas {
386 pub fn new(cx: &mut Cx) -> Self {
387
388 let atlas_texture = Texture::new_with_format(cx, TextureFormat::RenderBGRAu8{
389 size: TextureSize::Auto,
390 initial: true,
391 });
392 let draw_trapezoid = DrawTrapezoidVector::new_local(cx);
395 Self {
397 draw_trapezoid,
398 atlas_pass: Pass::new(cx),
399 atlas_draw_list: DrawList2d::new(cx),
400 atlas_texture: atlas_texture
401 }
402 }
403}
404
405impl<'a> CxDraw<'a> {
406 pub fn lazy_construct_icon_atlas(cx: &mut Cx) {
407 if !cx.has_global::<CxIconAtlasRc>() {
409
410 let draw_atlas = CxDrawIconAtlas::new(cx);
411 let texture = draw_atlas.atlas_texture.clone();
412 cx.set_global(CxDrawIconAtlasRc(Rc::new(RefCell::new(draw_atlas))));
413
414 let atlas = CxIconAtlas::new(texture);
415 cx.set_global(CxIconAtlasRc(Rc::new(RefCell::new(atlas))));
416 }
417 }
418
419 pub fn reset_icon_atlas(cx: &mut Cx) {
420 if cx.has_global::<CxIconAtlasRc>() {
421 let mut fonts_atlas = cx.get_global::<CxIconAtlasRc>().0.borrow_mut();
422 fonts_atlas.reset_icon_atlas();
423 }
424 }
425
426 pub fn draw_icon_atlas(&mut self) {
427 let draw_atlas_rc = self.cx.get_global::<CxDrawIconAtlasRc>().clone();
428 let mut draw_atlas = draw_atlas_rc.0.borrow_mut();
429 let atlas_rc = self.icon_atlas_rc.clone();
430 let mut atlas = atlas_rc.0.borrow_mut();
431 let atlas = &mut*atlas;
432 if atlas.alloc.todo.len()>0 {
435 self.begin_pass(&draw_atlas.atlas_pass, None);
436
437 let texture_size = atlas.alloc.texture_size;
438 draw_atlas.atlas_pass.set_size(self.cx, texture_size);
439
440 let clear = if atlas.clear_buffer {
441 atlas.clear_buffer = false;
442 PassClearColor::ClearWith(Vec4::default())
443 }
444 else {
445 PassClearColor::InitWith(Vec4::default())
446 };
447
448 draw_atlas.atlas_pass.clear_color_textures(self.cx);
449 draw_atlas.atlas_pass.add_color_texture(self.cx, &draw_atlas.atlas_texture, clear);
450 draw_atlas.atlas_draw_list.begin_always(self);
451
452 let mut atlas_todo = Vec::new();
453 std::mem::swap(&mut atlas.alloc.todo, &mut atlas_todo);
454
455 if let Some(mut many) = self.begin_many_instances(&draw_atlas.draw_trapezoid.draw_vars) {
456 for todo in atlas_todo {
457 let entry = atlas.entries.get(&todo).unwrap();
458 let path = atlas.paths.get(&entry.path_hash).unwrap();
459 for i in 0..path.len(){
460 draw_atlas.draw_trapezoid.draw_vector(entry, &path[i], &mut many);
461 }
462
463 }
464
465 self.end_many_instances(many);
466 }
467 draw_atlas.atlas_draw_list.end(self);
468 self.end_pass(&draw_atlas.atlas_pass);
469 }
470 }
471
472
473}
474
475pub fn parse_svg_path(path: &[u8]) -> Result<Vec<PathCommand>, String> {
476 #[derive(Debug)]
477 enum Cmd {
478 Unknown,
479 Move(bool),
480 Hor(bool),
481 Vert(bool),
482 Line(bool),
483 Arc(bool),
484 Cubic(bool),
485 CubicSmooth(bool),
486 Quadratic(bool),
487 Close
488 }
489 impl Default for Cmd {fn default() -> Self {Self::Unknown}}
490
491 #[derive(Default)]
492 struct ParseState {
493 cmd: Cmd,
494 expect_nums: usize,
495 chain: bool,
496 nums: [f64; 7],
497 num_count: usize,
498 last_pt: Point,
499 first_pt: Point,
500 last_ctl_pt: Option<Point>,
501 out: Vec<PathCommand>,
502 num_state: Option<NumState>
503 }
504
505 #[derive(Debug)]
506 struct NumState {
507 num: f64,
508 mul: f64,
509 has_dot: bool,
510 }
511
512 impl NumState {
513 fn new_pos(v: f64) -> Self {Self {num: v, mul: 1.0, has_dot: false}}
514 fn new_min() -> Self {Self {num: 0.0, mul: -1.0, has_dot: false}}
515 fn finalize(self) -> f64 {self.num * self.mul}
516 fn add_digit(&mut self, digit: f64) {
517 self.num *= 10.0;
518 self.num += digit;
519 if self.has_dot {
520 self.mul *= 0.1;
521 }
522 }
523 }
524
525 impl ParseState {
526 fn next_cmd(&mut self, cmd: Cmd) -> Result<(), String> {
527 self.finalize_cmd() ?;
528 self.chain = false;
529 self.expect_nums = match cmd {
530 Cmd::Unknown => panic!(),
531 Cmd::Move(_) => 2,
532 Cmd::Hor(_) => 1,
533 Cmd::Vert(_) => 1,
534 Cmd::Line(_) => 2,
535 Cmd::Cubic(_) => 6,
536 Cmd::CubicSmooth(_) => 4,
537 Cmd::Arc(_) => 7,
538 Cmd::Quadratic(_) => 4,
539 Cmd::Close => 0
540 };
541 self.cmd = cmd;
542 Ok(())
543 }
544
545 fn add_min(&mut self) -> Result<(), String> {
546 if self.num_state.is_some() {
547 self.finalize_num();
548 }
549 if self.expect_nums == self.num_count {
550 self.finalize_cmd() ?;
551 }
552 if self.expect_nums == 0 {
553 return Err(format!("Unexpected minus"));
554 }
555 self.num_state = Some(NumState::new_min());
556 Ok(())
557 }
558
559 fn add_digit(&mut self, digit: f64) -> Result<(), String> {
560 if let Some(num_state) = &mut self.num_state {
561 num_state.add_digit(digit);
562 }
563 else {
564 if self.expect_nums == self.num_count {
565 self.finalize_cmd() ?;
566 }
567 if self.expect_nums == 0 {
568 return Err(format!("Unexpected digit"));
569 }
570 self.num_state = Some(NumState::new_pos(digit))
571 }
572 Ok(())
573 }
574
575 fn add_dot(&mut self) -> Result<(), String> {
576 if let Some(num_state) = &mut self.num_state {
577 if num_state.has_dot {
578 self.finalize_num();
579 self.add_digit(0.0) ?;
580 self.add_dot() ?;
581 return Ok(());
582 }
583 num_state.has_dot = true;
584 }
585 else {
586 self.add_digit(0.0) ?;
587 self.add_dot() ?;
588 }
589 Ok(())
590 }
591
592 fn finalize_num(&mut self) {
593 if let Some(num_state) = self.num_state.take() {
594 self.nums[self.num_count] = num_state.finalize();
595 self.num_count += 1;
596 }
597 }
598
599 fn whitespace(&mut self) -> Result<(), String> {
600 self.finalize_num();
601 if self.expect_nums == self.num_count {
602 self.finalize_cmd() ?;
603 }
604 Ok(())
605 }
606
607 fn finalize_cmd(&mut self) -> Result<(), String> {
608 self.finalize_num();
609 if self.chain && self.num_count == 0 {
610 return Ok(())
611 }
612 if self.expect_nums != self.num_count {
613 return Err(format!("SVG Path command {:?} expected {} points, got {}", self.cmd, self.expect_nums, self.num_count));
614 }
615 match self.cmd {
616 Cmd::Unknown => (),
617 Cmd::Move(abs) => {
618 self.last_ctl_pt = None;
619 if abs {
620 self.last_pt = Point {x: self.nums[0], y: self.nums[1]};
621 }
622 else {
623 self.last_pt += Vector {x: self.nums[0], y: self.nums[1]};
624 }
625 self.first_pt = self.last_pt;
626 self.out.push(PathCommand::MoveTo(self.last_pt));
627 },
628 Cmd::Hor(abs) => {
629 self.last_ctl_pt = None;
630 if abs {
631 self.last_pt = Point {x: self.nums[0], y: self.last_pt.y};
632 }
633 else {
634 self.last_pt += Vector {x: self.nums[0], y: 0.0};
635 }
636 self.out.push(PathCommand::LineTo(self.last_pt));
637 }
638 Cmd::Vert(abs) => {
639 self.last_ctl_pt = None;
640 if abs {
641 self.last_pt = Point {x: self.last_pt.x, y: self.nums[0]};
642 }
643 else {
644 self.last_pt += Vector {x: 0.0, y: self.nums[0]};
645 }
646 self.out.push(PathCommand::LineTo(self.last_pt));
647 }
648 Cmd::Line(abs) => {
649 self.last_ctl_pt = None;
650 if abs {
651 self.last_pt = Point {x: self.nums[0], y: self.nums[1]};
652 }
653 else {
654 self.last_pt += Vector {x: self.nums[0], y: self.nums[1]};
655 }
656 self.out.push(PathCommand::LineTo(self.last_pt));
657 },
658 Cmd::Cubic(abs) => {
659 if abs {
660 self.last_pt = Point {x: self.nums[4], y: self.nums[5]};
661 self.out.push(PathCommand::CubicTo(
662 Point {x: self.nums[0], y: self.nums[1]},
663 Point {x: self.nums[2], y: self.nums[3]},
664 self.last_pt,
665 ));
666 self.last_ctl_pt = Some(Point {x: self.nums[2], y: self.nums[3]});
667 } else {
668 self.out.push(PathCommand::CubicTo(
669 self.last_pt + Vector {x: self.nums[0], y: self.nums[1]},
670 self.last_pt + Vector {x: self.nums[2], y: self.nums[3]},
671 self.last_pt + Vector {x: self.nums[4], y: self.nums[5]},
672 ));
673 self.last_ctl_pt = Some(
674 self.last_pt + Vector {x: self.nums[2], y: self.nums[3]});
675 self.last_pt += Vector {x: self.nums[4], y: self.nums[5]};
676 }
677 },
678 Cmd::CubicSmooth(abs) => {
679 let a_cp1 = if self.last_ctl_pt.is_none() {
681 self.last_pt
682 } else {
683 self.last_pt + (self.last_pt - self.last_ctl_pt.unwrap())
684 };
685
686 let mut tmp = [0.0; 4];
688 tmp.copy_from_slice(&self.nums[0..4]);
689 self.nums[2..6].copy_from_slice(&tmp);
690
691 if abs {
692 self.nums[0..2].copy_from_slice(&[a_cp1.x, a_cp1.y]);
693 self.last_pt = Point {x: self.nums[4], y: self.nums[5]};
694 self.out.push(PathCommand::CubicTo(
695 Point {x: self.nums[0], y: self.nums[1]},
696 Point {x: self.nums[2], y: self.nums[3]},
697 self.last_pt,
698 ));
699 self.last_ctl_pt = Some(Point {x: self.nums[2], y: self.nums[3]});
700 } else {
701 let r_cp1 = a_cp1 - self.last_pt;
702 self.nums[0..2].copy_from_slice(&[r_cp1.x, r_cp1.y]);
703 self.out.push(PathCommand::CubicTo(
704 self.last_pt + Vector {x: self.nums[0], y: self.nums[1]},
705 self.last_pt + Vector {x: self.nums[2], y: self.nums[3]},
706 self.last_pt + Vector {x: self.nums[4], y: self.nums[5]},
707 ));
708 self.last_ctl_pt = Some(
709 self.last_pt + Vector {x: self.nums[2], y: self.nums[3]});
710 self.last_pt += Vector {x: self.nums[4], y: self.nums[5]};
711 }
712 },
713 Cmd::Arc(abs) => {
714 if abs {
715 self.last_pt = Point {x: self.nums[5], y: self.nums[6]};
716 self.out.push(PathCommand::ArcTo(
717 self.last_pt,
718 Point {x: self.nums[0], y: self.nums[1]},
719 self.nums[2],
720 self.nums[3] != 0.0,
721 self.nums[4] != 0.0,
722 ));
723 }
724 else {
725 self.out.push(PathCommand::ArcTo(
726 self.last_pt + Vector {x: self.nums[5], y: self.nums[6]},
727 Point {x: self.nums[0], y: self.nums[1]},
728 self.nums[2],
729 self.nums[3] != 0.0,
730 self.nums[4] != 0.0,
731 ));
732 self.last_pt += Vector {x: self.nums[5], y: self.nums[6]};
733 }
734 },
735 Cmd::Quadratic(abs) => {
736 if abs {
737 self.last_pt = Point {x: self.nums[2], y: self.nums[3]};
738 self.out.push(PathCommand::QuadraticTo(
739 Point {x: self.nums[0], y: self.nums[1]},
740 self.last_pt
741 ));
742 }
743 else {
744 self.out.push(PathCommand::QuadraticTo(
745 self.last_pt + Vector {x: self.nums[0], y: self.nums[1]},
746 self.last_pt + Vector {x: self.nums[2], y: self.nums[3]},
747 ));
748 self.last_pt += Vector {x: self.nums[2], y: self.nums[3]};
749 }
750 }
751 Cmd::Close => {
752 self.last_pt = self.first_pt;
753 self.out.push(PathCommand::Close);
754 }
755 }
756 self.num_count = 0;
757 self.chain = true;
758 Ok(())
759 }
760 }
761
762 let mut state = ParseState::default();
763
764 for i in 0..path.len() {
765 match path[i] {
766 b'M' => state.next_cmd(Cmd::Move(true)) ?,
767 b'm' => state.next_cmd(Cmd::Move(false)) ?,
768 b'Q' => state.next_cmd(Cmd::Quadratic(true)) ?,
769 b'q' => state.next_cmd(Cmd::Quadratic(false)) ?,
770 b'C' => state.next_cmd(Cmd::Cubic(true)) ?,
771 b'c' => state.next_cmd(Cmd::Cubic(false)) ?,
772 b'S' => state.next_cmd(Cmd::CubicSmooth(true)) ?,
773 b's' => state.next_cmd(Cmd::CubicSmooth(false)) ?,
774 b'H' => state.next_cmd(Cmd::Hor(true)) ?,
775 b'h' => state.next_cmd(Cmd::Hor(false)) ?,
776 b'V' => state.next_cmd(Cmd::Vert(true)) ?,
777 b'v' => state.next_cmd(Cmd::Vert(false)) ?,
778 b'L' => state.next_cmd(Cmd::Line(true)) ?,
779 b'l' => state.next_cmd(Cmd::Line(false)) ?,
780 b'A' => state.next_cmd(Cmd::Arc(true)) ?,
781 b'a' => state.next_cmd(Cmd::Arc(false)) ?,
782 b'Z' | b'z' => state.next_cmd(Cmd::Close) ?,
783 b'-' => state.add_min() ?,
784 b'0'..=b'9' => state.add_digit((path[i] - b'0') as f64) ?,
785 b'.' => state.add_dot() ?,
786 b',' | b' ' | b'\r' | b'\n' | b'\t' => state.whitespace() ?,
787 x => {
788 return Err(format!("Unexpected character {} - {}", x, x as char))
789 }
790 }
791 }
792 state.finalize_cmd() ?;
793
794 Ok(state.out)
795}