skrifa/
bitmap.rs

1//! Bitmap strikes and glyphs.
2
3use super::{instance::Size, metrics::GlyphMetrics, MetadataProvider};
4use crate::prelude::LocationRef;
5use raw::{
6    tables::{bitmap, cbdt, cblc, ebdt, eblc, sbix},
7    types::{GlyphId, Tag},
8    FontData, FontRef, TableProvider,
9};
10
11/// Set of strikes, each containing embedded bitmaps of a single size.
12#[derive(Clone)]
13pub struct BitmapStrikes<'a>(StrikesKind<'a>);
14
15impl<'a> BitmapStrikes<'a> {
16    /// Creates a new `BitmapStrikes` for the given font.
17    ///
18    /// This will prefer `sbix`, `CBDT`, and `CBLC` formats in that order.
19    ///
20    /// To select a specific format, use [`with_format`](Self::with_format).
21    pub fn new(font: &FontRef<'a>) -> Self {
22        for format in [BitmapFormat::Sbix, BitmapFormat::Cbdt, BitmapFormat::Ebdt] {
23            if let Some(strikes) = Self::with_format(font, format) {
24                return strikes;
25            }
26        }
27        Self(StrikesKind::None)
28    }
29
30    /// Creates a new `BitmapStrikes` for the given font and format.
31    ///
32    /// Returns `None` if the requested format is not available.
33    pub fn with_format(font: &FontRef<'a>, format: BitmapFormat) -> Option<Self> {
34        let kind = match format {
35            BitmapFormat::Sbix => StrikesKind::Sbix(
36                font.sbix().ok()?,
37                font.glyph_metrics(Size::unscaled(), LocationRef::default()),
38            ),
39            BitmapFormat::Cbdt => {
40                StrikesKind::Cbdt(CbdtTables::new(font.cblc().ok()?, font.cbdt().ok()?))
41            }
42            BitmapFormat::Ebdt => {
43                StrikesKind::Ebdt(EbdtTables::new(font.eblc().ok()?, font.ebdt().ok()?))
44            }
45        };
46        Some(Self(kind))
47    }
48
49    /// Returns the format representing the underlying table for this set of
50    /// strikes.
51    pub fn format(&self) -> Option<BitmapFormat> {
52        match &self.0 {
53            StrikesKind::None => None,
54            StrikesKind::Sbix(..) => Some(BitmapFormat::Sbix),
55            StrikesKind::Cbdt(..) => Some(BitmapFormat::Cbdt),
56            StrikesKind::Ebdt(..) => Some(BitmapFormat::Ebdt),
57        }
58    }
59
60    /// Returns the number of available strikes.
61    pub fn len(&self) -> usize {
62        match &self.0 {
63            StrikesKind::None => 0,
64            StrikesKind::Sbix(sbix, _) => sbix.strikes().len(),
65            StrikesKind::Cbdt(cbdt) => cbdt.location.bitmap_sizes().len(),
66            StrikesKind::Ebdt(ebdt) => ebdt.location.bitmap_sizes().len(),
67        }
68    }
69
70    /// Returns true if there are no available strikes.
71    pub fn is_empty(&self) -> bool {
72        self.len() == 0
73    }
74
75    /// Returns the strike at the given index.
76    pub fn get(&self, index: usize) -> Option<BitmapStrike<'a>> {
77        let kind = match &self.0 {
78            StrikesKind::None => return None,
79            StrikesKind::Sbix(sbix, metrics) => {
80                StrikeKind::Sbix(sbix.strikes().get(index).ok()?, metrics.clone())
81            }
82            StrikesKind::Cbdt(tables) => StrikeKind::Cbdt(
83                tables.location.bitmap_sizes().get(index).copied()?,
84                tables.clone(),
85            ),
86            StrikesKind::Ebdt(tables) => StrikeKind::Ebdt(
87                tables.location.bitmap_sizes().get(index).copied()?,
88                tables.clone(),
89            ),
90        };
91        Some(BitmapStrike(kind))
92    }
93
94    /// Returns the best matching glyph for the given size and glyph
95    /// identifier.
96    ///
97    /// In this case, "best" means a glyph of the exact size, nearest larger
98    /// size, or nearest smaller size, in that order.
99    pub fn glyph_for_size(&self, size: Size, glyph_id: GlyphId) -> Option<BitmapGlyph<'a>> {
100        // Return the largest size for an unscaled request
101        let size = size.ppem().unwrap_or(f32::MAX);
102        self.iter()
103            .fold(None, |best: Option<BitmapGlyph<'a>>, entry| {
104                let entry_size = entry.ppem();
105                if let Some(best) = best {
106                    let best_size = best.ppem_y;
107                    if (entry_size >= size && entry_size < best_size)
108                        || (best_size < size && entry_size > best_size)
109                    {
110                        entry.get(glyph_id).or(Some(best))
111                    } else {
112                        Some(best)
113                    }
114                } else {
115                    entry.get(glyph_id)
116                }
117            })
118    }
119
120    /// Returns an iterator over all available strikes.
121    pub fn iter(&self) -> impl Iterator<Item = BitmapStrike<'a>> + 'a + Clone {
122        let this = self.clone();
123        (0..this.len()).filter_map(move |ix| this.get(ix))
124    }
125}
126
127#[derive(Clone)]
128enum StrikesKind<'a> {
129    None,
130    Sbix(sbix::Sbix<'a>, GlyphMetrics<'a>),
131    Cbdt(CbdtTables<'a>),
132    Ebdt(EbdtTables<'a>),
133}
134
135/// Set of embedded bitmap glyphs of a specific size.
136#[derive(Clone)]
137pub struct BitmapStrike<'a>(StrikeKind<'a>);
138
139impl<'a> BitmapStrike<'a> {
140    /// Returns the pixels-per-em (size) of this strike.
141    pub fn ppem(&self) -> f32 {
142        match &self.0 {
143            StrikeKind::Sbix(sbix, _) => sbix.ppem() as f32,
144            // Original implementation also considers `ppem_y` here:
145            // https://github.com/google/skia/blob/02cd0561f4f756bf4f7b16641d8fc4c61577c765/src/ports/fontations/src/bitmap.rs#L48
146            StrikeKind::Cbdt(size, _) => size.ppem_y() as f32,
147            StrikeKind::Ebdt(size, _) => size.ppem_y() as f32,
148        }
149    }
150
151    /// Returns a bitmap glyph for the given identifier, if available.
152    pub fn get(&self, glyph_id: GlyphId) -> Option<BitmapGlyph<'a>> {
153        match &self.0 {
154            StrikeKind::Sbix(sbix, metrics) => {
155                let glyph = sbix.glyph_data(glyph_id).ok()??;
156                if glyph.graphic_type() != Tag::new(b"png ") {
157                    return None;
158                }
159
160                // Note that this calculation does not entirely correspond to the description in
161                // the specification, but it's implemented this way in Skia (https://github.com/google/skia/blob/02cd0561f4f756bf4f7b16641d8fc4c61577c765/src/ports/fontations/src/bitmap.rs#L161-L178),
162                // the implementation of which has been tested against behavior in CoreText.
163                let glyf_bb = metrics.bounds(glyph_id).unwrap_or_default();
164                let lsb = metrics.left_side_bearing(glyph_id).unwrap_or_default();
165                let ppem = sbix.ppem() as f32;
166                let png_data = glyph.data();
167                // PNG format:
168                // 8 byte header, IHDR chunk (4 byte length, 4 byte chunk type), width, height
169                let reader = FontData::new(png_data);
170                let width = reader.read_at::<u32>(16).ok()?;
171                let height = reader.read_at::<u32>(20).ok()?;
172                Some(BitmapGlyph {
173                    data: BitmapData::Png(glyph.data()),
174                    bearing_x: lsb,
175                    bearing_y: glyf_bb.y_min,
176                    inner_bearing_x: glyph.origin_offset_x() as f32,
177                    inner_bearing_y: glyph.origin_offset_y() as f32,
178                    ppem_x: ppem,
179                    ppem_y: ppem,
180                    width,
181                    height,
182                    advance: None,
183                    placement_origin: Origin::BottomLeft,
184                })
185            }
186            StrikeKind::Cbdt(size, tables) => {
187                let location = size
188                    .location(tables.location.offset_data(), glyph_id)
189                    .ok()?;
190                let data = tables.data.data(&location).ok()?;
191                BitmapGlyph::from_bdt(size, &data)
192            }
193            StrikeKind::Ebdt(size, tables) => {
194                let location = size
195                    .location(tables.location.offset_data(), glyph_id)
196                    .ok()?;
197                let data = tables.data.data(&location).ok()?;
198                BitmapGlyph::from_bdt(size, &data)
199            }
200        }
201    }
202}
203
204#[derive(Clone)]
205enum StrikeKind<'a> {
206    Sbix(sbix::Strike<'a>, GlyphMetrics<'a>),
207    Cbdt(bitmap::BitmapSize, CbdtTables<'a>),
208    Ebdt(bitmap::BitmapSize, EbdtTables<'a>),
209}
210
211#[derive(Clone)]
212struct BdtTables<L, D> {
213    location: L,
214    data: D,
215}
216
217impl<L, D> BdtTables<L, D> {
218    fn new(location: L, data: D) -> Self {
219        Self { location, data }
220    }
221}
222
223type CbdtTables<'a> = BdtTables<cblc::Cblc<'a>, cbdt::Cbdt<'a>>;
224type EbdtTables<'a> = BdtTables<eblc::Eblc<'a>, ebdt::Ebdt<'a>>;
225
226/// An embedded bitmap glyph.
227#[derive(Clone)]
228pub struct BitmapGlyph<'a> {
229    /// The underlying data of the bitmap glyph.
230    pub data: BitmapData<'a>,
231    /// Outer glyph bearings in the x direction, given in font units.
232    pub bearing_x: f32,
233    /// Outer glyph bearings in the y direction, given in font units.
234    pub bearing_y: f32,
235    /// Inner glyph bearings in the x direction, given in pixels. This value should be scaled
236    /// by `ppem_*` and be applied as an offset when placing the image within the bounds rectangle.
237    pub inner_bearing_x: f32,
238    /// Inner glyph bearings in the y direction, given in pixels. This value should be scaled
239    /// by `ppem_*` and be applied as an offset when placing the image within the bounds rectangle.
240    pub inner_bearing_y: f32,
241    /// The assumed pixels-per-em in the x direction.
242    pub ppem_x: f32,
243    /// The assumed pixels-per-em in the y direction.
244    pub ppem_y: f32,
245    /// The horizontal advance width of the bitmap glyph in pixels, if given.
246    pub advance: Option<f32>,
247    /// The number of columns in the bitmap.
248    pub width: u32,
249    /// The number of rows in the bitmap.
250    pub height: u32,
251    /// The placement origin of the bitmap.
252    pub placement_origin: Origin,
253}
254
255impl<'a> BitmapGlyph<'a> {
256    fn from_bdt(
257        bitmap_size: &bitmap::BitmapSize,
258        bitmap_data: &bitmap::BitmapData<'a>,
259    ) -> Option<Self> {
260        let metrics = BdtMetrics::new(bitmap_data);
261        let (ppem_x, ppem_y) = (bitmap_size.ppem_x() as f32, bitmap_size.ppem_y() as f32);
262        let bpp = bitmap_size.bit_depth();
263        let data = match bpp {
264            32 => {
265                match &bitmap_data.content {
266                    bitmap::BitmapContent::Data(bitmap::BitmapDataFormat::Png, bytes) => {
267                        BitmapData::Png(bytes)
268                    }
269                    // 32-bit formats are always byte aligned
270                    bitmap::BitmapContent::Data(bitmap::BitmapDataFormat::ByteAligned, bytes) => {
271                        BitmapData::Bgra(bytes)
272                    }
273                    _ => return None,
274                }
275            }
276            1 | 2 | 4 | 8 => {
277                let (data, is_packed) = match &bitmap_data.content {
278                    bitmap::BitmapContent::Data(bitmap::BitmapDataFormat::ByteAligned, bytes) => {
279                        (bytes, false)
280                    }
281                    bitmap::BitmapContent::Data(bitmap::BitmapDataFormat::BitAligned, bytes) => {
282                        (bytes, true)
283                    }
284                    _ => return None,
285                };
286                BitmapData::Mask(MaskData {
287                    bpp,
288                    is_packed,
289                    data,
290                })
291            }
292            // All other bit depth values are invalid
293            _ => return None,
294        };
295        Some(Self {
296            data,
297            bearing_x: 0.0,
298            bearing_y: 0.0,
299            inner_bearing_x: metrics.inner_bearing_x,
300            inner_bearing_y: metrics.inner_bearing_y,
301            ppem_x,
302            ppem_y,
303            width: metrics.width,
304            height: metrics.height,
305            advance: Some(metrics.advance),
306            placement_origin: Origin::TopLeft,
307        })
308    }
309}
310
311struct BdtMetrics {
312    inner_bearing_x: f32,
313    inner_bearing_y: f32,
314    advance: f32,
315    width: u32,
316    height: u32,
317}
318
319impl BdtMetrics {
320    fn new(data: &bitmap::BitmapData) -> Self {
321        match data.metrics {
322            bitmap::BitmapMetrics::Small(metrics) => Self {
323                inner_bearing_x: metrics.bearing_x() as f32,
324                inner_bearing_y: metrics.bearing_y() as f32,
325                advance: metrics.advance() as f32,
326                width: metrics.width() as u32,
327                height: metrics.height() as u32,
328            },
329            bitmap::BitmapMetrics::Big(metrics) => Self {
330                inner_bearing_x: metrics.hori_bearing_x() as f32,
331                inner_bearing_y: metrics.hori_bearing_y() as f32,
332                advance: metrics.hori_advance() as f32,
333                width: metrics.width() as u32,
334                height: metrics.height() as u32,
335            },
336        }
337    }
338}
339
340///The origin point for drawing a bitmap glyph.
341#[derive(Copy, Clone, PartialEq, Eq, Debug)]
342pub enum Origin {
343    /// The origin is in the top-left.
344    TopLeft,
345    /// The origin is in the bottom-left.
346    BottomLeft,
347}
348
349/// Data content of a bitmap.
350#[derive(Clone)]
351pub enum BitmapData<'a> {
352    /// Uncompressed 32-bit color bitmap data, pre-multiplied in BGRA order
353    /// and encoded in the sRGB color space.
354    Bgra(&'a [u8]),
355    /// Compressed PNG bitmap data.
356    Png(&'a [u8]),
357    /// Data representing a single channel alpha mask.
358    Mask(MaskData<'a>),
359}
360
361/// A single channel alpha mask.
362#[derive(Clone)]
363pub struct MaskData<'a> {
364    /// Number of bits-per-pixel. Always 1, 2, 4 or 8.
365    pub bpp: u8,
366    /// True if each row of the data is bit-aligned. Otherwise, each row
367    /// is padded to the next byte.
368    pub is_packed: bool,
369    /// Raw bitmap data.
370    pub data: &'a [u8],
371}
372
373/// The format (or table) containing the data backing a set of bitmap strikes.
374#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
375pub enum BitmapFormat {
376    Sbix,
377    Cbdt,
378    Ebdt,
379}
380
381#[cfg(test)]
382mod tests {
383    use crate::bitmap::{BitmapData, StrikesKind};
384    use crate::prelude::Size;
385    use crate::{GlyphId, MetadataProvider};
386    use raw::FontRef;
387
388    #[test]
389    fn cbdt_metadata() {
390        let font = FontRef::new(font_test_data::CBDT).unwrap();
391        let strikes = font.bitmap_strikes();
392
393        assert!(matches!(strikes.0, StrikesKind::Cbdt(_)));
394        assert!(matches!(strikes.len(), 3));
395
396        // Note that this is only `ppem_y`.
397        assert!(matches!(strikes.get(0).unwrap().ppem(), 16.0));
398        assert!(matches!(strikes.get(1).unwrap().ppem(), 64.0));
399        assert!(matches!(strikes.get(2).unwrap().ppem(), 128.0));
400    }
401
402    #[test]
403    fn cbdt_glyph_metrics() {
404        let font = FontRef::new(font_test_data::CBDT).unwrap();
405        let strike_0 = font.bitmap_strikes().get(0).unwrap();
406
407        let zero = strike_0.get(GlyphId::new(0)).unwrap();
408        assert_eq!(zero.width, 11);
409        assert_eq!(zero.height, 13);
410        assert_eq!(zero.bearing_x, 0.0);
411        assert_eq!(zero.bearing_y, 0.0);
412        assert_eq!(zero.inner_bearing_x, 1.0);
413        assert_eq!(zero.inner_bearing_y, 13.0);
414        assert_eq!(zero.advance, Some(12.0));
415
416        let strike_1 = font.bitmap_strikes().get(1).unwrap();
417
418        let zero = strike_1.get(GlyphId::new(2)).unwrap();
419        assert_eq!(zero.width, 39);
420        assert_eq!(zero.height, 52);
421        assert_eq!(zero.bearing_x, 0.0);
422        assert_eq!(zero.bearing_y, 0.0);
423        assert_eq!(zero.inner_bearing_x, 6.0);
424        assert_eq!(zero.inner_bearing_y, 52.0);
425        assert_eq!(zero.advance, Some(51.0));
426    }
427
428    #[test]
429    fn cbdt_glyph_selection() {
430        let font = FontRef::new(font_test_data::CBDT).unwrap();
431        let strikes = font.bitmap_strikes();
432
433        let g1 = strikes
434            .glyph_for_size(Size::new(12.0), GlyphId::new(2))
435            .unwrap();
436        assert_eq!(g1.ppem_x, 16.0);
437
438        let g2 = strikes
439            .glyph_for_size(Size::new(17.0), GlyphId::new(2))
440            .unwrap();
441        assert_eq!(g2.ppem_x, 64.0);
442
443        let g3 = strikes
444            .glyph_for_size(Size::new(60.0), GlyphId::new(2))
445            .unwrap();
446        assert_eq!(g3.ppem_x, 64.0);
447
448        let g4 = strikes
449            .glyph_for_size(Size::unscaled(), GlyphId::new(2))
450            .unwrap();
451        assert_eq!(g4.ppem_x, 128.0);
452    }
453
454    #[test]
455    fn sbix_metadata() {
456        let font = FontRef::new(font_test_data::NOTO_HANDWRITING_SBIX).unwrap();
457        let strikes = font.bitmap_strikes();
458
459        assert!(matches!(strikes.0, StrikesKind::Sbix(_, _)));
460        assert!(matches!(strikes.len(), 1));
461
462        assert!(matches!(strikes.get(0).unwrap().ppem(), 109.0));
463    }
464
465    #[test]
466    fn sbix_glyph_metrics() {
467        let font = FontRef::new(font_test_data::NOTO_HANDWRITING_SBIX).unwrap();
468        let strike_0 = font.bitmap_strikes().get(0).unwrap();
469
470        let g0 = strike_0.get(GlyphId::new(7)).unwrap();
471        // `bearing_x` is always the lsb, which is 0 for this glyph.
472        assert_eq!(g0.bearing_x, 0.0);
473        // The glyph doesn't have an associated outline, so `bbox.min_y` is 0, and thus bearing_y
474        // should also be 0.
475
476        assert_eq!(g0.bearing_y, 0.0);
477        // Origin offsets are 4.0 and -27.0 respectively.
478        assert_eq!(g0.inner_bearing_x, 4.0);
479        assert_eq!(g0.inner_bearing_y, -27.0);
480        assert!(matches!(g0.data, BitmapData::Png(_)))
481    }
482}