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); + } + } +}