makepad_draw/
icon_atlas.rs

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                            // TODO: this is pretty rough
143                            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            // alright so. lets see if we have a path hash
187            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            // lets parse the path range out of the svg file
200            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                                // do something with clipping/transform groups here.
217                            }                            
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    // atlas drawing function used by CxAfterDraw
340    fn draw_vector(&mut self, entry: &CxIconEntry, path: &CxIconPathCommands, many: &mut ManyInstances) {
341        let trapezoids = {
342            let mut trapezoids = Vec::new();
343            //log_str(&format!("Serializing char {} {} {} {}", glyphtc.tx1 , cx.fonts_atlas.texture_size.x ,todo.subpixel_x_fract ,atlas_page.dpi_factor));
344            let trapezoidate = self.trapezoidator.trapezoidate(
345                path.map({
346                    //log!("{:?} {:?}", entry.args, entry.pos);
347                    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        //cx.fonts_atlas.texture_id = Some(atlas_texture.texture_id());
393        
394        let draw_trapezoid = DrawTrapezoidVector::new_local(cx);
395        // ok we need to initialize drawtrapezoidtext from a live pointer.
396        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        // ok lets fetch/instance our CxFontsAtlasRc
408        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        //let start = Cx::profile_time_ns();
433        // we need to start a pass that just uses the texture
434        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                    // Calculate absolute x1, y1 for control point 1
680                    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                    // Transform s command into c command
687                    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}