diff --git a/font-test-data/src/lib.rs b/font-test-data/src/lib.rs
index cff1e07cf..dbc3ccc69 100644
--- a/font-test-data/src/lib.rs
+++ b/font-test-data/src/lib.rs
@@ -37,6 +37,8 @@ pub static CANTARELL_VF_TRIMMED_GLYPHS: &str =
pub static CHARSTRING_PATH_OPS: &[u8] = include_bytes!("../test_data/ttf/charstring_path_ops.ttf");
+pub static EMBEDDED_BITMAPS: &[u8] = include_bytes!("../test_data/ttf/embedded_bitmaps.ttf");
+
pub mod post {
#[rustfmt::skip]
diff --git a/font-test-data/test_data/ttf/embedded_bitmaps.ttf b/font-test-data/test_data/ttf/embedded_bitmaps.ttf
new file mode 100644
index 000000000..eeba70af3
Binary files /dev/null and b/font-test-data/test_data/ttf/embedded_bitmaps.ttf differ
diff --git a/font-test-data/test_data/ttx/embedded_bitmaps.ttx b/font-test-data/test_data/ttx/embedded_bitmaps.ttx
new file mode 100644
index 000000000..9cd11bea7
--- /dev/null
+++ b/font-test-data/test_data/ttx/embedded_bitmaps.ttx
@@ -0,0 +1,313 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ eeaeea
+
+
+
+
+
+
+
+
+
+
+
+ f0f0f0f0
+
+
+
+
+
+
+
+
+
+
+
+ abababab
+
+
+
+
+
+
+ aabbccdd 00112233 ffee1234 424242aa
+ 88990011
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 89504e47 0d0a1a0a 0000000d 49484452
+ 00000088 00000080 08030000 00e737d1
+ 0d000000 8a504c54 4547704c 5c5c5c75
+ 75756d6d 6d727272 7474746a 69696c6c
+ 6c696969 6f6f6f6b 6b6b6968 68737272
+ 56555570 70706d6d 6d6b6b6b 61606068
+ 68686666 66646363 64636354 53535b59
+ 59616060 52515154 5353605f 5f5e5e5e
+ 5d5d5d5b 5b5b5150 505a5959 51505052
+ 51515756 56515050 51505052 51515453
+ 534f4e4e 4f4e4e51 50504f4e 4e4f4e4e
+ 4f4e4eb5 c8e4e900 00002e74 524e5300
+ 208040eb ff511070 a3c38fff 30ffffff
+ 80ffffea ff60bfff af9fffff ffff70ff
+ 10cfff40 ef8fff80 bfff50ff df66dbac
+ 80000002 6b494441 547801ed d8878eea
+ 400c85e1 13c2d27b 872dd909 1d26efff
+ 78b76107 4b9adb22 e2a0953f b5e8df36
+ 5e4c8579 06c61863 8c31c618 13d57e89
+ 21c5b718 414f547f b96988d8 a0566f42
+ 4deb85b4 45ec708c a1a6db23 3511eb1c
+ 63a8e9f4 4803b966 8f414f8f 45c8c5dc
+ 3a50d31f 30116b94 7a6da869 0c881cbe
+ cdb10635 b5e1cda0 2bf76648 34777548
+ 1a220e07 149b5033 1cddc8e1 e311a943
+ 4d73c444 6c71eb40 4d1cfa9b 5d8e63a8
+ 194f485b ee0dc706 d4b42744 0e3f6111
+ d474a624 46ae3f65 d033654d e41adc34
+ 77754a66 726f3876 a1a63527 72f80eb5
+ e9026aba 73227775 c631869a e59cc817
+ 23730635 fdd57c75 b346aeb5 620b94a8
+ 31beebce 56643e5f 72dcac72 f30db5b8
+ 8473bc0a 6f6ff7eb 557ec157 f2ba8147
+ 1bbf1532 fec20779 2fa48483 7c14f285
+ 0f927c16 f2f883b8 e56701a9 4389a274
+ cb960e64 b7cf63e2 a064b765 1172074a
+ 9f29a07e 903de441 c8016a92 23598ab8
+ e4b8809a cd919c44 dc73dc41 cd39307c
+ 7464d073 617de476 dc52a8e9 5f988827
+ 6e1ba8b9 7a22875f 733c41cd c913397c
+ ca517357 3d4944f4 cc41cd3e 30fc8edb
+ 1e6a5c68 f884db19 4ac4f099 88872a77
+ 550e7fd6 3f88cb3c eb83f5bd ce4db338
+ df653eb7 3f337f97 715b9470 0e5fc8a2
+ 84ad28e4 6407b183 a81f24f1 85247834
+ b7f1059c 1d4a9478 9645206e edd9c641
+ 4b1af8c7 f79feec5 48f6ac2f 46ecc5c8
+ 156a36a1 e13d8ba0 260d0cdf f70c5ac2
+ c35f2bd9 5516da9b 03d42cfe b8ab09d4
+ 1c42c367 4ff26ed3 79862779 804fa126
+ fae3aeae 2bfe6464 53e503fc bae24f46
+ d6cff100 1fbea3f6 b9ed2b7e 80df693e
+ c0b373e0 6f467b8a 0be871c9 e9a7c441
+ e89f7e59 c018638c 31c61863 4cf5be03
+ d8291f21 ceb2e953 00000000 49454e44
+ ae426082
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/read-fonts/src/tables/bitmap.rs b/read-fonts/src/tables/bitmap.rs
index fd2643708..aea1d7c2b 100644
--- a/read-fonts/src/tables/bitmap.rs
+++ b/read-fonts/src/tables/bitmap.rs
@@ -3,30 +3,13 @@
include!("../../generated/generated_bitmap.rs");
impl BitmapSize {
- pub fn subtable<'a>(
- &self,
- offset_data: FontData<'a>,
- index: u32,
- ) -> Result, ReadError> {
- let base_offset = self.index_subtable_array_offset() as usize;
- const SUBTABLE_HEADER_SIZE: usize = 8;
- let header_offset = base_offset + index as usize * SUBTABLE_HEADER_SIZE;
- let header_data = offset_data
- .slice(header_offset..)
- .ok_or(ReadError::OutOfBounds)?;
- let header = IndexSubtableArray::read(header_data)?;
- let subtable_offset = base_offset + header.additional_offset_to_index_subtable() as usize;
- let subtable_data = offset_data
- .slice(subtable_offset..)
- .ok_or(ReadError::OutOfBounds)?;
- let subtable = IndexSubtable::read(subtable_data)?;
- Ok(BitmapSizeSubtable {
- first_glyph_index: header.first_glyph_index(),
- last_glyph_index: header.last_glyph_index(),
- kind: subtable,
- })
- }
-
+ /// Returns the bitmap location information for the given glyph.
+ ///
+ /// The `offset_data` parameter is provided by the `offset_data()` method
+ /// of the parent `Eblc` or `Cblc` table.
+ ///
+ /// The resulting [`BitmapLocation`] value is used by the `data()` method
+ /// in the associated `Ebdt` or `Cbdt` table to extract the bitmap data.
pub fn location(
&self,
offset_data: FontData,
@@ -104,9 +87,33 @@ impl BitmapSize {
}
Err(ReadError::OutOfBounds)
}
+
+ fn subtable<'a>(
+ &self,
+ offset_data: FontData<'a>,
+ index: u32,
+ ) -> Result, ReadError> {
+ let base_offset = self.index_subtable_array_offset() as usize;
+ const SUBTABLE_HEADER_SIZE: usize = 8;
+ let header_offset = base_offset + index as usize * SUBTABLE_HEADER_SIZE;
+ let header_data = offset_data
+ .slice(header_offset..)
+ .ok_or(ReadError::OutOfBounds)?;
+ let header = IndexSubtableArray::read(header_data)?;
+ let subtable_offset = base_offset + header.additional_offset_to_index_subtable() as usize;
+ let subtable_data = offset_data
+ .slice(subtable_offset..)
+ .ok_or(ReadError::OutOfBounds)?;
+ let subtable = IndexSubtable::read(subtable_data)?;
+ Ok(BitmapSizeSubtable {
+ first_glyph_index: header.first_glyph_index(),
+ last_glyph_index: header.last_glyph_index(),
+ kind: subtable,
+ })
+ }
}
-pub struct BitmapSizeSubtable<'a> {
+struct BitmapSizeSubtable<'a> {
pub first_glyph_index: GlyphId,
pub last_glyph_index: GlyphId,
pub kind: IndexSubtable<'a>,
@@ -116,16 +123,22 @@ pub struct BitmapSizeSubtable<'a> {
pub struct BitmapLocation {
/// Format of EBDT/CBDT image data.
pub format: u16,
- /// Offset in EBDT/CBDT table.
+ /// Offset in bytes from the start of the EBDT/CBDT table.
pub data_offset: usize,
+ /// Size of the image data in bytes, if present in the EBLC/CBLC table.
pub data_size: Option,
+ /// Bit depth from the associated size. Required for computing image data
+ /// size when unspecified.
pub bit_depth: u8,
+ /// Full metrics, if present in the EBLC/CBLC table.
pub metrics: Option,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum BitmapDataFormat {
+ /// The full bitmap is tightly packed according to the bit depth.
BitAligned,
+ /// Each row of the data is aligned to a byte boundary.
ByteAligned,
Png,
}
diff --git a/read-fonts/src/tables/cbdt.rs b/read-fonts/src/tables/cbdt.rs
index c2c723414..37fb6b792 100644
--- a/read-fonts/src/tables/cbdt.rs
+++ b/read-fonts/src/tables/cbdt.rs
@@ -9,3 +9,116 @@ impl<'a> Cbdt<'a> {
super::bitmap::bitmap_data(self.offset_data(), location, true)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::super::bitmap::{BitmapDataFormat, SmallGlyphMetrics};
+ use crate::{types::GlyphId, FontRef, TableProvider};
+
+ #[test]
+ fn read_cblc_1_cbdt_17() {
+ let font = FontRef::new(font_test_data::EMBEDDED_BITMAPS).unwrap();
+ let cblc = font.cblc().unwrap();
+ let cbdt = font.cbdt().unwrap();
+ let size = &cblc.bitmap_sizes()[0];
+ // Metrics for size at index 0
+ assert_eq!(size.hori.ascender(), 101);
+ assert_eq!(size.hori.descender(), -27);
+ assert_eq!(size.hori.width_max(), 136);
+ assert_eq!(size.vert.ascender(), 101);
+ assert_eq!(size.vert.descender(), -27);
+ assert_eq!(size.vert.width_max(), 136);
+ assert_eq!(size.start_glyph_index(), GlyphId::new(4));
+ assert_eq!(size.end_glyph_index(), GlyphId::new(4));
+ assert_eq!(size.ppem_x(), 109);
+ assert_eq!(size.ppem_y(), 109);
+ assert_eq!(size.bit_depth(), 32);
+ let expected: &[(GlyphId, &[u8], SmallGlyphMetrics)] = &[(
+ GlyphId::new(4),
+ &raw_image_data(
+ r#"89504e47 0d0a1a0a 0000000d 49484452
+ 00000088 00000080 08030000 00e737d1
+ 0d000000 8a504c54 4547704c 5c5c5c75
+ 75756d6d 6d727272 7474746a 69696c6c
+ 6c696969 6f6f6f6b 6b6b6968 68737272
+ 56555570 70706d6d 6d6b6b6b 61606068
+ 68686666 66646363 64636354 53535b59
+ 59616060 52515154 5353605f 5f5e5e5e
+ 5d5d5d5b 5b5b5150 505a5959 51505052
+ 51515756 56515050 51505052 51515453
+ 534f4e4e 4f4e4e51 50504f4e 4e4f4e4e
+ 4f4e4eb5 c8e4e900 00002e74 524e5300
+ 208040eb ff511070 a3c38fff 30ffffff
+ 80ffffea ff60bfff af9fffff ffff70ff
+ 10cfff40 ef8fff80 bfff50ff df66dbac
+ 80000002 6b494441 547801ed d8878eea
+ 400c85e1 13c2d27b 872dd909 1d26efff
+ 78b76107 4b9adb22 e2a0953f b5e8df36
+ 5e4c8579 06c61863 8c31c618 13d57e89
+ 21c5b718 414f547f b96988d8 a0566f42
+ 4deb85b4 45ec708c a1a6db23 3511eb1c
+ 63a8e9f4 4803b966 8f414f8f 45c8c5dc
+ 3a50d31f 30116b94 7a6da869 0c881cbe
+ cdb10635 b5e1cda0 2bf76648 34777548
+ 1a220e07 149b5033 1cddc8e1 e311a943
+ 4d73c444 6c71eb40 4d1cfa9b 5d8e63a8
+ 194f485b ee0dc706 d4b42744 0e3f6111
+ d474a624 46ae3f65 d033654d e41adc34
+ 77754a66 726f3876 a1a63527 72f80eb5
+ e9026aba 73227775 c631869a e59cc817
+ 23730635 fdd57c75 b346aeb5 620b94a8
+ 31beebce 56643e5f 72dcac72 f30db5b8
+ 8473bc0a 6f6ff7eb 557ec157 f2ba8147
+ 1bbf1532 fec20779 2fa48483 7c14f285
+ 0f927c16 f2f883b8 e56701a9 4389a274
+ cb960e64 b7cf63e2 a064b765 1172074a
+ 9f29a07e 903de441 c8016a92 23598ab8
+ e4b8809a cd919c44 dc73dc41 cd39307c
+ 7464d073 617de476 dc52a8e9 5f988827
+ 6e1ba8b9 7a22875f 733c41cd c913397c
+ ca517357 3d4944f4 cc41cd3e 30fc8edb
+ 1e6a5c68 f884db19 4ac4f099 88872a77
+ 550e7fd6 3f88cb3c eb83f5bd ce4db338
+ df653eb7 3f337f97 715b9470 0e5fc8a2
+ 84ad28e4 6407b183 a81f24f1 85247834
+ b7f1059c 1d4a9478 9645206e edd9c641
+ 4b1af8c7 f79feec5 48f6ac2f 46ecc5c8
+ 156a36a1 e13d8ba0 260d0cdf f70c5ac2
+ c35f2bd9 5516da9b 03d42cfe b8ab09d4
+ 1c42c367 4ff26ed3 79862779 804fa126
+ fae3aeae 2bfe6464 53e503fc bae24f46
+ d6cff100 1fbea3f6 b9ed2b7e 80df693e
+ c0b373e0 6f467b8a 0be871c9 e9a7c441
+ e89f7e59 c018638c 31c61863 4cf5be03
+ d8291f21 ceb2e953 00000000 49454e44
+ ae426082"#,
+ ),
+ SmallGlyphMetrics {
+ height: 128,
+ width: 136,
+ bearing_x: 0.into(),
+ bearing_y: 101.into(),
+ advance: 136,
+ },
+ )];
+ for (gid, data, metrics) in expected {
+ let location = size.location(cblc.offset_data(), *gid).unwrap();
+ // all glyphs have data format == 17
+ assert_eq!(location.format, 17);
+ let bitmap_data = cbdt.data(&location).unwrap();
+ let (img_fmt, img_data) = bitmap_data.content.extract_data();
+ // all glyphs are PNG
+ assert_eq!(img_fmt, BitmapDataFormat::Png);
+ assert_eq!(img_data, *data);
+ assert_eq!(bitmap_data.extract_small_metrics(), metrics);
+ }
+ }
+
+ fn raw_image_data(raw: &str) -> Vec {
+ raw.replace([' ', '\n', '\r'], "")
+ .as_bytes()
+ .chunks(2)
+ .map(|str_hex| u8::from_str_radix(std::str::from_utf8(str_hex).unwrap(), 16).unwrap())
+ .collect()
+ }
+}
diff --git a/read-fonts/src/tables/ebdt.rs b/read-fonts/src/tables/ebdt.rs
index daa6755b4..a6b133167 100644
--- a/read-fonts/src/tables/ebdt.rs
+++ b/read-fonts/src/tables/ebdt.rs
@@ -9,3 +9,139 @@ impl<'a> Ebdt<'a> {
super::bitmap::bitmap_data(self.offset_data(), location, false)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::super::bitmap::{
+ BigGlyphMetrics, BitmapContent, BitmapData, BitmapDataFormat, BitmapMetrics,
+ SmallGlyphMetrics,
+ };
+ use crate::{types::GlyphId, FontRef, TableProvider};
+
+ impl<'a> BitmapContent<'a> {
+ pub(crate) fn extract_data(&self) -> (BitmapDataFormat, &'a [u8]) {
+ match self {
+ BitmapContent::Data(fmt, data) => (*fmt, *data),
+ _ => panic!("expected data content"),
+ }
+ }
+ }
+
+ impl<'a> BitmapData<'a> {
+ pub(crate) fn extract_small_metrics(&self) -> &SmallGlyphMetrics {
+ match &self.metrics {
+ BitmapMetrics::Small(small) => small,
+ _ => panic!("expected small glyph metrics"),
+ }
+ }
+ }
+
+ #[test]
+ fn read_eblc_3_ebdt_2() {
+ let font = FontRef::new(font_test_data::EMBEDDED_BITMAPS).unwrap();
+ let eblc = font.eblc().unwrap();
+ let ebdt = font.ebdt().unwrap();
+ let size = &eblc.bitmap_sizes()[0];
+ // Metrics for size at index 0
+ assert_eq!(size.hori.ascender(), 6);
+ assert_eq!(size.hori.descender(), 2);
+ assert_eq!(size.hori.width_max(), 4);
+ assert_eq!(size.hori.max_before_bl(), 6);
+ assert_eq!(size.hori.min_after_bl(), -2);
+ assert_eq!(size.vert.ascender(), 6);
+ assert_eq!(size.vert.descender(), 2);
+ assert_eq!(size.start_glyph_index(), GlyphId::new(1));
+ assert_eq!(size.end_glyph_index(), GlyphId::new(2));
+ assert_eq!(size.ppem_x(), 7);
+ assert_eq!(size.ppem_y(), 7);
+ assert_eq!(size.bit_depth(), 1);
+ // Bit aligned formats in this strike:
+ let expected: &[(GlyphId, &[u8], SmallGlyphMetrics)] = &[
+ (
+ GlyphId::new(1),
+ &[0xee, 0xae, 0xea],
+ SmallGlyphMetrics {
+ height: 8,
+ width: 3,
+ bearing_x: 1.into(),
+ bearing_y: 6.into(),
+ advance: 4,
+ },
+ ),
+ (
+ GlyphId::new(2),
+ &[0xf0, 0xf0, 0xf0, 0xf0],
+ SmallGlyphMetrics {
+ height: 8,
+ width: 4,
+ bearing_x: 0.into(),
+ bearing_y: 6.into(),
+ advance: 4,
+ },
+ ),
+ ];
+ for (gid, data, metrics) in expected {
+ let location = size.location(eblc.offset_data(), *gid).unwrap();
+ // all glyphs have data format == 2
+ assert_eq!(location.format, 2);
+ let bitmap_data = ebdt.data(&location).unwrap();
+ let (img_fmt, img_data) = bitmap_data.content.extract_data();
+ // all glyphs are bit aligned
+ assert_eq!(img_fmt, BitmapDataFormat::BitAligned);
+ assert_eq!(img_data, *data);
+ assert_eq!(bitmap_data.extract_small_metrics(), metrics);
+ }
+ }
+
+ #[test]
+ fn read_eblc_2_ebdt_5() {
+ let font = FontRef::new(font_test_data::EMBEDDED_BITMAPS).unwrap();
+ let eblc = font.eblc().unwrap();
+ let ebdt = font.ebdt().unwrap();
+ let size = &eblc.bitmap_sizes()[1];
+ // Metrics for size at index 1
+ assert_eq!(size.hori.ascender(), 12);
+ assert_eq!(size.hori.descender(), 5);
+ assert_eq!(size.hori.width_max(), 9);
+ assert_eq!(size.hori.max_before_bl(), 12);
+ assert_eq!(size.hori.min_after_bl(), -5);
+ assert_eq!(size.vert.ascender(), 12);
+ assert_eq!(size.vert.descender(), 5);
+ assert_eq!(size.start_glyph_index(), GlyphId::new(3));
+ assert_eq!(size.end_glyph_index(), GlyphId::new(3));
+ assert_eq!(size.ppem_x(), 15);
+ assert_eq!(size.ppem_y(), 15);
+ assert_eq!(size.bit_depth(), 1);
+ let expected: &[(GlyphId, &[u8])] = &[(
+ GlyphId::new(3),
+ &[
+ 0xaa, 0xbb, 0xcc, 0xdd, 0x00, 0x11, 0x22, 0x33, 0xff, 0xee, 0x12, 0x34, 0x42, 0x42,
+ 0x42, 0xaa, 0x88, 0x99, 0x00, 0x11,
+ ],
+ )];
+ for (gid, data) in expected {
+ let location = size.location(eblc.offset_data(), *gid).unwrap();
+ // Metrics are in EBLC, so the same for all glyphs
+ assert_eq!(
+ &location.metrics,
+ &Some(BigGlyphMetrics {
+ height: 17,
+ width: 9,
+ hori_bearing_x: 0.into(),
+ hori_bearing_y: 12.into(),
+ hori_advance: 9,
+ vert_bearing_x: (-4).into(),
+ vert_bearing_y: (-9).into(),
+ vert_advance: 0,
+ })
+ );
+ // all glyphs have data format == 5
+ assert_eq!(location.format, 5);
+ let bitmap_data = ebdt.data(&location).unwrap();
+ let (img_fmt, img_data) = bitmap_data.content.extract_data();
+ // all glyphs are bit aligned
+ assert_eq!(img_fmt, BitmapDataFormat::BitAligned);
+ assert_eq!(img_data, *data);
+ }
+ }
+}