makepad_draw/
icon_atlas.rs

1pub use {
2    std::{
3        rc::Rc,
4        cell::RefCell,
5        io::prelude::*,
6        fs::File,
7        collections::HashMap,
8    },
9    crate::{
10        shader::draw_trapezoid::DrawTrapezoidVector,
11        makepad_platform::*,
12        cx_2d::Cx2d,
13        turtle::{Walk, Layout},
14        draw_list_2d::{ManyInstances, DrawList2d, RedrawingApi},
15        geometry::GeometryQuad2D,
16        makepad_vector::trapezoidator::Trapezoidator,
17        makepad_vector::geometry::{AffineTransformation, Transform, Vector, Point},
18        makepad_vector::internal_iter::*,
19        makepad_vector::path::{PathIterator, PathCommand},
20    }
21};
22
23#[derive(Clone, Copy)]
24pub struct CxIconSlot {
25    pub t1: Vec2,
26    pub t2: Vec2,
27    pub chan: f32
28}
29
30#[derive(Clone)]
31pub struct CxIconEntry {
32    path_hash: CxIconPathHash,
33    pos: DVec2,
34    slot: CxIconSlot,
35    args: CxIconArgs,
36}
37
38struct CxIconPathCommands {
39    bounds: Rect,
40    path: Vec<PathCommand>
41}
42
43impl<'a> InternalIterator for &CxIconPathCommands {
44    type Item = PathCommand;
45    fn for_each<F>(self, f: &mut F) -> bool
46    where
47    F: FnMut(PathCommand) -> bool,
48    {
49        for item in &self.path {
50            if !f(item.clone()) {
51                return false
52            }
53        }
54        true
55    }
56}
57
58#[derive(Clone, Copy, Hash, PartialEq, Eq)]
59pub struct CxIconPathHash(LiveId);
60
61#[derive(Clone, Copy, Hash, PartialEq, Eq)]
62pub struct CxIconEntryHash(LiveId);
63
64pub struct CxIconAtlas {
65    pub texture_id: TextureId,
66    pub clear_buffer: bool,
67    svg_deps: HashMap<String, CxIconPathHash>,
68    paths: HashMap<CxIconPathHash, CxIconPathCommands>,
69    entries: HashMap<CxIconEntryHash, CxIconEntry>,
70    alloc: CxIconAtlasAlloc
71}
72
73#[derive(Default)]
74pub struct CxIconAtlasAlloc {
75    pub texture_size: DVec2,
76    pub xpos: f64,
77    pub ypos: f64,
78    pub hmax: f64,
79    pub todo: Vec<CxIconEntryHash>,
80}
81
82#[derive(Clone, Debug)]
83pub struct CxIconArgs {
84    pub linearize: f64,
85    pub size: DVec2,
86    pub translate: DVec2,
87    pub subpixel: DVec2,
88    pub scale: f64,
89}
90
91impl CxIconArgs {
92    fn hash(&self) -> LiveId {
93        LiveId::seeded()
94            .bytes_append(&self.linearize.to_be_bytes())
95            .bytes_append(&self.translate.x.to_be_bytes())
96            .bytes_append(&self.translate.y.to_be_bytes())
97            .bytes_append(&self.subpixel.x.to_be_bytes())
98            .bytes_append(&self.subpixel.y.to_be_bytes())
99            .bytes_append(&self.scale.to_be_bytes())
100            .bytes_append(&self.size.x.to_be_bytes())
101            .bytes_append(&self.size.y.to_be_bytes())
102    }
103}
104
105impl CxIconAtlas {
106    pub fn new(texture_id: TextureId) -> Self {
107        Self {
108            texture_id,
109            clear_buffer: false,
110            entries: HashMap::new(),
111            svg_deps: HashMap::new(),
112            paths: HashMap::new(),
113            alloc: CxIconAtlasAlloc {
114                texture_size: DVec2 {x: 2048.0, y: 2048.0},
115                xpos: 0.0,
116                ypos: 0.0,
117                hmax: 0.0,
118                todo: Vec::new(),
119            }
120        }
121    }
122    
123    pub fn parse_and_cache_path(&mut self, path_hash: CxIconPathHash, path: &[u8]) -> Option<(CxIconPathHash, Rect)> {
124        match parse_svg_path(path) {
125            Ok(path) => {
126                let mut min = dvec2(f64::INFINITY, f64::INFINITY);
127                let mut max = dvec2(-f64::INFINITY, -f64::INFINITY);
128                fn bound(p: &Point, min: &mut DVec2, max: &mut DVec2) {
129                    if p.x < min.x {min.x = p.x}
130                    if p.y < min.y {min.y = p.y}
131                    if p.x > max.x {max.x = p.x}
132                    if p.y > max.y {max.y = p.y}
133                }
134                for cmd in &path {
135                    match cmd {
136                        PathCommand::MoveTo(p) => {bound(p, &mut min, &mut max)},
137                        PathCommand::LineTo(p) => {bound(p, &mut min, &mut max)},
138                        PathCommand::QuadraticTo(p1, p) => {
139                            bound(p1, &mut min, &mut max);
140                            bound(p, &mut min, &mut max);
141                        },
142                        PathCommand::CubicTo(p1, p2, p) => {
143                            bound(p1, &mut min, &mut max);
144                            bound(p2, &mut min, &mut max);
145                            bound(p, &mut min, &mut max);
146                        },
147                        PathCommand::Close => ()
148                    }
149                }
150                let bounds = Rect {pos: min, size: max - min};
151                self.paths.insert(path_hash, CxIconPathCommands {
152                    bounds,
153                    path
154                });
155                return Some((path_hash, bounds));
156            }
157            Err(e) => {
158                log!("Error in SVG Path {}", e);
159                return None
160            }
161        }
162    }
163    
164    pub fn get_icon_bounds(&mut self, cx: &Cx, path_str: &Rc<String>, svg_dep: &Rc<String>) -> Option<(CxIconPathHash, Rect)> {
165        if svg_dep.len() != 0 {
166            // alright so. lets see if we have a path hash
167            if let Some(path_hash) = self.svg_deps.get(svg_dep.as_str()) {
168                if let Some(path) = self.paths.get(&path_hash) {
169                    return Some((*path_hash, path.bounds))
170                }
171                return None
172            }
173            let path_hash = CxIconPathHash(LiveId(self.svg_deps.len() as u64));
174            self.svg_deps.insert(svg_dep.as_str().to_string(), path_hash);
175            // lets parse the path range out of the svg file
176            match cx.get_dependency(svg_dep.as_str()) {
177                Ok(data)=>{
178                    fn find_path_str(data:&[u8])->Option<&[u8]>{
179                        let pat = "path d=\"".as_bytes();
180                        'outer:for i in 0..data.len(){
181                            for j in 0..pat.len(){
182                                if data[i+j] != pat[j]{
183                                    continue 'outer;
184                                }
185                            }
186                            for k in i+pat.len()..data.len(){
187                                if data[k] == '\"' as u8{
188                                    return Some(&data[i+pat.len()..k])
189                                }
190                            }
191                            return None
192                        }
193                        None
194                    }
195                    if let Some(data) = find_path_str(&data){
196                        return self.parse_and_cache_path(path_hash, data)
197                    }
198                    return None
199                    
200                }
201                Err(_err)=>{
202                    return None
203                }
204            }
205        }
206        if path_str.len() == 0 {
207            return None
208        }
209        let path_hash = CxIconPathHash(LiveId(Rc::as_ptr(path_str) as u64));
210        if let Some(path) = self.paths.get(&path_hash) {
211            return Some((path_hash, path.bounds))
212        }
213        self.parse_and_cache_path(path_hash, path_str.as_str().as_bytes())
214    }
215    
216    pub fn get_icon_slot(&mut self, args: CxIconArgs, path_hash: CxIconPathHash) -> CxIconSlot {
217        let entry_hash = CxIconEntryHash(path_hash.0.id_append(args.hash()));
218        
219        if let Some(entry) = self.entries.get(&entry_hash) {
220            return entry.slot
221        }
222        
223        let (slot,pos) = self.alloc.alloc_icon_slot(args.size.x as f64, args.size.y as f64);
224        self.entries.insert(
225            entry_hash,
226            CxIconEntry {
227                path_hash,
228                slot,
229                pos,
230                args
231            }
232        );
233        self.alloc.todo.push(entry_hash);
234        
235        return slot
236    }
237    
238}
239impl CxIconAtlasAlloc {
240    pub fn alloc_icon_slot(&mut self, w: f64, h: f64) -> (CxIconSlot,DVec2) {
241        if w + self.xpos >= self.texture_size.x {
242            self.xpos = 0.0;
243            self.ypos += self.hmax + 1.0;
244            self.hmax = 0.0;
245        }
246        if h + self.ypos >= self.texture_size.y {
247            println!("ICON ATLAS FULL, TODO FIX THIS {} > {},", h + self.ypos, self.texture_size.y);
248        }
249        if h > self.hmax {
250            self.hmax = h;
251        }
252        
253        let px = self.xpos;
254        let py = self.ypos;
255        
256        let tx1 = px / self.texture_size.x;
257        let ty1 = py / self.texture_size.y;
258        
259        self.xpos += w + 1.0;
260        
261        (CxIconSlot {
262            chan: 0.0,
263            t1: dvec2(tx1, ty1).into(),
264            t2: dvec2(tx1 + (w / self.texture_size.x), ty1 + (h / self.texture_size.y)).into()
265        },dvec2(px, py).into())
266    }
267}
268
269#[derive(Clone)]
270pub struct CxIconAtlasRc(pub Rc<RefCell<CxIconAtlas >>);
271
272impl CxIconAtlas {
273    pub fn reset_icon_atlas(&mut self) {
274        self.entries.clear();
275        self.alloc.xpos = 0.;
276        self.alloc.ypos = 0.;
277        self.alloc.hmax = 0.;
278        self.clear_buffer = true;
279    }
280    
281    pub fn get_internal_atlas_texture_id(&self) -> TextureId {
282        self.texture_id
283    }
284}
285
286
287impl DrawTrapezoidVector {
288    // atlas drawing function used by CxAfterDraw
289    fn draw_vector(&mut self, entry: &CxIconEntry, path: &CxIconPathCommands, many: &mut ManyInstances) {
290        let trapezoids = {
291            let mut trapezoids = Vec::new();
292            //log_str(&format!("Serializing char {} {} {} {}", glyphtc.tx1 , cx.fonts_atlas.texture_size.x ,todo.subpixel_x_fract ,atlas_page.dpi_factor));
293            let trapezoidate = self.trapezoidator.trapezoidate(
294                path.map({
295                    //log!("{:?} {:?}", entry.args, entry.pos);
296                    move | cmd | {
297                        let cmd = cmd.transform(
298                            &AffineTransformation::identity()
299                                .translate(Vector::new(entry.args.translate.x, entry.args.translate.y))
300                                .uniform_scale(entry.args.scale)
301                                .translate(Vector::new(entry.pos.x + entry.args.subpixel.x, entry.pos.y + entry.args.subpixel.y))
302                        );
303                        cmd
304                    }
305                }).linearize(entry.args.linearize)
306            );
307            if let Some(trapezoidate) = trapezoidate {
308                trapezoids.extend_from_internal_iter(
309                    trapezoidate
310                );
311            }
312            trapezoids
313        };
314        
315        for trapezoid in trapezoids {
316            self.a_xs = Vec2 {x: trapezoid.xs[0], y: trapezoid.xs[1]};
317            self.a_ys = Vec4 {x: trapezoid.ys[0], y: trapezoid.ys[1], z: trapezoid.ys[2], w: trapezoid.ys[3]};
318            self.chan = 0.0 as f32;
319            many.instances.extend_from_slice(self.draw_vars.as_slice());
320        }
321    }
322}
323
324#[derive(Clone)]
325pub struct CxDrawIconAtlasRc(pub Rc<RefCell<CxDrawIconAtlas >>);
326
327pub struct CxDrawIconAtlas {
328    pub draw_trapezoid: DrawTrapezoidVector,
329    pub atlas_pass: Pass,
330    pub atlas_draw_list: DrawList2d,
331    pub atlas_texture: Texture,
332}
333
334impl CxDrawIconAtlas {
335    pub fn new(cx: &mut Cx) -> Self {
336        
337        let atlas_texture = Texture::new(cx);
338        
339        //cx.fonts_atlas.texture_id = Some(atlas_texture.texture_id());
340        
341        let draw_trapezoid = DrawTrapezoidVector::new_local(cx);
342        // ok we need to initialize drawtrapezoidtext from a live pointer.
343        Self {
344            draw_trapezoid,
345            atlas_pass: Pass::new(cx),
346            atlas_draw_list: DrawList2d::new(cx),
347            atlas_texture: atlas_texture
348        }
349    }
350}
351
352impl<'a> Cx2d<'a> {
353    pub fn lazy_construct_icon_atlas(cx: &mut Cx) {
354        // ok lets fetch/instance our CxFontsAtlasRc
355        if !cx.has_global::<CxIconAtlasRc>() {
356            
357            let draw_atlas = CxDrawIconAtlas::new(cx);
358            let texture_id = draw_atlas.atlas_texture.texture_id();
359            cx.set_global(CxDrawIconAtlasRc(Rc::new(RefCell::new(draw_atlas))));
360            
361            let atlas = CxIconAtlas::new(texture_id);
362            cx.set_global(CxIconAtlasRc(Rc::new(RefCell::new(atlas))));
363        }
364    }
365    
366    pub fn reset_icon_atlas(cx: &mut Cx) {
367        if cx.has_global::<CxIconAtlasRc>() {
368            let mut fonts_atlas = cx.get_global::<CxIconAtlasRc>().0.borrow_mut();
369            fonts_atlas.reset_icon_atlas();
370        }
371    }
372    
373    pub fn draw_icon_atlas(&mut self) {
374        let draw_atlas_rc = self.cx.get_global::<CxDrawIconAtlasRc>().clone();
375        let mut draw_atlas = draw_atlas_rc.0.borrow_mut();
376        let atlas_rc = self.icon_atlas_rc.clone();
377        let mut atlas = atlas_rc.0.borrow_mut();
378        let atlas = &mut*atlas;
379        //let start = Cx::profile_time_ns();
380        // we need to start a pass that just uses the texture
381        if atlas.alloc.todo.len()>0 {
382            self.begin_pass(&draw_atlas.atlas_pass, None);
383            
384            let texture_size = atlas.alloc.texture_size;
385            draw_atlas.atlas_pass.set_size(self.cx, texture_size);
386            
387            let clear = if atlas.clear_buffer {
388                atlas.clear_buffer = false;
389                PassClearColor::ClearWith(Vec4::default())
390            }
391            else {
392                PassClearColor::InitWith(Vec4::default())
393            };
394            
395            draw_atlas.atlas_pass.clear_color_textures(self.cx);
396            draw_atlas.atlas_pass.add_color_texture(self.cx, &draw_atlas.atlas_texture, clear);
397            draw_atlas.atlas_draw_list.begin_always(self);
398            
399            let mut atlas_todo = Vec::new();
400            std::mem::swap(&mut atlas.alloc.todo, &mut atlas_todo);
401            
402            if let Some(mut many) = self.begin_many_instances(&draw_atlas.draw_trapezoid.draw_vars) {
403                for todo in atlas_todo {
404                    let entry = atlas.entries.get(&todo).unwrap();
405                    let path = atlas.paths.get(&entry.path_hash).unwrap();
406                    draw_atlas.draw_trapezoid.draw_vector(entry, path, &mut many);
407                }
408                
409                self.end_many_instances(many);
410            }
411            draw_atlas.atlas_draw_list.end(self);
412            self.end_pass(&draw_atlas.atlas_pass);
413        }
414    }
415    
416    
417}
418
419fn parse_svg_path(path: &[u8]) -> Result<Vec<PathCommand>, String> {
420    #[derive(Debug)]
421    enum Cmd {
422        Unknown,
423        Move(bool),
424        Hor(bool),
425        Vert(bool),
426        Line(bool),
427        Cubic(bool),
428        Quadratic(bool),
429        Close
430    }
431    impl Default for Cmd {fn default() -> Self {Self::Unknown}}
432    
433    #[derive(Default)]
434    struct ParseState {
435        cmd: Cmd,
436        expect_nums: usize,
437        chain: bool,
438        nums: [f64; 6],
439        num_count: usize,
440        last_pt: Point,
441        out: Vec<PathCommand>,
442        num_state: Option<NumState>
443    }
444    
445    struct NumState {
446        num: f64,
447        mul: f64,
448        has_dot: bool,
449    }
450    
451    impl NumState {
452        fn new_pos(v: f64) -> Self {Self {num: v, mul: 1.0, has_dot: false}}
453        fn new_min() -> Self {Self {num: 0.0, mul: -1.0, has_dot: false}}
454        fn finalize(self) -> f64 {self.num * self.mul}
455        fn add_digit(&mut self, digit: f64) {
456            self.num *= 10.0;
457            self.num += digit;
458            if self.has_dot {
459                self.mul *= 0.1;
460            }
461        }
462    }
463    
464    impl ParseState {
465        fn next_cmd(&mut self, cmd: Cmd) -> Result<(), String> {
466            self.finalize_cmd() ?;
467            self.chain = false;
468            self.expect_nums = match cmd {
469                Cmd::Unknown => panic!(),
470                Cmd::Move(_) => 2,
471                Cmd::Hor(_) => 1,
472                Cmd::Vert(_) => 1,
473                Cmd::Line(_) => 2,
474                Cmd::Cubic(_) => 6,
475                Cmd::Quadratic(_) => 4,
476                Cmd::Close => 0
477            };
478            self.cmd = cmd;
479            Ok(())
480        }
481        
482        fn add_min(&mut self) -> Result<(), String> {
483            if self.expect_nums == self.num_count {
484                self.finalize_cmd() ?;
485            }
486            if self.expect_nums == 0 {
487                return Err(format!("Unexpected minus"));
488            }
489            self.num_state = Some(NumState::new_min());
490            Ok(())
491        }
492        
493        fn add_digit(&mut self, digit: f64) -> Result<(), String> {
494            if let Some(num_state) = &mut self.num_state {
495                num_state.add_digit(digit);
496            }
497            else {
498                if self.expect_nums == self.num_count {
499                    self.finalize_cmd() ?;
500                }
501                if self.expect_nums == 0 {
502                    return Err(format!("Unexpected digit"));
503                }
504                self.num_state = Some(NumState::new_pos(digit))
505            }
506            Ok(())
507        }
508        
509        fn add_dot(&mut self) -> Result<(), String> {
510            if let Some(num_state) = &mut self.num_state {
511                if num_state.has_dot {
512                    return Err(format!("Unexpected ."));
513                }
514                num_state.has_dot = true;
515            }
516            else {
517                return Err(format!("Unexpected ."));
518            }
519            Ok(())
520        }
521        
522        fn finalize_num(&mut self) {
523            if let Some(num_state) = self.num_state.take() {
524                self.nums[self.num_count] = num_state.finalize();
525                self.num_count += 1;
526            }
527        }
528        
529        fn whitespace(&mut self) -> Result<(), String> {
530            self.finalize_num();
531            if self.expect_nums == self.num_count {
532                self.finalize_cmd() ?;
533            }
534            Ok(())
535        }
536        
537        fn finalize_cmd(&mut self) -> Result<(), String> {
538            self.finalize_num();
539            if self.chain && self.num_count == 0 {
540                return Ok(())
541            }
542            if self.expect_nums != self.num_count {
543                return Err(format!("SVG Path command {:?} expected {} points, got {}", self.cmd, self.expect_nums, self.num_count));
544            }
545            match self.cmd {
546                Cmd::Unknown => (),
547                Cmd::Move(abs) => {
548                    if abs {
549                        self.last_pt = Point {x: self.nums[0], y: self.nums[1]};
550                    }
551                    else {
552                        self.last_pt += Vector {x: self.nums[0], y: self.nums[1]};
553                    }
554                    self.out.push(PathCommand::MoveTo(self.last_pt));
555                },
556                Cmd::Hor(abs) => {
557                    if abs {
558                        self.last_pt = Point {x: self.nums[0], y: self.last_pt.y};
559                    }
560                    else {
561                        self.last_pt += Vector {x: self.nums[0], y: 0.0};
562                    }
563                    self.out.push(PathCommand::LineTo(self.last_pt));
564                }
565                Cmd::Vert(abs) => {
566                    if abs {
567                        self.last_pt = Point {x: self.last_pt.x, y: self.nums[0]};
568                    }
569                    else {
570                        self.last_pt += Vector {x: 0.0, y: self.nums[0]};
571                    }
572                    self.out.push(PathCommand::LineTo(self.last_pt));
573                }
574                Cmd::Line(abs) => {
575                    if abs {
576                        self.last_pt = Point {x: self.nums[0], y: self.nums[1]};
577                    }
578                    else {
579                        self.last_pt += Vector {x: self.nums[0], y: self.nums[1]};
580                    }
581                    self.out.push(PathCommand::LineTo(self.last_pt));
582                },
583                Cmd::Cubic(abs) => {
584                    if abs {
585                        self.last_pt = Point {x: self.nums[4], y: self.nums[5]};
586                    }
587                    else {
588                        self.last_pt += Vector {x: self.nums[4], y: self.nums[5]};
589                    }
590                    self.out.push(PathCommand::CubicTo(
591                        Point {x: self.nums[0], y: self.nums[1]},
592                        Point {x: self.nums[2], y: self.nums[3]},
593                        
594                        self.last_pt,
595                    ))
596                },
597                Cmd::Quadratic(abs) => {
598                    if abs {
599                        self.last_pt = Point {x: self.nums[2], y: self.nums[3]};
600                    }
601                    else {
602                        self.last_pt += Vector {x: self.nums[2], y: self.nums[3]};
603                    }
604                    self.out.push(PathCommand::QuadraticTo(
605                        Point {x: self.nums[0], y: self.nums[1]},
606                        self.last_pt
607                    ));
608                }
609                Cmd::Close => {
610                    self.out.push(PathCommand::Close);
611                }
612            }
613            self.num_count = 0;
614            self.chain = true;
615            Ok(())
616        }
617    }
618    
619    let mut state = ParseState::default();
620    for i in 0..path.len() {
621        match path[i] {
622            b'M' => state.next_cmd(Cmd::Move(true)) ?,
623            b'm' => state.next_cmd(Cmd::Move(false)) ?,
624            b'Q' => state.next_cmd(Cmd::Quadratic(true)) ?,
625            b'q' => state.next_cmd(Cmd::Quadratic(false)) ?,
626            b'C' => state.next_cmd(Cmd::Cubic(true)) ?,
627            b'c' => state.next_cmd(Cmd::Cubic(false)) ?,
628            b'H' => state.next_cmd(Cmd::Hor(true)) ?,
629            b'h' => state.next_cmd(Cmd::Hor(false)) ?,
630            b'V' => state.next_cmd(Cmd::Vert(true)) ?,
631            b'v' => state.next_cmd(Cmd::Vert(false)) ?,
632            b'L' => state.next_cmd(Cmd::Line(true)) ?,
633            b'l' => state.next_cmd(Cmd::Line(false)) ?,
634            b'Z' | b'z' => state.next_cmd(Cmd::Close) ?,
635            b'-' => state.add_min() ?,
636            b'0'..=b'9' => state.add_digit((path[i] - b'0') as f64) ?,
637            b'.' => state.add_dot() ?,
638            b',' | b' ' | b'\r' | b'\n' | b'\t' => state.whitespace() ?,
639            x => {
640                return Err(format!("Unexpected character {} - {}", x, x as char))
641            }
642        }
643    }
644    state.finalize_cmd() ?;
645    
646    Ok(state.out)
647}
648