microui_redux/
atlas.rs

1//
2// Copyright 2023-Present (c) Raja Lehtihet & Wael El Oraiby
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are met:
6//
7// 1. Redistributions of source code must retain the above copyright notice,
8// this list of conditions and the following disclaimer.
9//
10// 2. Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13//
14// 3. Neither the name of the copyright holder nor the names of its contributors
15// may be used to endorse or promote products derived from this software without
16// specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28// POSSIBILITY OF SUCH DAMAGE.
29//
30
31#[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)]
59/// Metrics and atlas coordinates for a glyph.
60pub struct CharEntry {
61    /// Pixel offset relative to the draw origin.
62    pub offset: Vec2i,
63    /// Horizontal advance after drawing this glyph.
64    pub advance: Vec2i,
65    /// Rectangle inside the atlas texture.
66    pub rect: Recti, // coordinates in the atlas
67}
68
69#[derive(Clone)]
70struct Font {
71    line_size: usize,                  // line size
72    baseline: i32,                     // distance from top of line to baseline
73    font_size: usize,                  // font size in pixels
74    entries: HashMap<char, CharEntry>, // all printable chars [32-127]
75}
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)]
92/// Handle referencing a font stored in the atlas.
93pub struct FontId(usize);
94
95#[derive(Default, Copy, Clone)]
96/// Handle referencing a bitmap icon stored in the atlas.
97pub struct IconId(usize);
98
99#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
100/// Handle referencing an arbitrary image slot stored in the atlas.
101pub 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)]
127/// Shared handle exposing read/write access to the atlas.
128pub struct AtlasHandle(Rc<RefCell<Atlas>>);
129
130/// Identifier of the solid white icon baked into the default atlas.
131pub const WHITE_ICON: IconId = IconId(0);
132/// Identifier of the close icon baked into the default atlas.
133pub const CLOSE_ICON: IconId = IconId(1);
134/// Identifier of the expand icon baked into the default atlas.
135pub const EXPAND_ICON: IconId = IconId(2);
136/// Identifier of the collapse icon baked into the default atlas.
137pub const COLLAPSE_ICON: IconId = IconId(3);
138/// Identifier of the checkbox icon baked into the default atlas.
139pub const CHECK_ICON: IconId = IconId(4);
140/// Identifier of the combo-box expand icon baked into the default atlas.
141pub const EXPAND_DOWN_ICON: IconId = IconId(5);
142/// Identifier of the open-folder icon baked into the default atlas.
143pub const OPEN_FOLDER_16_ICON: IconId = IconId(6);
144/// Identifier of the closed-folder icon baked into the default atlas.
145pub const CLOSED_FOLDER_16_ICON: IconId = IconId(7);
146/// Identifier of the file icon baked into the default atlas.
147pub const FILE_16_ICON: IconId = IconId(8);
148
149/// Decodes image data into 32-bit pixels according to `source`.
150/// Grayscale and RGB PNG inputs are expanded to opaque RGBA (alpha = 255).
151pub 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")]
243/// Helpers for constructing atlas textures at build time.
244pub mod builder {
245    use std::io::Seek;
246
247    use super::*;
248    use fontdue::*;
249
250    use rect_packer::*;
251
252    /// Incrementally constructs an atlas by packing fonts, icons, and slots.
253    pub struct Builder {
254        packer: Packer,
255        atlas: Atlas,
256    }
257
258    #[derive(Clone)]
259    /// Configuration for constructing an atlas from disk assets.
260    #[cfg(feature = "builder")]
261    pub struct Config<'a> {
262        /// Width of the atlas texture in pixels.
263        pub texture_width: usize,
264        /// Height of the atlas texture in pixels.
265        pub texture_height: usize,
266        /// Path to the solid white icon.
267        pub white_icon: String,
268        /// Path to the close icon.
269        pub close_icon: String,
270        /// Path to the expand icon.
271        pub expand_icon: String,
272        /// Path to the collapse icon.
273        pub collapse_icon: String,
274        /// Path to the checkbox icon.
275        pub check_icon: String,
276        /// Path to the combo box expand icon.
277        pub expand_down_icon: String,
278        /// Path to the open-folder icon.
279        pub open_folder_16_icon: String,
280        /// Path to the closed-folder icon.
281        pub closed_folder_16_icon: String,
282        /// Path to the file icon.
283        pub file_16_icon: String,
284        /// Path to the default font file.
285        pub default_font: String,
286        /// Size of the default font.
287        pub default_font_size: usize,
288        /// Dimensions of additional slots to reserve in the atlas.
289        pub slots: &'a [Dimensioni],
290    }
291
292    impl Builder {
293        /// Creates a builder using the provided configuration and assets.
294        #[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        /// Adds an icon from the given image path and returns its [`IconId`].
335        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        /// Adds a font at the requested size and returns its [`FontId`].
345        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                // Rasterize and get the layout metrics for the letter at font size.
352                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        /// Serializes the atlas texture into PNG bytes.
387        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 _); // Width is 2 pixels and height is 1.
392                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        /// Writes the atlas texture to disk as a PNG.
415        pub fn save_png_image(atlas: AtlasHandle, path: &str) -> Result<()> {
416            // png writer
417            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        /// Consumes the builder and returns an [`AtlasHandle`].
495        pub fn to_atlas(self) -> AtlasHandle { AtlasHandle(Rc::new(RefCell::new(self.atlas))) }
496    }
497}
498
499/// Describes a font baked into an [`AtlasSource`].
500pub struct FontEntry<'a> {
501    /// Distance between baselines in pixels.
502    pub line_size: usize, // line size
503    /// Offset from the top of the line to the baseline.
504    pub baseline: i32,
505    /// Requested pixel size.
506    pub font_size: usize, // font size in pixels
507    /// Glyph metadata table.
508    pub entries: &'a [(char, CharEntry)], // all printable chars [32-127]
509}
510
511/// Encodes how atlas pixel data is stored.
512pub enum SourceFormat {
513    /// Raw RGBA byte array.
514    Raw,
515    #[cfg(feature = "png_source")]
516    /// PNG-formatted byte array.
517    Png,
518}
519
520/// Serializable representation of an atlas that can be shipped with the binary.
521pub struct AtlasSource<'a> {
522    /// Width of the atlas texture.
523    pub width: usize,
524    /// Height of the atlas texture.
525    pub height: usize,
526    /// Pixel data matching [`AtlasSource::format`].
527    pub pixels: &'a [u8],
528    /// Icon lookup table.
529    pub icons: &'a [(&'a str, Recti)],
530    /// Fonts baked into the atlas.
531    pub fonts: &'a [(&'a str, FontEntry<'a>)],
532    /// Encoding of [`AtlasSource::pixels`].
533    pub format: SourceFormat,
534    /// Slot rectangles reserved in the atlas.
535    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    /// Reconstructs an atlas from a serialized [`AtlasSource`].
572    /// If image decoding fails, a blank atlas is returned; use [`AtlasHandle::try_from`]
573    /// to handle errors explicitly.
574    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    /// Attempts to reconstruct an atlas from a serialized [`AtlasSource`].
586    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    /// Serializes the atlas into Rust source files for reuse at build time.
617    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    /// Returns the atlas texture width in pixels.
716    pub fn width(&self) -> usize { self.0.borrow().width }
717    /// Returns the atlas texture height in pixels.
718    pub fn height(&self) -> usize { self.0.borrow().height }
719    /// Returns a clone of the atlas pixel data.
720    pub fn pixels_clone(&self) -> Vec<Color4b> { self.0.borrow().pixels.clone() }
721
722    /// Executes a closure with shared access to the atlas pixels.
723    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    /// Returns a mapping from icon names to their identifiers.
729    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    /// Returns a mapping from font names to their identifiers.
732    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    /// Returns a list of available slot identifiers.
735    pub fn clone_slot_table(&self) -> Vec<SlotId> { self.0.borrow().slots.iter().enumerate().map(|(i, _)| SlotId(i)).collect() }
736
737    /// Returns glyph metrics for the specified character, if available.
738    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    /// Returns the line height for the specified font.
741    pub fn get_font_height(&self, font: FontId) -> usize { self.0.borrow().fonts[font.0].1.line_size }
742
743    /// Returns the baseline offset (in pixels) for the specified font.
744    pub fn get_font_baseline(&self, font: FontId) -> i32 { self.0.borrow().fonts[font.0].1.baseline }
745
746    /// Returns the dimensions of an icon.
747    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    /// Returns the atlas rectangle storing an icon.
753    pub fn get_icon_rect(&self, icon: IconId) -> Recti { self.0.borrow().icons[icon.0].1.rect }
754
755    /// Returns the dimensions of a slot.
756    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    /// Returns the atlas rectangle storing a slot.
762    pub(crate) fn get_slot_rect(&self, slot: SlotId) -> Recti { self.0.borrow().slots[slot.0] }
763
764    /// Returns the atlas texture dimensions.
765    pub fn get_texture_dimension(&self) -> Dimensioni { Dimension::new(self.0.borrow().width as _, self.0.borrow().height as _) }
766
767    /// Internal helper that walks glyphs applying baseline-aware placement.
768    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    /// Walks each glyph in the string and invokes the closure with draw information.
802    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    /// Measures the bounding box of the provided text.
807    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    /// Renders into a slot using the provided callback and bumps the update counter.
829    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    /// Returns a monotonically increasing value that changes whenever slot pixels are modified.
854    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}