Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for bitmap fonts, EBDT + EBLC tables #121

Merged
merged 5 commits into from
Apr 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
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) | ✓ | |
RazrFalcon marked this conversation as resolved.
Show resolved Hide resolved
| `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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading