1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
//! A lightweight easy-to-use bitmap font  loader and parser for .sfl files.
//!
//! # Examples
//! ```
//! use bmfont_parser::{BMFont, Format};
//!
//! let bmfont = match BMFont::from_path(&Format::SFL, "examples/fonts/iosevka.sfl") {
//!     Ok(bmfont) => bmfont,
//!     Err(_) => panic!("Failed to load iosevka.sfl"),
//! };
//!
//! println!("bmfont: {}", bmfont);
//! ```

#![warn(missing_docs)]
#![warn(clippy::all)]

#[cfg(test)]
mod tests;

mod bmfont_parser;
mod parser;
mod sfl_parser;

use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::io::{Error, ErrorKind, Read};
use std::path::PathBuf;

pub(crate) fn err<T: Into<String>, U>(text: T) -> Result<U, Error> {
    Err(Error::new(ErrorKind::Other, text.into()))
}

pub(crate) fn result_or<T: Into<String>, U, N>(res: Result<U, N>, text: T) -> Result<U, Error> {
    match res {
        Ok(val) => Ok(val),
        Err(_) => Err(Error::new(ErrorKind::Other, text.into())),
    }
}

/// Represents a single character in the bitmap font atlas. Contains coordinates, sizes, offsets and advances (everything required to render letters from the atlas).
#[derive(Debug, Clone)]
pub struct BMCharacter {
    /// char id of the character.
    pub id: u32,
    /// x-position of the character on the atlas.
    pub x: u32,
    /// y-position of the character on the atlas.
    pub y: u32,
    /// Width of the character on the atlas.
    pub width: u32,
    /// Height of the character.
    pub height: u32,
    /// x-offset of the character.
    pub xoffset: i32,
    /// y-offset of the character.
    pub yoffset: i32,
    /// x-advance of the character.
    pub xadvance: i32,
    /// The texture page where the character is found
    pub page: u32,
    /// The texture channel where the character is found
    pub channel: u32,
}

/// Some details from the info block
#[derive(Debug, Clone)]
pub struct InfoDetails {
    /// Is the font bold
    pub bold: u32,
    /// Is the font italic
    pub italic: u32,
    /// OEM charset name
    pub charset: String,
    /// Is the font unicode
    pub unicode: u32,
    /// height-stretch of the font
    pub stretch_h: u32,
    /// 1 if smoothing was turned on
    pub smooth: u32,
    /// Supersampling level used. 1 means no supersampling
    pub aa: u32,
    /// Padding for each character [up, right, down, left]
    pub padding: [u32; 4],
    /// Spacing for each character [horizontal, vertical]
    pub spacing: [u32; 2],
    /// Outline thickness
    pub outline: u32,
}

/// Some details from the common block
#[derive(Debug, Clone)]
pub struct CommonDetails {
    /// Number of pixels from the absolute top of the line to the base.
    pub base: u32,
    /// Width of the texture
    pub scale_w: u32,
    /// Height of the texture
    pub scale_h: u32,
    /// The amount of pages in this font
    pub pages_count: u32,
    /// 1 if the monochrome characters have been packed into each texture channel
    pub packed: u32,
    /// 0 = holds glyph data  
    /// 1 = holds outline data  
    /// 2 = holds glyph & outline data  
    /// 3 = set to zero  
    /// 4 = set to one
    pub alpha_channel: u32,
    /// 0 = holds glyph data  
    /// 1 = holds outline data  
    /// 2 = holds glyph & outline data  
    /// 3 = set to zero  
    /// 4 = set to one
    pub red_channel: u32,
    /// 0 = holds glyph data  
    /// 1 = holds outline data  
    /// 2 = holds glyph & outline data  
    /// 3 = set to zero  
    /// 4 = set to one
    pub green_channel: u32,
    /// 0 = holds glyph data  
    /// 1 = holds outline data  
    /// 2 = holds glyph & outline data  
    /// 3 = set to zero  
    /// 4 = set to one
    pub blue_channel: u32,
}

/// Loaded and parsed struct of an .sfl file (a bitmap font file).
#[derive(Debug, Clone)]
pub struct BMFont {
    // Info
    /// The name of the font.
    pub font_name: String,
    /// Size of the font.
    pub size: u32,
    /// Some details from the Info-block that are not available in all parsing methods
    pub info_details: Option<InfoDetails>,

    // Common
    /// Line height of the font.
    pub line_height: u32,
    /// Some details from the Common-block that are not available in all parsing methods
    pub common_details: Option<CommonDetails>,

    /// The pages of this font
    pub pages: Vec<Page>,
    /// Hashmap of the characters in the font. <CharID, [`BMCharacter`][bmcharacter]>
    ///
    /// [bmcharacter]: struct.BMCharacter.html
    pub chars: HashMap<u32, BMCharacter>,
}

/// The pages (or textures) of the BMFont
#[derive(Debug, Clone)]
pub struct Page {
    /// The id of this page
    pub id: u32,
    /// The path of the image
    pub image_path: PathBuf,
}

/// Specifies the type of file format which the font file uses.
pub enum Format {
    /// Files ending in .sfl, like those created by FontBuilder
    SFL,
    /// Files ending in .fnt, like those created by BMFont
    BMFont,
}

impl BMFont {
    /// Load and parse a `BMFont` from the given `path`, which should be an .sfl file.
    ///
    /// # Examples
    /// ```
    /// use bmfont_parser::{BMFont, Format};
    ///
    /// let bmfont = match BMFont::from_path(&Format::SFL, "examples/fonts/iosevka.sfl") {
    ///     Ok(bmfont) => bmfont,
    ///     Err(_) => panic!("Failed to load iosevka.sfl"),
    /// };
    ///
    /// println!("bmfont: {}", bmfont);
    /// ```
    pub fn from_path<T: Into<PathBuf>>(format: &Format, path: T) -> Result<BMFont, Error> {
        let path = path.into();
        let mut file = File::open(&path)?;

        let mut buffer = String::new();
        file.read_to_string(&mut buffer)?;

        let mut bmfont = match format {
            Format::SFL => sfl_parser::load(buffer)?,
            Format::BMFont => bmfont_parser::load(buffer)?,
        };

        if let Some(path) = path.parent() {
            for page in bmfont.pages.iter_mut() {
                let mut image_path = (*path).to_path_buf();
                image_path.push(page.image_path.clone());
                page.image_path = image_path;
            }

            Ok(bmfont)
        } else {
            err("Unable to retrieve path parent.")
        }
    }

    /// Load and parse a `BMFont` from the given `String`, which should be the contents of an .sfl file.
    ///
    /// # Examples
    /// ```
    /// use bmfont_parser::{BMFont, Format};
    ///
    /// let iosevka_sfl = include_str!("../examples/fonts/iosevka.sfl");
    ///
    /// let bmfont = match BMFont::from_loaded(&Format::SFL, iosevka_sfl, &["examples/fonts/iosevka.png"]) {
    ///     Ok(bmfont) => bmfont,
    ///     Err(_) => panic!("Failed to load iosevka.sfl"),
    /// };
    ///
    /// println!("bmfont: {}", bmfont);
    /// ```
    pub fn from_loaded<T: Into<String>>(
        format: &Format,
        contents: T,
        image_path: &[&str],
    ) -> Result<BMFont, Error> {
        let mut bmfont = match format {
            Format::SFL => sfl_parser::load(contents)?,
            Format::BMFont => bmfont_parser::load(contents)?,
        };

        for (idx, page) in bmfont.pages.iter_mut().enumerate() {
            let mut pathbuf = PathBuf::new();
            let path;
            if let Some(p) = image_path.get(idx) {
                path = p;
            } else {
                return err("Wrong amount of image paths given to accompany each page");
            };
            pathbuf.push(path);
            page.image_path = pathbuf;
        }

        Ok(bmfont)
    }
}

impl Display for BMFont {
    fn fmt<'a>(&self, f: &mut Formatter<'a>) -> std::fmt::Result {
        write!(
            f,
            "BMFont: {{ name: {}, line_height: {}, size: {}, amount of characters: {}, pages: {:?} }}",
            self.font_name,
            self.line_height,
            self.size,
            self.chars.len(),
            self.pages
        )
    }
}