bmfont_rs/
font.rs

1use std::collections::HashSet;
2use std::convert::{TryFrom, TryInto};
3
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7use crate::parse::{Parse, ParseError, ParseResult};
8
9use super::charset::Charset;
10
11/// Bitmap font descriptor.
12///
13/// This object holds, in it's entirety, the information contained within a BMFont descriptor file.
14/// This, when paired with the associated texture file/s, allows us to render the described font.
15///
16/// Data is structured in accordance with the
17/// [Bitmap Font Generator - Documentation](http://www.angelcode.com/products/bmfont/doc/file_format.html)
18/// .
19///
20/// N.B. Certain tools deviate from the BMFont standard and can generate incompatible files.
21///
22/// Outline:
23///
24/// - `info`: holds information on how the font was generated.
25/// - `common`: holds information common to all characters.
26/// - `pages`: holds an ordered list of texture files, the index corresponds to the page id.
27/// - `chars`: holds an unordered list of character descriptions.
28/// - `kernings` holds an unordered list of kerning pairs.
29///
30/// For efficient usage you'll likely want to convert `chars` and `kernings` to maps.
31#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32#[derive(Clone, Debug, Default, PartialEq, Eq)]
33pub struct Font {
34    /// Font information.
35    pub info: Info,
36    /// Common character description.
37    pub common: Common,
38    /// Texture filenames.
39    pub pages: Vec<String>,
40    /// Character descriptors.
41    pub chars: Vec<Char>,
42    /// Kerning pairs.
43    pub kernings: Vec<Kerning>,
44}
45
46impl Font {
47    /// Construct a new Font.
48    ///
49    /// N.B. The supplied arguments are not validated.
50    #[inline(always)]
51    pub fn new(
52        info: Info,
53        common: Common,
54        pages: Vec<String>,
55        chars: Vec<Char>,
56        kernings: Vec<Kerning>,
57    ) -> Self {
58        Self { info, common, pages, chars, kernings }
59    }
60
61    /// Validate references. Ensure that all page/ character references exist. In other words, that
62    /// we don't have references to a non-existent page/ character.
63    pub fn validate_references(&self) -> crate::Result<()> {
64        self.validate_char_references()?;
65        self.validate_kerning_references()?;
66        Ok(())
67    }
68
69    fn validate_char_references(&self) -> crate::Result<()> {
70        for char in &self.chars {
71            if self.pages.len() <= char.page as usize {
72                return Err(crate::Error::InvalidCharPage {
73                    char_id: char.id,
74                    page_id: char.page as u32,
75                });
76            }
77        }
78        Ok(())
79    }
80
81    fn validate_kerning_references(&self) -> crate::Result<()> {
82        let set: HashSet<u32> = self.chars.iter().map(|u| u.id).collect();
83        for kerning in &self.kernings {
84            if !set.contains(&kerning.first) {
85                return Err(crate::Error::InvalidKerningChar { id: kerning.first });
86            }
87            if !set.contains(&kerning.second) {
88                return Err(crate::Error::InvalidKerningChar { id: kerning.second });
89            }
90        }
91        Ok(())
92    }
93}
94
95/// Character description.
96///
97/// This block describes a character in the font.
98/// There is one for each included character in the font.
99#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
100#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
101pub struct Char {
102    /// The character id.
103    pub id: u32,
104    /// The left position of the character image in the texture.
105    pub x: u16,
106    /// The top position of the character image in the texture.   
107    pub y: u16,
108    /// The width of the character image in the texture.
109    pub width: u16,
110    /// The height of the character image in the texture.
111    pub height: u16,
112    /// How much the current position should be offset when copying the image from the texture to
113    /// the screen.
114    pub xoffset: i16,
115    /// How much the current position should be offset when copying the image from the texture to
116    /// the screen.
117    pub yoffset: i16,
118    /// How much the current position should be advanced after drawing the character.
119    pub xadvance: i16,
120    /// The texture page where the character image is found.
121    pub page: u8,
122    /// The texture channel where the character image is found.
123    pub chnl: Chnl,
124}
125
126impl Char {
127    /// Construct a new Char.
128    ///
129    /// N.B. The supplied arguments are not validated.
130    #[allow(clippy::too_many_arguments)]
131    #[inline(always)]
132    pub fn new(
133        id: u32,
134        x: u16,
135        y: u16,
136        width: u16,
137        height: u16,
138        xoffset: i16,
139        yoffset: i16,
140        xadvance: i16,
141        page: u8,
142        chnl: Chnl,
143    ) -> Self {
144        Self { id, x, y, width, height, xoffset, yoffset, xadvance, page, chnl }
145    }
146}
147
148/// Common character description.
149///
150/// This block holds information common to all characters.
151#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
152#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
153pub struct Common {
154    /// This is the distance in pixels between each line of text.
155    pub line_height: u16,
156    /// The number of pixels from the absolute top of the line to the base of the characters.
157    pub base: u16,
158    /// The width of the texture, normally used to scale the x pos of the character image.
159    pub scale_w: u16,
160    /// The height of the texture, normally used to scale the y pos of the character image.
161    pub scale_h: u16,
162    /// The number of texture pages included in the font.
163    pub pages: u16,
164    /// True if the monochrome characters have been packed into each of the texture channels.
165    /// In this case the channel packing describes what is stored in each channel.
166    #[cfg_attr(
167        feature = "serde",
168        serde(serialize_with = "se_bool"),
169        serde(deserialize_with = "de_bool")
170    )]
171    pub packed: bool,
172    /// Alpha channel packing.
173    pub alpha_chnl: Packing,
174    /// Red channel packing.
175    pub red_chnl: Packing,
176    /// Green channel packing.
177    pub green_chnl: Packing,
178    /// Blue channel packing.
179    pub blue_chnl: Packing,
180}
181
182impl Common {
183    /// Construct a new Common block.
184    ///
185    /// N.B. The supplied arguments are not validated.
186    #[allow(clippy::too_many_arguments)]
187    #[inline(always)]
188    pub fn new(
189        line_height: u16,
190        base: u16,
191        scale_w: u16,
192        scale_h: u16,
193        pages: u16,
194        packed: bool,
195        alpha_chnl: Packing,
196        red_chnl: Packing,
197        green_chnl: Packing,
198        blue_chnl: Packing,
199    ) -> Self {
200        Self {
201            line_height,
202            base,
203            scale_w,
204            scale_h,
205            pages,
206            packed,
207            alpha_chnl,
208            red_chnl,
209            green_chnl,
210            blue_chnl,
211        }
212    }
213}
214
215/// Font information.
216///
217/// This block holds information on how the font was generated.
218#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
219#[derive(Clone, Debug, PartialEq, Eq)]
220pub struct Info {
221    /// This is the name of the true type font.
222    pub face: String,
223    /// The size of the true type font.
224    pub size: i16,
225    /// True if the font is bold.
226    #[cfg_attr(
227        feature = "serde",
228        serde(serialize_with = "se_bool"),
229        serde(deserialize_with = "de_bool")
230    )]
231    pub bold: bool,
232    /// True if the font is italic.
233    #[cfg_attr(
234        feature = "serde",
235        serde(serialize_with = "se_bool"),
236        serde(deserialize_with = "de_bool")
237    )]
238    pub italic: bool,
239    /// The name of the OEM charset (when not Unicode).
240    pub charset: Charset,
241    #[cfg_attr(
242        feature = "serde",
243        serde(serialize_with = "se_bool"),
244        serde(deserialize_with = "de_bool")
245    )]
246    /// True if Unicode charset.
247    pub unicode: bool,
248    /// The font height stretch in percentage. 100% means no stretch.
249    pub stretch_h: u16,
250    /// True if smoothing was turned on.
251    #[cfg_attr(
252        feature = "serde",
253        serde(serialize_with = "se_bool"),
254        serde(deserialize_with = "de_bool")
255    )]
256    pub smooth: bool,
257    /// The supersampling level used. 1 means no supersampling was used.
258    pub aa: u8,
259    /// The padding for each character.
260    pub padding: Padding,
261    /// The spacing for each character.
262    pub spacing: Spacing,
263    /// The outline thickness for the characters.
264    #[cfg_attr(feature = "serde", serde(default))]
265    pub outline: u8,
266}
267
268impl Info {
269    /// Construct a new Info block.
270    ///
271    /// N.B. The supplied arguments are not validated.
272    #[allow(clippy::too_many_arguments)]
273    #[inline(always)]
274    pub fn new(
275        face: String,
276        size: i16,
277        bold: bool,
278        italic: bool,
279        charset: Charset,
280        unicode: bool,
281        stretch_height: u16,
282        smooth: bool,
283        aa: u8,
284        padding: Padding,
285        spacing: Spacing,
286        outline: u8,
287    ) -> Self {
288        Self {
289            face,
290            size,
291            bold,
292            italic,
293            charset,
294            unicode,
295            stretch_h: stretch_height,
296            smooth,
297            aa,
298            padding,
299            spacing,
300            outline,
301        }
302    }
303
304    #[allow(dead_code)]
305    pub(crate) fn check_encoding(&self) -> crate::Result<()> {
306        if self.unicode && self.charset != Charset::Null {
307            return Err(crate::Error::InvalidCharsetEncoding {
308                unicode: true,
309                charset: self.charset.clone(),
310            });
311        }
312        Ok(())
313    }
314}
315
316impl Default for Info {
317    fn default() -> Self {
318        Self {
319            face: Default::default(),
320            size: Default::default(),
321            bold: Default::default(),
322            italic: Default::default(),
323            charset: Charset::Tagged(0),
324            unicode: Default::default(),
325            stretch_h: Default::default(),
326            smooth: Default::default(),
327            aa: Default::default(),
328            padding: Default::default(),
329            spacing: Default::default(),
330            outline: Default::default(),
331        }
332    }
333}
334
335#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
336#[derive(Clone, Debug, Default)]
337pub struct Page {
338    pub id: u16,
339    pub file: String,
340}
341
342// impl Page {
343//     #[inline(always)]
344//     pub fn new(id: u16, file: String) -> Self {
345//         Self { id, file }
346//     }
347// }
348
349/// Character padding.
350///
351/// The padding for each character (up, right, down, left).
352#[cfg_attr(
353    feature = "serde",
354    derive(Serialize, Deserialize),
355    serde(from = "[u8; 4]"),
356    serde(into = "[u8; 4]")
357)]
358#[allow(missing_docs)]
359#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
360pub struct Padding {
361    pub up: u8,
362    pub right: u8,
363    pub down: u8,
364    pub left: u8,
365}
366
367impl Padding {
368    /// Construct a new Padding.
369    ///
370    /// N.B. The supplied arguments are not validated.
371    #[inline(always)]
372    pub fn new(up: u8, right: u8, down: u8, left: u8) -> Self {
373        Self { up, right, down, left }
374    }
375}
376
377impl Parse for Padding {
378    fn parse(src: &str) -> ParseResult<Self> {
379        <[u8; 4]>::parse(src).map(Into::into)
380    }
381
382    fn parse_bytes(bytes: &[u8]) -> ParseResult<Self> {
383        <[u8; 4]>::parse_bytes(bytes).map(Into::into)
384    }
385}
386
387impl From<[u8; 4]> for Padding {
388    #[inline(always)]
389    fn from(array: [u8; 4]) -> Self {
390        Self { up: array[0], right: array[1], down: array[2], left: array[3] }
391    }
392}
393
394impl From<Padding> for [u8; 4] {
395    #[inline(always)]
396    fn from(padding: Padding) -> Self {
397        [padding.up, padding.right, padding.down, padding.left]
398    }
399}
400
401/// Character spacing.
402///
403/// The spacing for each character (horizontal, vertical).
404#[cfg_attr(
405    feature = "serde",
406    derive(Serialize, Deserialize),
407    serde(from = "[u8; 2]"),
408    serde(into = "[u8; 2]")
409)]
410#[allow(missing_docs)]
411#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
412pub struct Spacing {
413    pub horizontal: u8,
414    pub vertical: u8,
415}
416
417impl Spacing {
418    /// Construct a new Spacing.
419    ///
420    /// N.B. The supplied arguments are not validated.
421    #[inline(always)]
422    pub fn new(horizontal: u8, vertical: u8) -> Self {
423        Self { horizontal, vertical }
424    }
425}
426
427impl Parse for Spacing {
428    fn parse(src: &str) -> ParseResult<Self> {
429        <[u8; 2]>::parse(src).map(Into::into)
430    }
431
432    fn parse_bytes(bytes: &[u8]) -> ParseResult<Self> {
433        <[u8; 2]>::parse_bytes(bytes).map(Into::into)
434    }
435}
436
437impl From<[u8; 2]> for Spacing {
438    #[inline(always)]
439    fn from(array: [u8; 2]) -> Self {
440        Self { horizontal: array[0], vertical: array[1] }
441    }
442}
443
444impl From<Spacing> for [u8; 2] {
445    #[inline(always)]
446    fn from(spacing: Spacing) -> Self {
447        [spacing.horizontal, spacing.vertical]
448    }
449}
450
451/// Kerning pair.
452///
453/// The kerning information is used to adjust the distance between certain characters,
454/// e.g. some characters should be placed closer to each other than others.
455#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
456#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
457pub struct Kerning {
458    /// The first character id.
459    pub first: u32,
460    /// The second character id.
461    pub second: u32,
462    /// How much the x position should be adjusted when drawing the second character immediately
463    /// following the first.
464    pub amount: i16,
465}
466
467impl Kerning {
468    /// Construct a new Kerning.
469    ///
470    /// N.B. The supplied arguments are not validated.
471    #[inline(always)]
472    pub fn new(first: u32, second: u32, amount: i16) -> Self {
473        Self { first, second, amount }
474    }
475}
476
477/// Channel packing description.
478///
479/// Used when character packing is specified to describe what is stored in each texture
480/// channel.
481#[cfg_attr(
482    feature = "serde",
483    derive(Serialize, Deserialize),
484    serde(try_from = "u8"),
485    serde(into = "u8")
486)]
487#[derive(Clone, Copy, Debug, PartialEq, Eq)]
488#[repr(u8)]
489pub enum Packing {
490    /// Channel holds glyph data.
491    Glyph = 0,
492    /// Channel holds outline data.
493    Outline = 1,
494    /// Channel holds glyph and outline data.
495    GlyphOutline = 2,
496    /// Channel is set to zero.
497    Zero = 3,
498    /// Channel is set to one.
499    One = 4,
500}
501
502impl Default for Packing {
503    #[inline(always)]
504    fn default() -> Self {
505        Self::Glyph
506    }
507}
508
509impl From<Packing> for u8 {
510    #[inline(always)]
511    fn from(chnl: Packing) -> Self {
512        chnl as u8
513    }
514}
515
516impl TryFrom<u8> for Packing {
517    type Error = ParseError;
518
519    fn try_from(chnl: u8) -> Result<Self, Self::Error> {
520        match chnl {
521            0 => Ok(Self::Glyph),
522            1 => Ok(Self::Outline),
523            2 => Ok(Self::GlyphOutline),
524            3 => Ok(Self::Zero),
525            4 => Ok(Self::One),
526            u => Err(ParseError::Other(format!("Packing: invalid u8: {}", u))),
527        }
528    }
529}
530
531impl Parse for Packing {
532    fn parse(src: &str) -> ParseResult<Self> {
533        let u: u8 = src.parse()?;
534        let packing: Packing = u.try_into()?;
535        Ok(packing)
536    }
537}
538
539/// Character texture channel description.
540///
541/// The texture channel/s where the character image is found.
542///
543/// The official BMFont documentation only specifies five possible combinations:
544/// red, blue, green, alpha and all.
545///
546/// These are encoded as the constants:
547/// [RED](Self::RED),
548/// [GREEN](Self::GREEN),
549/// [BLUE](Self::BLUE),
550/// [ALPHA](Self::ALPHA),
551/// [ALL](Self::ALL),
552///
553///
554/// Internally the structure is represented by a byte bit field. The individual channel bits can
555/// be queried and set as desired. Unless you know what you're doing, take care when setting bits
556/// to avoid non-standard combinations.
557///
558/// # Examples
559///
560/// ```
561/// # use bmfont_rs::Chnl;
562/// // Constructing using the standard constants
563/// let chnl = Chnl::RED;
564/// assert!(chnl.red());
565/// assert!(!chnl.green());
566/// assert!(!chnl.blue());
567/// assert!(!chnl.alpha());
568/// ```
569///
570/// ```
571/// # use bmfont_rs::Chnl;
572/// // Matching using the standard constants, although you'll likely want to throw an error
573/// // rather than panic.
574/// let chnl = Chnl::RED;
575/// match chnl {
576///     Chnl::RED => { /* RED handling code here */},
577///     Chnl::ALL => { /* ALL handling code here */},
578///     _ => { /* cannot handle */ panic!() }
579/// }
580/// ```
581#[cfg_attr(
582    feature = "serde",
583    derive(Serialize, Deserialize),
584    serde(try_from = "u8"),
585    serde(into = "u8")
586)]
587#[derive(Clone, Copy, Debug, PartialEq, Eq)]
588pub struct Chnl(u8);
589
590impl Chnl {
591    /// Character image data is stored in all channels.    
592    pub const ALL: Chnl = Chnl(15);
593
594    /// Character image data is stored in the alpha channel.    
595    pub const ALPHA: Chnl = Chnl(8);
596
597    /// Character image data is stored in the red channel.    
598    pub const RED: Chnl = Chnl(4);
599
600    /// Character image data is stored in the green channel.    
601    pub const GREEN: Chnl = Chnl(2);
602
603    /// Character image data is stored in the blue channel.    
604    pub const BLUE: Chnl = Chnl(1);
605
606    /// The alpha channel bit.
607    #[inline(always)]
608    pub fn alpha(self) -> bool {
609        self.0 & 8 != 0
610    }
611
612    /// Set the alpha channel bit.
613    #[inline(always)]
614    pub fn set_alpha(&mut self, v: bool) {
615        if v {
616            self.0 |= 8;
617        } else {
618            self.0 &= !8;
619        }
620    }
621
622    /// The red channel bit.
623    #[inline(always)]
624    pub fn red(self) -> bool {
625        self.0 & 4 != 0
626    }
627
628    /// Set the red channel bit.
629    #[inline(always)]
630    pub fn set_red(&mut self, v: bool) {
631        if v {
632            self.0 |= 4;
633        } else {
634            self.0 &= !4;
635        }
636    }
637
638    /// The green channel bit.
639    #[inline(always)]
640    pub fn green(self) -> bool {
641        self.0 & 2 != 0
642    }
643
644    /// Set the green channel bit.
645    #[inline(always)]
646    pub fn set_green(&mut self, v: bool) {
647        if v {
648            self.0 |= 2;
649        } else {
650            self.0 &= !2;
651        }
652    }
653
654    /// The blue channel bit.
655    #[inline(always)]
656    pub fn blue(self) -> bool {
657        self.0 & 1 != 0
658    }
659
660    /// Set the blue channel bit.
661    #[inline(always)]
662    pub fn set_blue(&mut self, v: bool) {
663        if v {
664            self.0 |= 1;
665        } else {
666            self.0 &= !1;
667        }
668    }
669}
670
671impl Default for Chnl {
672    #[inline(always)]
673    fn default() -> Self {
674        Self::ALL
675    }
676}
677
678impl From<Chnl> for u8 {
679    #[inline(always)]
680    fn from(chnl: Chnl) -> Self {
681        chnl.0
682    }
683}
684
685impl TryFrom<u8> for Chnl {
686    type Error = ParseError;
687
688    fn try_from(u: u8) -> Result<Self, Self::Error> {
689        if u < 0x10 {
690            Ok(Self(u))
691        } else {
692            Err(ParseError::Other(format!("Chnl: invalid u8: {}", u)))
693        }
694    }
695}
696
697impl Parse for Chnl {
698    fn parse(src: &str) -> ParseResult<Self> {
699        let u: u8 = src.parse()?;
700        let packing: Chnl = u.try_into()?;
701        Ok(packing)
702    }
703}
704
705#[cfg(feature = "serde")]
706pub fn se_bool<S: Serializer>(v: &bool, s: S) -> Result<S::Ok, S::Error> {
707    (*v as u8).serialize(s)
708}
709
710#[cfg(feature = "serde")]
711pub fn de_bool<'de, D: Deserializer<'de>>(d: D) -> Result<bool, D::Error> {
712    Ok(u8::deserialize(d)? != 0)
713}