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 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 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 fn draw_vector(&mut self, entry: &CxIconEntry, path: &CxIconPathCommands, many: &mut ManyInstances) {
290 let trapezoids = {
291 let mut trapezoids = Vec::new();
292 let trapezoidate = self.trapezoidator.trapezoidate(
294 path.map({
295 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 let draw_trapezoid = DrawTrapezoidVector::new_local(cx);
342 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 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 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