1#[cfg(any(feature = "builder", feature = "png_source"))]
32use png::{BitDepth, ColorType, Decoder, Transformations};
33use std::collections::HashMap;
34use std::fmt::{Debug, Formatter};
35#[cfg(any(feature = "builder", feature = "save-to-rust"))]
36use std::fs::*;
37#[cfg(any(feature = "builder", feature = "png_source"))]
38use std::io::Cursor;
39use std::io::Error;
40use std::io::ErrorKind;
41#[cfg(feature = "builder")]
42use std::io::Read;
43#[cfg(feature = "builder")]
44use std::io::BufWriter;
45#[cfg(any(feature = "builder", feature = "save-to-rust"))]
46use std::io::Result;
47#[cfg(any(feature = "builder", feature = "save-to-rust"))]
48use std::io::Write;
49#[cfg(feature = "builder")]
50use std::path::*;
51
52#[cfg(feature = "save-to-rust")]
53use std::str::FromStr;
54
55use super::*;
56use crate::ImageSource;
57
58#[derive(Debug, Clone)]
59pub struct CharEntry {
61 pub offset: Vec2i,
63 pub advance: Vec2i,
65 pub rect: Recti, }
68
69#[derive(Clone)]
70struct Font {
71 line_size: usize, baseline: i32, font_size: usize, entries: HashMap<char, CharEntry>, }
76
77impl Debug for Font {
78 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
79 use std::fmt::Write;
80 let mut entries = String::new();
81 for e in &self.entries {
82 entries.write_fmt(format_args!("{:?}, ", e))?;
83 }
84 f.write_fmt(format_args!(
85 "Font {{ line_size: {}, baseline: {}, font_size: {}, entries: [{}] }}",
86 self.line_size, self.baseline, self.font_size, entries
87 ))
88 }
89}
90
91#[derive(Default, Copy, Clone)]
92pub struct FontId(usize);
94
95#[derive(Default, Copy, Clone)]
96pub struct IconId(usize);
98
99#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
100pub struct SlotId(usize);
102
103impl Into<u32> for IconId {
104 fn into(self) -> u32 { self.0 as _ }
105}
106
107impl Into<u32> for SlotId {
108 fn into(self) -> u32 { self.0 as _ }
109}
110
111#[derive(Debug, Clone)]
112struct Icon {
113 rect: Recti,
114}
115
116struct Atlas {
117 width: usize,
118 height: usize,
119 pixels: Vec<Color4b>,
120 fonts: Vec<(String, Font)>,
121 icons: Vec<(String, Icon)>,
122 slots: Vec<Recti>,
123 last_update_id: usize,
124}
125
126#[derive(Clone)]
127pub struct AtlasHandle(Rc<RefCell<Atlas>>);
129
130pub const WHITE_ICON: IconId = IconId(0);
132pub const CLOSE_ICON: IconId = IconId(1);
134pub const EXPAND_ICON: IconId = IconId(2);
136pub const COLLAPSE_ICON: IconId = IconId(3);
138pub const CHECK_ICON: IconId = IconId(4);
140pub const EXPAND_DOWN_ICON: IconId = IconId(5);
142pub const OPEN_FOLDER_16_ICON: IconId = IconId(6);
144pub const CLOSED_FOLDER_16_ICON: IconId = IconId(7);
146pub const FILE_16_ICON: IconId = IconId(8);
148
149pub fn load_image_bytes(source: ImageSource) -> std::io::Result<(usize, usize, Vec<Color4b>)> {
152 match source {
153 ImageSource::Raw { width, height, pixels } => {
154 if width <= 0 || height <= 0 {
155 return Err(Error::new(ErrorKind::Other, "Image dimensions must be positive"));
156 }
157 let width_usize = width as usize;
158 let height_usize = height as usize;
159 let expected = width_usize * height_usize * 4;
160 if pixels.len() != expected {
161 return Err(Error::new(
162 ErrorKind::Other,
163 format!("Expected {} RGBA bytes, found {}", expected, pixels.len()),
164 ));
165 }
166 let mut colors = Vec::with_capacity(width_usize * height_usize);
167 for chunk in pixels.chunks_exact(4) {
168 colors.push(color4b(chunk[0], chunk[1], chunk[2], chunk[3]));
169 }
170 Ok((width_usize, height_usize, colors))
171 }
172 #[cfg(any(feature = "builder", feature = "png_source"))]
173 ImageSource::Png { bytes } => decode_png_to_colors(bytes),
174 }
175}
176
177#[cfg(any(feature = "builder", feature = "png_source"))]
178fn decode_png_to_colors(bytes: &[u8]) -> std::io::Result<(usize, usize, Vec<Color4b>)> {
179 let mut cursor = Cursor::new(bytes);
180 let mut decoder = Decoder::new(&mut cursor);
181 decoder.set_transformations(Transformations::normalize_to_color8());
182 let mut reader = decoder
183 .read_info()
184 .map_err(|e| Error::new(ErrorKind::Other, format!("PNG decode error: {}", e)))?;
185 let buf_size = reader
186 .output_buffer_size()
187 .ok_or_else(|| Error::new(ErrorKind::Other, "PNG decoder did not report output size"))?;
188 let mut img_data = vec![0; buf_size];
189 let info = reader.next_frame(&mut img_data)?;
190
191 if info.bit_depth != BitDepth::Eight {
192 return Err(Error::new(
193 ErrorKind::Other,
194 format!("Unsupported PNG bit depth: {:?}", info.bit_depth),
195 ));
196 }
197
198 let pixel_size = match info.color_type {
199 ColorType::Grayscale => 1,
200 ColorType::GrayscaleAlpha => 2,
201 ColorType::Indexed => 1,
202 ColorType::Rgb => 3,
203 ColorType::Rgba => 4,
204 };
205
206 let mut pixels = vec![Color4b::default(); (info.width * info.height) as usize];
207 let line_size = info.line_size;
208 for y in 0..info.height {
209 let line = &img_data[(y as usize * line_size)..((y as usize + 1) * line_size)];
210
211 for x in 0..info.width {
212 let xx = (x * pixel_size) as usize;
213 let color = match info.color_type {
214 ColorType::Grayscale => {
215 let v = line[xx];
216 color4b(v, v, v, 0xFF)
217 }
218 ColorType::GrayscaleAlpha => {
219 let c = line[xx];
220 let a = line[xx + 1];
221 color4b(c, c, c, a)
222 }
223 ColorType::Indexed => {
224 return Err(Error::new(ErrorKind::Other, "Indexed PNGs are not supported"));
225 }
226 ColorType::Rgb => color4b(line[xx], line[xx + 1], line[xx + 2], 0xFF),
227 ColorType::Rgba => {
228 let r = line[xx];
229 let g = line[xx + 1];
230 let b = line[xx + 2];
231 let a = line[xx + 3];
232 color4b(r, g, b, a)
233 }
234 };
235 pixels[(x + y * info.width) as usize] = color;
236 }
237 }
238
239 Ok((info.width as _, info.height as _, pixels))
240}
241
242#[cfg(feature = "builder")]
243pub mod builder {
245 use std::io::Seek;
246
247 use super::*;
248 use fontdue::*;
249
250 use rect_packer::*;
251
252 pub struct Builder {
254 packer: Packer,
255 atlas: Atlas,
256 }
257
258 #[derive(Clone)]
259 #[cfg(feature = "builder")]
261 pub struct Config<'a> {
262 pub texture_width: usize,
264 pub texture_height: usize,
266 pub white_icon: String,
268 pub close_icon: String,
270 pub expand_icon: String,
272 pub collapse_icon: String,
274 pub check_icon: String,
276 pub expand_down_icon: String,
278 pub open_folder_16_icon: String,
280 pub closed_folder_16_icon: String,
282 pub file_16_icon: String,
284 pub default_font: String,
286 pub default_font_size: usize,
288 pub slots: &'a [Dimensioni],
290 }
291
292 impl Builder {
293 #[cfg(feature = "builder")]
295 pub fn from_config<'a>(config: &'a Config) -> Result<Builder> {
296 let rp_config = rect_packer::Config {
297 width: config.texture_width as _,
298 height: config.texture_height as _,
299
300 border_padding: 1,
301 rectangle_padding: 1,
302 };
303
304 let atlas = Atlas {
305 width: config.texture_width,
306 height: config.texture_height,
307 pixels: vec![Color4b::default(); config.texture_height * config.texture_width],
308 fonts: Vec::new(),
309 icons: Vec::new(),
310 slots: Vec::new(),
311 last_update_id: 0,
312 };
313
314 let mut builder = Builder { atlas, packer: Packer::new(rp_config) };
315
316 builder.add_icon(&config.white_icon)?;
317 builder.add_icon(&config.close_icon)?;
318 builder.add_icon(&config.expand_icon)?;
319 builder.add_icon(&config.collapse_icon)?;
320 builder.add_icon(&config.check_icon)?;
321 builder.add_icon(&config.expand_down_icon)?;
322 builder.add_icon(&config.open_folder_16_icon)?;
323 builder.add_icon(&config.closed_folder_16_icon)?;
324 builder.add_icon(&config.file_16_icon)?;
325 builder.add_font(&config.default_font, config.default_font_size)?;
326
327 for slot in config.slots {
328 builder.add_slot(*slot)?;
329 }
330
331 Ok(builder)
332 }
333
334 pub fn add_icon(&mut self, path: &str) -> Result<IconId> {
336 let (width, height, pixels) = Self::load_icon(path)?;
337 let rect = self.add_tile(width, height, pixels.as_slice())?;
338 let id = self.atlas.icons.len();
339 let icon = Icon { rect };
340 self.atlas.icons.push((Self::format_path(&path), icon.clone()));
341 Ok(IconId(id))
342 }
343
344 pub fn add_font(&mut self, path: &str, size: usize) -> Result<FontId> {
346 let font = Self::load_font(path)?;
347 let mut entries = HashMap::new();
348 let mut min_y = i32::MAX;
349 let mut max_y = -i32::MAX;
350 for i in 32..127 {
351 let ch = i as u8 as char;
353 let (metrics, bitmap) = font.rasterize(ch, size as f32);
354 let rect = self.add_tile(
355 metrics.width as _,
356 metrics.height as _,
357 bitmap.iter().map(|c| color4b(0xFF, 0xFF, 0xFF, *c)).collect::<Vec<Color4b>>().as_slice(),
358 )?;
359 let ce = CharEntry {
360 offset: Vec2i::new(metrics.xmin, metrics.ymin),
361 advance: Vec2i::new(metrics.advance_width as _, metrics.advance_height as _),
362 rect,
363 };
364 entries.insert(i as u8 as char, ce);
365 min_y = min_y.min(size as i32 - metrics.ymin - metrics.height as i32);
366 max_y = max_y.max(size as i32 - metrics.ymin - metrics.height as i32);
367 }
368
369 let id = self.atlas.fonts.len();
370 let line_metrics = font.horizontal_line_metrics(size as f32);
371 let line_size = line_metrics
372 .as_ref()
373 .map(|m| m.new_line_size.round() as usize)
374 .unwrap_or((max_y - min_y) as usize);
375 let baseline = line_metrics.as_ref().map(|m| m.ascent.round() as i32).unwrap_or(line_size as i32);
376 let font = super::Font {
377 line_size,
378 baseline,
379 font_size: size,
380 entries,
381 };
382 self.atlas.fonts.push((Self::format_path(path), font.clone()));
383 Ok(FontId(id))
384 }
385
386 pub fn png_image_bytes(atlas: AtlasHandle) -> Result<Vec<u8>> {
388 let mut w: Vec<u8> = Vec::new();
389 let mut cursor = Cursor::new(Vec::new());
390 {
391 let mut encoder = png::Encoder::new(&mut cursor, atlas.width() as _, atlas.height() as _); encoder.set_color(png::ColorType::Rgba);
393 encoder.set_depth(png::BitDepth::Eight);
394
395 let mut writer = encoder.write_header()?;
396
397 writer.write_image_data(
398 atlas
399 .0
400 .borrow()
401 .pixels
402 .iter()
403 .map(|c| [c.x, c.y, c.z, c.w])
404 .flatten()
405 .collect::<Vec<u8>>()
406 .as_slice(),
407 )?;
408 }
409 cursor.seek(std::io::SeekFrom::Start(0))?;
410 cursor.read_to_end(&mut w)?;
411 Ok(w)
412 }
413
414 pub fn save_png_image(atlas: AtlasHandle, path: &str) -> Result<()> {
416 let file = File::create(path)?;
418 let ref mut w = BufWriter::new(file);
419 let bytes = Self::png_image_bytes(atlas)?;
420 w.write_all(bytes.as_slice())?;
421 Ok(())
422 }
423
424 #[cfg(any(feature = "builder", feature = "png_source"))]
425 fn load_icon(path: &str) -> Result<(usize, usize, Vec<Color4b>)> {
426 let mut f = File::open(path)?;
427 let mut bytes = Vec::new();
428 f.read_to_end(&mut bytes)?;
429 load_image_bytes(ImageSource::Png { bytes: bytes.as_slice() })
430 }
431
432 fn add_slot(&mut self, slot: Dimensioni) -> Result<Recti> {
433 let rect = self.packer.pack(slot.width, slot.height, false);
434 match rect {
435 Some(r) => {
436 self.atlas.slots.push(r);
437 Ok(r)
438 }
439 None => {
440 let error = format!(
441 "Bitmap size of {}x{} is not enough to hold the atlas, please resize",
442 self.atlas.width, self.atlas.height
443 );
444 Err(Error::new(ErrorKind::Other, error))
445 }
446 }
447 }
448
449 fn add_tile(&mut self, width: usize, height: usize, pixels: &[Color4b]) -> Result<Recti> {
450 let rect = self.packer.pack(width as _, height as _, false);
451 match rect {
452 Some(r) => {
453 for y in 0..height {
454 for x in 0..width {
455 self.atlas.pixels[(r.x + x as i32 + (r.y + y as i32) * self.atlas.width as i32) as usize] = pixels[x + y * width];
456 }
457 }
458 Ok(Recti::new(r.x, r.y, r.width, r.height))
459 }
460 None if width != 0 && height != 0 => {
461 let error = format!(
462 "Bitmap size of {}x{} is not enough to hold the atlas, please resize",
463 self.atlas.width, self.atlas.height
464 );
465 Err(Error::new(ErrorKind::Other, error))
466 }
467 _ => Ok(Recti::new(0, 0, 0, 0)),
468 }
469 }
470
471 fn load_font(path: &str) -> Result<fontdue::Font> {
472 let mut data = Vec::new();
473 File::open(path)
474 .map_err(|e| Error::new(ErrorKind::Other, format!("Cannot open font file '{}': {}", path, e)))?
475 .read_to_end(&mut data)
476 .map_err(|e| Error::new(ErrorKind::Other, format!("Cannot read font file '{}': {}", path, e)))?;
477
478 let font = fontdue::Font::from_bytes(data, FontSettings::default()).map_err(|error| Error::new(ErrorKind::Other, format!("{}", error)))?;
479 Ok(font)
480 }
481
482 fn strip_path_to_file(path: &str) -> String {
483 let p = Path::new(path);
484 p.file_name().and_then(|n| n.to_str()).unwrap_or(path).to_string()
485 }
486
487 fn strip_extension(path: &str) -> String {
488 let p = Path::new(path);
489 p.with_extension("").to_str().unwrap_or(path).to_string()
490 }
491
492 fn format_path(path: &str) -> String { Self::strip_extension(&Self::strip_path_to_file(path)) }
493
494 pub fn to_atlas(self) -> AtlasHandle { AtlasHandle(Rc::new(RefCell::new(self.atlas))) }
496 }
497}
498
499pub struct FontEntry<'a> {
501 pub line_size: usize, pub baseline: i32,
505 pub font_size: usize, pub entries: &'a [(char, CharEntry)], }
510
511pub enum SourceFormat {
513 Raw,
515 #[cfg(feature = "png_source")]
516 Png,
518}
519
520pub struct AtlasSource<'a> {
522 pub width: usize,
524 pub height: usize,
526 pub pixels: &'a [u8],
528 pub icons: &'a [(&'a str, Recti)],
530 pub fonts: &'a [(&'a str, FontEntry<'a>)],
532 pub format: SourceFormat,
534 pub slots: &'a [Recti],
536}
537
538impl AtlasHandle {
539 fn from_parts<'a>(source: &AtlasSource<'a>, pixels: Vec<Color4b>) -> Self {
540 let icons: Vec<(String, Icon)> = source
541 .icons
542 .iter()
543 .map(|(name, rect)| (name.to_string(), Icon { rect: rect.clone() }))
544 .collect();
545 let fonts: Vec<(String, Font)> = source
546 .fonts
547 .iter()
548 .map(|(name, f)| {
549 let font = Font {
550 line_size: f.line_size,
551 baseline: f.baseline,
552 font_size: f.font_size,
553 entries: f.entries.iter().map(|(ch, e)| (ch.clone(), e.clone())).collect(),
554 };
555 (name.to_string(), font)
556 })
557 .collect();
558 let slots: Vec<Recti> = source.slots.iter().map(|p| *p).collect();
559
560 Self(Rc::new(RefCell::new(Atlas {
561 width: source.width,
562 height: source.height,
563 icons,
564 fonts,
565 slots,
566 pixels,
567 last_update_id: 0,
568 })))
569 }
570
571 pub fn from<'a>(source: &AtlasSource<'a>) -> Self {
575 match Self::try_from(source) {
576 Ok(atlas) => atlas,
577 Err(err) => {
578 debug_assert!(false, "Atlas decode failed: {}", err);
579 let pixel_count = source.width.saturating_mul(source.height);
580 Self::from_parts(source, vec![Color4b::default(); pixel_count])
581 }
582 }
583 }
584
585 pub fn try_from<'a>(source: &AtlasSource<'a>) -> std::io::Result<Self> {
587 let width = i32::try_from(source.width)
588 .map_err(|_| Error::new(ErrorKind::Other, "Atlas width exceeds i32::MAX"))?;
589 let height = i32::try_from(source.height)
590 .map_err(|_| Error::new(ErrorKind::Other, "Atlas height exceeds i32::MAX"))?;
591 let pixels = match source.format {
592 SourceFormat::Raw => {
593 let (raw_width, raw_height, pixels) = load_image_bytes(ImageSource::Raw {
594 width,
595 height,
596 pixels: source.pixels,
597 })?;
598 if raw_width != source.width || raw_height != source.height {
599 return Err(Error::new(ErrorKind::Other, "Atlas dimensions do not match raw data"));
600 }
601 pixels
602 }
603 #[cfg(feature = "png_source")]
604 SourceFormat::Png => {
605 let (png_width, png_height, pixels) = load_image_bytes(ImageSource::Png { bytes: source.pixels })?;
606 if png_width != source.width || png_height != source.height {
607 return Err(Error::new(ErrorKind::Other, "Atlas dimensions do not match PNG data"));
608 }
609 pixels
610 }
611 };
612 Ok(Self::from_parts(source, pixels))
613 }
614
615 #[cfg(feature = "save-to-rust")]
616 pub fn to_rust_files(&self, atlas_name: &str, format: SourceFormat, path: &str) -> Result<()> {
618 let mut font_meta = String::new();
619 font_meta.push_str(format!("use microui_redux::*; pub const {} : AtlasSource = AtlasSource {{\n", atlas_name).as_str());
620 font_meta.push_str(format!("width: {}, height: {},\n", self.width(), self.height()).as_str());
621 let mut icons = String::from_str("&[\n").unwrap();
622 for (i, r) in &self.0.borrow().icons {
623 icons.push_str(
624 format!(
625 "(\"{}\", Rect {{ x: {}, y: {}, width: {}, height: {} }}),",
626 i, r.rect.x, r.rect.y, r.rect.width, r.rect.height,
627 )
628 .as_str(),
629 );
630 }
631 icons.push_str("]");
632 let mut slots = String::from_str("&[\n").unwrap();
633 for r in &self.0.borrow().slots {
634 slots.push_str(format!("Rect {{ x: {}, y: {}, width: {}, height: {} }},", r.x, r.y, r.width, r.height,).as_str());
635 }
636 slots.push_str("]");
637 let mut fonts = String::from_str("&[\n").unwrap();
638 for (n, f) in &self.0.borrow().fonts {
639 let mut char_entries = String::from_str("&[\n").unwrap();
640 for (ch, entry) in &f.entries {
641 let str = match ch {
642 '\'' => String::from_str("\\'").unwrap(),
643 '\\' => String::from_str("\\\\").unwrap(),
644 _ => format!("{}", ch),
645 };
646 char_entries.push_str(
647 format!(
648 "('{}', CharEntry {{ offset: Vec2i {{ x: {}, y:{} }}, advance: Vec2i {{ x:{}, y: {} }}, rect: Recti {{x: {}, y: {}, width: {}, height: {} }}, }}),\n",
649 str, entry.offset.x, entry.offset.y, entry.advance.x, entry.advance.y, entry.rect.x, entry.rect.y, entry.rect.width, entry.rect.height,
650 )
651 .as_str(),
652 );
653 }
654 char_entries.push_str("]\n");
655 fonts.push_str(
656 format!(
657 "(\"{}\", FontEntry {{ line_size: {}, baseline: {}, font_size: {}, entries: {} }}),\n",
658 n, f.line_size, f.baseline, f.font_size, char_entries
659 )
660 .as_str(),
661 );
662 }
663 fonts.push_str("]");
664 font_meta.push_str(format!("icons: {},\n", icons).as_str());
665 font_meta.push_str(format!("fonts: {},\n", fonts).as_str());
666 font_meta.push_str(format!("slots: {},\n", slots).as_str());
667 let (source_pixels, source_format) = match format {
668 SourceFormat::Raw => (
669 self.0
670 .borrow()
671 .pixels
672 .iter()
673 .map(|p| [p.x, p.y, p.z, p.w])
674 .flatten()
675 .collect::<Vec<_>>(),
676 "SourceFormat::Raw",
677 ),
678 #[cfg(feature = "png_source")]
679 SourceFormat::Png => (self.png_image_bytes()?, "SourceFormat::Png"),
680 };
681
682 let mut pixels = String::from_str("&[\n").unwrap();
683 for p in source_pixels {
684 pixels.push_str(format!("0x{:02x},", p).as_str());
685 }
686 pixels.push_str("]\n");
687 font_meta.push_str(format!("format: {},\n", source_format).as_str());
688 font_meta.push_str(format!("pixels: {},\n", pixels).as_str());
689 font_meta.push_str("};");
690 let mut f = File::create(path).unwrap();
691 write!(f, "{}", font_meta)
692 }
693
694 #[cfg(all(feature = "save-to-rust", feature = "png_source"))]
695 fn png_image_bytes(&self) -> Result<Vec<u8>> {
696 let mut bytes = Vec::new();
697 let pixels = self
698 .0
699 .borrow()
700 .pixels
701 .iter()
702 .map(|c| [c.x, c.y, c.z, c.w])
703 .flatten()
704 .collect::<Vec<_>>();
705 {
706 let mut encoder = png::Encoder::new(&mut bytes, self.width() as _, self.height() as _);
707 encoder.set_color(ColorType::Rgba);
708 encoder.set_depth(BitDepth::Eight);
709 let mut writer = encoder.write_header()?;
710 writer.write_image_data(pixels.as_slice())?;
711 }
712 Ok(bytes)
713 }
714
715 pub fn width(&self) -> usize { self.0.borrow().width }
717 pub fn height(&self) -> usize { self.0.borrow().height }
719 pub fn pixels_clone(&self) -> Vec<Color4b> { self.0.borrow().pixels.clone() }
721
722 pub fn apply_pixels<F: FnMut(usize, usize, &Vec<Color4b>)>(&self, mut f: F) {
724 let s = self.0.borrow();
725 f(s.width, s.height, &s.pixels);
726 }
727
728 pub fn clone_icon_table(&self) -> Vec<(String, IconId)> { self.0.borrow().icons.iter().enumerate().map(|(i, icon)| (icon.0.clone(), IconId(i))).collect() }
730
731 pub fn clone_font_table(&self) -> Vec<(String, FontId)> { self.0.borrow().fonts.iter().enumerate().map(|(i, font)| (font.0.clone(), FontId(i))).collect() }
733
734 pub fn clone_slot_table(&self) -> Vec<SlotId> { self.0.borrow().slots.iter().enumerate().map(|(i, _)| SlotId(i)).collect() }
736
737 pub fn get_char_entry(&self, font: FontId, c: char) -> Option<CharEntry> { self.0.borrow().fonts[font.0].1.entries.get(&c).map(|x| x.clone()) }
739
740 pub fn get_font_height(&self, font: FontId) -> usize { self.0.borrow().fonts[font.0].1.line_size }
742
743 pub fn get_font_baseline(&self, font: FontId) -> i32 { self.0.borrow().fonts[font.0].1.baseline }
745
746 pub fn get_icon_size(&self, icon: IconId) -> Dimensioni {
748 let r = self.0.borrow().icons[icon.0].1.rect;
749 Dimensioni::new(r.width, r.height)
750 }
751
752 pub fn get_icon_rect(&self, icon: IconId) -> Recti { self.0.borrow().icons[icon.0].1.rect }
754
755 pub fn get_slot_size(&self, slot: SlotId) -> Dimensioni {
757 let r = self.0.borrow().slots[slot.0];
758 Dimension::new(r.width, r.height)
759 }
760
761 pub(crate) fn get_slot_rect(&self, slot: SlotId) -> Recti { self.0.borrow().slots[slot.0] }
763
764 pub fn get_texture_dimension(&self) -> Dimensioni { Dimension::new(self.0.borrow().width as _, self.0.borrow().height as _) }
766
767 fn walk_glyphs<F>(&self, font: FontId, text: &str, mut f: F)
769 where
770 F: FnMut(char, Vec2i, Recti, Recti, i32),
771 {
772 let mut dst = Recti { x: 0, y: 0, width: 0, height: 0 };
773 let line_height = self.get_font_height(font) as i32;
774 let baseline = self.get_font_baseline(font);
775 let mut baseline_y = baseline;
776 let mut pen_x = 0;
777
778 for chr in text.chars() {
779 if chr == '\n' || chr == '\r' {
780 pen_x = 0;
781 baseline_y += line_height;
782 continue;
783 }
784
785 let src = self.get_char_entry(font, chr).or_else(|| self.get_char_entry(font, '_')).unwrap_or(CharEntry {
786 offset: Vec2i::new(0, 0),
787 advance: Vec2i::new(8, 0),
788 rect: Recti::new(0, 0, 8, 8),
789 });
790
791 dst.width = src.rect.width;
792 dst.height = src.rect.height;
793 dst.x = pen_x + src.offset.x;
794 dst.y = baseline_y - src.offset.y - src.rect.height;
795
796 f(chr, src.advance, dst, src.rect, baseline_y);
797 pen_x += src.advance.x;
798 }
799 }
800
801 pub fn draw_string<DrawFunction: FnMut(char, Vec2i, Recti, Recti)>(&self, font: FontId, text: &str, mut f: DrawFunction) {
803 self.walk_glyphs(font, text, |chr, advance, dst, src, _| f(chr, advance, dst, src));
804 }
805
806 pub fn get_text_size(&self, font: FontId, text: &str) -> Dimensioni {
808 let mut res = Dimensioni::new(0, 0);
809 let line_height = self.get_font_height(font) as i32;
810 let baseline = self.get_font_baseline(font);
811 let descent = (line_height - baseline).max(0);
812 let mut max_line_bottom = 0;
813 let mut saw_glyph = false;
814
815 self.walk_glyphs(font, text, |_, advance, dst, _, baseline_y| {
816 saw_glyph = true;
817 res.width = max(res.width, dst.x + max(advance.x, dst.width));
818 res.height = max(res.height, dst.y + dst.height);
819 max_line_bottom = max(max_line_bottom, baseline_y + descent);
820 });
821
822 if saw_glyph {
823 res.height = max(res.height, max_line_bottom);
824 }
825 res
826 }
827
828 pub fn render_slot(&mut self, slot: SlotId, f: Rc<dyn Fn(usize, usize) -> Color4b>) {
830 let slot_rect = match self.0.borrow().slots.get(slot.0) {
831 Some(rect) => *rect,
832 None => return,
833 };
834 let width = self.width();
835 let height = self.height();
836 {
837 let pixels = &mut self.0.borrow_mut().pixels;
838 let max_y = (slot_rect.y + slot_rect.height).min(height as i32);
839 let max_x = (slot_rect.x + slot_rect.width).min(width as i32);
840 for y in slot_rect.y.max(0)..max_y {
841 for x in slot_rect.x.max(0)..max_x {
842 let index = (x + y * (width as i32)) as usize;
843 if index < pixels.len() {
844 pixels[index] = f(x as _, y as _)
845 }
846 }
847 }
848 }
849 let last_update = self.0.borrow().last_update_id;
850 self.0.borrow_mut().last_update_id = last_update.wrapping_add(1);
851 }
852
853 pub fn get_last_update_id(&self) -> usize { self.0.borrow().last_update_id }
855}
856
857#[cfg(test)]
858mod tests {
859 use super::*;
860 #[cfg(any(feature = "builder", feature = "png_source"))]
861 use png::Encoder;
862 #[cfg(any(feature = "builder", feature = "png_source"))]
863 use std::fmt::Write;
864
865 #[cfg(any(feature = "builder", feature = "png_source"))]
866 fn encode_png(color_type: ColorType, data: &[u8], width: u32, height: u32, palette: Option<&[u8]>) -> Vec<u8> {
867 let mut buffer = Vec::new();
868 {
869 let mut encoder = Encoder::new(&mut buffer, width, height);
870 encoder.set_color(color_type);
871 encoder.set_depth(BitDepth::Eight);
872 if let Some(palette) = palette {
873 encoder.set_palette(palette);
874 }
875 let mut writer = encoder.write_header().unwrap();
876 writer.write_image_data(data).unwrap();
877 }
878 buffer
879 }
880
881 #[cfg(any(feature = "builder", feature = "png_source"))]
882 #[test]
883 fn png_decode_error_returns_err() {
884 let res = load_image_bytes(ImageSource::Png { bytes: &[] });
885 assert!(res.is_err());
886 }
887
888 #[cfg(any(feature = "builder", feature = "png_source"))]
889 #[test]
890 fn png_decode_rgb_expands_alpha() {
891 let bytes = encode_png(ColorType::Rgb, &[10, 20, 30], 1, 1, None);
892 let (width, height, pixels) = load_image_bytes(ImageSource::Png { bytes: &bytes }).unwrap();
893
894 assert_eq!(width, 1);
895 assert_eq!(height, 1);
896 assert_eq!(pixels.len(), 1);
897 let pixel = pixels[0];
898 assert_eq!((pixel.x, pixel.y, pixel.z, pixel.w), (10, 20, 30, 0xFF));
899 }
900
901 #[cfg(any(feature = "builder", feature = "png_source"))]
902 #[test]
903 fn png_decode_indexed_uses_palette() {
904 let palette = [0x01, 0x02, 0x03];
905 let bytes = encode_png(ColorType::Indexed, &[0], 1, 1, Some(&palette));
906 let (width, height, pixels) = load_image_bytes(ImageSource::Png { bytes: &bytes }).unwrap();
907
908 assert_eq!(width, 1);
909 assert_eq!(height, 1);
910 assert_eq!(pixels.len(), 1);
911
912 let pixel = pixels[0];
913 let mut message = String::new();
914 let _ = write!(&mut message, "{},{},{},{}", pixel.x, pixel.y, pixel.z, pixel.w);
915 assert_eq!(message, "1,2,3,255");
916 }
917}