Skip to content

Commit

Permalink
Support bloc+bdat and EBDT+EBLC tables.
Browse files Browse the repository at this point in the history
Extending `glyph_raster_image` to support bitmaps, handling for EBDT and EBLC tables, fixing some issues with CBLC lookup.
  • Loading branch information
dzamkov committed Apr 15, 2023
1 parent eb68238 commit a3eb088
Show file tree
Hide file tree
Showing 8 changed files with 460 additions and 49 deletions.
10 changes: 5 additions & 5 deletions README.md
Expand Up @@ -60,17 +60,17 @@ There are roughly three types of TrueType tables:
| Rendering | -<sup>1</sup> || ~ (very primitive) |
| `ankr` table || | |
| `avar` table ||| |
| `bdat` table | || |
| `bloc` table | || |
| `CBDT` table | || |
| `bdat` table | ~ (no 4) || |
| `bloc` table | || |
| `CBDT` table | ~ (no 8, 9) || |
| `CBLC` table ||| |
| `COLR` table | || |
| `CPAL` table | || |
| `CFF `&nbsp;table ||| ~ (no `seac` support) |
| `CFF2` table ||| |
| `cmap` table | ~ (no 8) || ~ (no 2,8,10,14; Unicode-only) |
| `EBDT` table | || |
| `EBLC` table | || |
| `EBDT` table | ~ (no 8, 9) || |
| `EBLC` table | || |
| `feat` table || | |
| `fvar` table ||| |
| `gasp` table | || |
Expand Down
80 changes: 80 additions & 0 deletions c-api/lib.rs
Expand Up @@ -61,6 +61,62 @@ pub struct ttfp_name_record {
#[repr(C)]
pub enum ttfp_raster_image_format {
PNG = 0,

/// @brief A monochrome bitmap.
///
/// The most significant bit of the first byte corresponds to the top-left pixel, proceeding
/// through succeeding bits moving left to right. The data for each row is padded to a byte
/// boundary, so the next row begins with the most significant bit of a new byte. 1 corresponds
/// to black, and 0 to white.
BITMAP_MONO = 1,

/// @brief A packed monochrome bitmap.
///
/// The most significant bit of the first byte corresponds to the top-left pixel, proceeding
/// through succeeding bits moving left to right. Data is tightly packed with no padding. 1
/// corresponds to black, and 0 to white.
BITMAP_MONO_PACKED = 2,

/// @brief A grayscale bitmap with 2 bits per pixel.
///
/// The most significant bits of the first byte corresponds to the top-left pixel, proceeding
/// through succeeding bits moving left to right. The data for each row is padded to a byte
/// boundary, so the next row begins with the most significant bit of a new byte.
BITMAP_GRAY_2 = 3,

/// @brief A packed grayscale bitmap with 2 bits per pixel.
///
/// The most significant bits of the first byte corresponds to the top-left pixel, proceeding
/// through succeeding bits moving left to right. Data is tightly packed with no padding.
BITMAP_GRAY_2_PACKED = 4,

/// @brief A grayscale bitmap with 4 bits per pixel.
///
/// The most significant bits of the first byte corresponds to the top-left pixel, proceeding
/// through succeeding bits moving left to right. The data for each row is padded to a byte
/// boundary, so the next row begins with the most significant bit of a new byte.
BITMAP_GRAY_4 = 5,

/// @brief A packed grayscale bitmap with 4 bits per pixel.
///
/// The most significant bits of the first byte corresponds to the top-left pixel, proceeding
/// through succeeding bits moving left to right. Data is tightly packed with no padding.
BITMAP_GRAY_4_PACKED = 6,

/// @brief A grayscale bitmap with 8 bits per pixel.
///
/// The first byte corresponds to the top-left pixel, proceeding through succeeding bytes
/// moving left to right.
BITMAP_GRAY_8 = 7,

/// @brief A color bitmap with 32 bits per pixel.
///
/// The first group of four bytes corresponds to the top-left pixel, proceeding through
/// succeeding pixels moving left to right. Each byte corresponds to a color channel and the
/// channels within a pixel are in blue, green, red, alpha order. Color values are
/// pre-multiplied by the alpha. For example, the color "full-green with half translucency"
/// is encoded as `\x00\x80\x00\x80`, and not `\x00\xFF\x00\x80`.
BITMAP_PREMUL_BGRA_32 = 8,
}

/// @brief A glyph image.
Expand Down Expand Up @@ -752,6 +808,30 @@ pub extern "C" fn ttfp_get_glyph_raster_image(
pixels_per_em: image.pixels_per_em,
format: match image.format {
ttf_parser::RasterImageFormat::PNG => ttfp_raster_image_format::PNG,
ttf_parser::RasterImageFormat::BitmapMono => {
ttfp_raster_image_format::BITMAP_MONO
}
ttf_parser::RasterImageFormat::BitmapMonoPacked => {
ttfp_raster_image_format::BITMAP_MONO_PACKED
}
ttf_parser::RasterImageFormat::BitmapGray2 => {
ttfp_raster_image_format::BITMAP_GRAY_2
}
ttf_parser::RasterImageFormat::BitmapGray2Packed => {
ttfp_raster_image_format::BITMAP_GRAY_2_PACKED
}
ttf_parser::RasterImageFormat::BitmapGray4 => {
ttfp_raster_image_format::BITMAP_GRAY_4
}
ttf_parser::RasterImageFormat::BitmapGray4Packed => {
ttfp_raster_image_format::BITMAP_GRAY_4_PACKED
}
ttf_parser::RasterImageFormat::BitmapGray8 => {
ttfp_raster_image_format::BITMAP_GRAY_8
}
ttf_parser::RasterImageFormat::BitmapPremulBgra32 => {
ttfp_raster_image_format::BITMAP_PREMUL_BGRA_32
}
},
data: image.data.as_ptr() as _,
len: image.data.len() as u32,
Expand Down
72 changes: 72 additions & 0 deletions c-api/ttfparser.h
Expand Up @@ -20,6 +20,78 @@
*/
typedef enum {
TTFP_RASTER_IMAGE_FORMAT_PNG = 0,

/**
* @brief A monochrome bitmap.
*
* The most significant bit of the first byte corresponds to the top-left pixel, proceeding
* through succeeding bits moving left to right. The data for each row is padded to a byte
* boundary, so the next row begins with the most significant bit of a new byte. 1 corresponds
* to black, and 0 to white.
*/
TTFP_RASTER_IMAGE_FORMAT_BITMAP_MONO = 1,

/**
* @brief A packed monochrome bitmap.
*
* The most significant bit of the first byte corresponds to the top-left pixel, proceeding
* through succeeding bits moving left to right. Data is tightly packed with no padding. 1
* corresponds to black, and 0 to white.
*/
TTFP_RASTER_IMAGE_FORMAT_BITMAP_MONO_PACKED = 2,

/**
* @brief A grayscale bitmap with 2 bits per pixel.
*
* The most significant bits of the first byte corresponds to the top-left pixel, proceeding
* through succeeding bits moving left to right. The data for each row is padded to a byte
* boundary, so the next row begins with the most significant bit of a new byte.
*/
TTFP_RASTER_IMAGE_FORMAT_BITMAP_GRAY_2 = 3,

/**
* @brief A packed grayscale bitmap with 2 bits per pixel.
*
* The most significant bits of the first byte corresponds to the top-left pixel, proceeding
* through succeeding bits moving left to right. Data is tightly packed with no padding.
*/
TTFP_RASTER_IMAGE_FORMAT_BITMAP_GRAY_2_PACKED = 4,

/**
* @brief A grayscale bitmap with 4 bits per pixel.
*
* The most significant bits of the first byte corresponds to the top-left pixel, proceeding
* through succeeding bits moving left to right. The data for each row is padded to a byte
* boundary, so the next row begins with the most significant bit of a new byte.
*/
TTFP_RASTER_IMAGE_FORMAT_BITMAP_GRAY_4 = 5,

/**
* @brief A packed grayscale bitmap with 4 bits per pixel.
*
* The most significant bits of the first byte corresponds to the top-left pixel, proceeding
* through succeeding bits moving left to right. Data is tightly packed with no padding.
*/
TTFP_RASTER_IMAGE_FORMAT_BITMAP_GRAY_4_PACKED = 6,

/**
* @brief A grayscale bitmap with 8 bits per pixel.
*
* The first byte corresponds to the top-left pixel, proceeding through succeeding bytes
* moving left to right.
*/
TTFP_RASTER_IMAGE_FORMAT_BITMAP_GRAY_8 = 7,

/**
* @brief A color bitmap with 32 bits per pixel.
*
* The first group of four bytes corresponds to the top-left pixel, proceeding through
* succeeding pixels moving left to right. Each byte corresponds to a color channel and the
* channels within a pixel are in blue, green, red, alpha order. Color values are
* pre-multiplied by the alpha. For example, the color "full-green with half translucency"
* is encoded as `\x00\x80\x00\x80`, and not `\x00\xFF\x00\x80`.
*/
TTFP_RASTER_IMAGE_FORMAT_BITMAP_PREMUL_BGRA_32 = 8,
} ttfp_raster_image_format;

/**
Expand Down
95 changes: 92 additions & 3 deletions src/lib.rs
Expand Up @@ -431,6 +431,62 @@ impl OutlineBuilder for DummyOutline {
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RasterImageFormat {
PNG,

/// A monochrome bitmap.
///
/// The most significant bit of the first byte corresponds to the top-left pixel, proceeding
/// through succeeding bits moving left to right. The data for each row is padded to a byte
/// boundary, so the next row begins with the most significant bit of a new byte. 1 corresponds
/// to black, and 0 to white.
BitmapMono,

/// A packed monochrome bitmap.
///
/// The most significant bit of the first byte corresponds to the top-left pixel, proceeding
/// through succeeding bits moving left to right. Data is tightly packed with no padding. 1
/// corresponds to black, and 0 to white.
BitmapMonoPacked,

/// A grayscale bitmap with 2 bits per pixel.
///
/// The most significant bits of the first byte corresponds to the top-left pixel, proceeding
/// through succeeding bits moving left to right. The data for each row is padded to a byte
/// boundary, so the next row begins with the most significant bit of a new byte.
BitmapGray2,

/// A packed grayscale bitmap with 2 bits per pixel.
///
/// The most significant bits of the first byte corresponds to the top-left pixel, proceeding
/// through succeeding bits moving left to right. Data is tightly packed with no padding.
BitmapGray2Packed,

/// A grayscale bitmap with 4 bits per pixel.
///
/// The most significant bits of the first byte corresponds to the top-left pixel, proceeding
/// through succeeding bits moving left to right. The data for each row is padded to a byte
/// boundary, so the next row begins with the most significant bit of a new byte.
BitmapGray4,

/// A packed grayscale bitmap with 4 bits per pixel.
///
/// The most significant bits of the first byte corresponds to the top-left pixel, proceeding
/// through succeeding bits moving left to right. Data is tightly packed with no padding.
BitmapGray4Packed,

/// A grayscale bitmap with 8 bits per pixel.
///
/// The first byte corresponds to the top-left pixel, proceeding through succeeding bytes
/// moving left to right.
BitmapGray8,

/// A color bitmap with 32 bits per pixel.
///
/// The first group of four bytes corresponds to the top-left pixel, proceeding through
/// succeeding pixels moving left to right. Each byte corresponds to a color channel and the
/// channels within a pixel are in blue, green, red, alpha order. Color values are
/// pre-multiplied by the alpha. For example, the color "full-green with half translucency"
/// is encoded as `\x00\x80\x00\x80`, and not `\x00\xFF\x00\x80`.
BitmapPremulBgra32
}

/// A glyph's raster image.
Expand Down Expand Up @@ -677,10 +733,14 @@ pub struct RawFaceTables<'a> {
pub hhea: &'a [u8],
pub maxp: &'a [u8],

pub bdat: Option<&'a [u8]>,
pub bloc: Option<&'a [u8]>,
pub cbdt: Option<&'a [u8]>,
pub cblc: Option<&'a [u8]>,
pub cff: Option<&'a [u8]>,
pub cmap: Option<&'a [u8]>,
pub ebdt: Option<&'a [u8]>,
pub eblc: Option<&'a [u8]>,
pub glyf: Option<&'a [u8]>,
pub hmtx: Option<&'a [u8]>,
pub kern: Option<&'a [u8]>,
Expand Down Expand Up @@ -746,9 +806,11 @@ pub struct FaceTables<'a> {
pub hhea: hhea::Table,
pub maxp: maxp::Table,

pub bdat: Option<cbdt::Table<'a>>,
pub cbdt: Option<cbdt::Table<'a>>,
pub cff: Option<cff::Table<'a>>,
pub cmap: Option<cmap::Table<'a>>,
pub ebdt: Option<cbdt::Table<'a>>,
pub glyf: Option<glyf::Table<'a>>,
pub hmtx: Option<hmtx::Table<'a>>,
pub kern: Option<kern::Table<'a>>,
Expand Down Expand Up @@ -884,11 +946,15 @@ impl<'a> Face<'a> {

let table_data = raw_face.data.get(start..end);
match &record.tag.to_bytes() {
b"bdat" => tables.bdat = table_data,
b"bloc" => tables.bloc = table_data,
b"CBDT" => tables.cbdt = table_data,
b"CBLC" => tables.cblc = table_data,
b"CFF " => tables.cff = table_data,
#[cfg(feature = "variable-fonts")]
b"CFF2" => tables.cff2 = table_data,
b"EBDT" => tables.ebdt = table_data,
b"EBLC" => tables.eblc = table_data,
#[cfg(feature = "opentype-layout")]
b"GDEF" => tables.gdef = table_data,
#[cfg(feature = "opentype-layout")]
Expand Down Expand Up @@ -994,6 +1060,14 @@ impl<'a> Face<'a> {
None
};

let bdat = if let Some(bloc) = raw_tables.bloc.and_then(cblc::Table::parse) {
raw_tables
.bdat
.and_then(|data| cbdt::Table::parse(bloc, data))
} else {
None
};

let cbdt = if let Some(cblc) = raw_tables.cblc.and_then(cblc::Table::parse) {
raw_tables
.cbdt
Expand All @@ -1002,14 +1076,24 @@ impl<'a> Face<'a> {
None
};

let ebdt = if let Some(eblc) = raw_tables.eblc.and_then(cblc::Table::parse) {
raw_tables
.ebdt
.and_then(|data| cbdt::Table::parse(eblc, data))
} else {
None
};

Ok(FaceTables {
head,
hhea,
maxp,

bdat,
cbdt,
cff: raw_tables.cff.and_then(cff::Table::parse),
cmap: raw_tables.cmap.and_then(cmap::Table::parse),
ebdt,
glyf,
hmtx,
kern: raw_tables.kern.and_then(kern::Table::parse),
Expand Down Expand Up @@ -1873,13 +1957,11 @@ impl<'a> Face<'a> {
/// Note that this method will return an encoded image. It should be decoded
/// by the caller. We don't validate or preprocess it in any way.
///
/// Currently, only PNG images are supported.
///
/// Also, a font can contain both: images and outlines. So when this method returns `None`
/// you should also try `outline_glyph()` afterwards.
///
/// There are multiple ways an image can be stored in a TrueType font
/// and this method supports only `sbix`, `CBLC`+`CBDT`.
/// and this method supports only `sbix`, `CBLC`+`CBDT`, `EBLC + EBDT`.
/// Font's tables be accesses in this specific order.
#[inline]
pub fn glyph_raster_image(
Expand All @@ -1892,11 +1974,18 @@ impl<'a> Face<'a> {
return strike.get(glyph_id);
}
}
if let Some(bdat) = self.tables.bdat {
return bdat.get(glyph_id, pixels_per_em);
}

if let Some(cbdt) = self.tables.cbdt {
return cbdt.get(glyph_id, pixels_per_em);
}

if let Some(ebdt) = self.tables.ebdt {
return ebdt.get(glyph_id, pixels_per_em);
}

None
}

Expand Down

0 comments on commit a3eb088

Please sign in to comment.