use std::borrow::Cow;
use std::convert::{self};
use std::sync::Arc;
use bitflags::bitflags;
use pathfinder_geometry::line_segment::LineSegment2F;
use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::vector::Vector2F;
use tinyvec::tiny_vec;
use crate::big5::unicode_to_big5;
use crate::binary::read::ReadScope;
use crate::bitmap::cbdt::{CBDTTable, CBLCTable};
use crate::bitmap::sbix::Sbix as SbixTable;
use crate::bitmap::{BitDepth, BitmapGlyph};
use crate::cff::cff2::CFF2;
use crate::cff::outline::{CFF2Outlines, CFFOutlines};
use crate::cff::{CFFError, CFF};
use crate::context::Glyph;
use crate::error::{ParseError, ShapingError};
use crate::font::tables::ColrCpalTryBuilder;
use crate::glyph_info::GlyphNames;
use crate::gpos::Info;
use crate::gsub::{Features, GlyphOrigin, RawGlyph, RawGlyphFlags};
use crate::layout::morx;
use crate::layout::{new_layout_cache, GDEFTable, LayoutCache, LayoutTable, GPOS, GSUB};
use crate::macroman::char_to_macroman;
use crate::outline::{BoundingBoxSink, OutlineBuilder, OutlineSink};
use crate::scripts::preprocess_text;
use crate::tables::aat::DELETED_GLYPH;
use crate::tables::cmap::{Cmap, CmapSubtable, EncodingId, EncodingRecord, PlatformId};
use crate::tables::colr::{ColrTable, Painter};
use crate::tables::cpal::CpalTable;
use crate::tables::glyf::{BoundingBox, GlyfVisitorContext, LocaGlyf};
use crate::tables::kern::owned::KernTable;
use crate::tables::loca::{owned, LocaTable};
use crate::tables::morx::MorxTable;
use crate::tables::os2::Os2;
use crate::tables::svg::SvgTable;
use crate::tables::variable_fonts::fvar::{FvarAxisCount, FvarTable, Tuple, VariationAxisRecord};
use crate::tables::{
kern, read_and_box_optional_table, read_and_box_table, FontTableProvider, HeadTable, HheaTable,
MaxpTable,
};
use crate::unicode::{self, VariationSelector};
use crate::variations::{AxisNamesError, NamedAxis};
use crate::{cff, glyph_info, tag, variations, GlyphId, SafeFrom};
use crate::{gpos, gsub, DOTTED_CIRCLE};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Encoding {
Unicode = 1,
Symbol = 2,
AppleRoman = 3,
Big5 = 4,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum OutlineFormat {
Glyf,
Cff,
Svg,
None,
}
enum LazyLoad<T> {
NotLoaded,
Loaded(Option<T>),
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum MatchingPresentation {
Required,
NotRequired,
}
struct GlyphCache(Option<(u16, VariationSelector)>);
pub struct Font<T: FontTableProvider> {
pub font_table_provider: T,
pub head_table: HeadTable,
cmap_table: Box<[u8]>,
pub maxp_table: MaxpTable,
hmtx_table: Box<[u8]>,
pub hhea_table: HheaTable,
vmtx_table: LazyLoad<Arc<[u8]>>,
vhea_table: LazyLoad<Arc<HheaTable>>,
cmap_subtable_offset: usize,
pub cmap_subtable_encoding: Encoding,
gdef_cache: LazyLoad<Arc<GDEFTable>>,
morx_cache: LazyLoad<Arc<tables::Morx>>,
gsub_cache: LazyLoad<LayoutCache<GSUB>>,
gpos_cache: LazyLoad<LayoutCache<GPOS>>,
kern_cache: LazyLoad<Arc<KernTable>>,
os2_us_first_char_index: LazyLoad<u16>,
glyph_cache: GlyphCache,
pub glyph_table_flags: GlyphTableFlags,
loca_glyf: LocaGlyf,
cff_cache: LazyLoad<Arc<tables::CFF>>,
cff2_cache: LazyLoad<Arc<tables::CFF2>>,
embedded_image_filter: GlyphTableFlags,
embedded_images: LazyLoad<Arc<Images>>,
axis_count: u16,
}
pub enum Images {
Embedded {
cblc: tables::CBLC,
cbdt: tables::CBDT,
},
Colr(tables::ColrCpal),
Sbix(tables::Sbix),
Svg(tables::Svg),
}
#[allow(unused)]
mod tables {
use ouroboros::self_referencing;
use crate::bitmap::cbdt::{CBDTTable, CBLCTable};
use crate::bitmap::sbix::Sbix as SbixTable;
use crate::cff::cff2::CFF2 as CFF2Table;
use crate::cff::CFF as CFFTable;
use crate::tables::colr::ColrTable;
use crate::tables::cpal::CpalTable;
use crate::tables::morx::MorxTable;
use crate::tables::svg::SvgTable;
#[self_referencing(pub_extras)]
pub struct CFF {
data: Box<[u8]>,
#[borrows(data)]
#[not_covariant]
table: CFFTable<'this>,
}
#[self_referencing(pub_extras)]
pub struct CFF2 {
data: Box<[u8]>,
#[borrows(data)]
#[not_covariant]
table: CFF2Table<'this>,
}
#[self_referencing(pub_extras)]
pub struct CBLC {
data: Box<[u8]>,
#[borrows(data)]
#[not_covariant]
pub(crate) table: CBLCTable<'this>,
}
#[self_referencing(pub_extras)]
pub struct CBDT {
data: Box<[u8]>,
#[borrows(data)]
#[covariant]
pub(crate) table: CBDTTable<'this>,
}
#[self_referencing(pub_extras)]
pub struct ColrCpal {
colr_data: Box<[u8]>,
cpal_data: Box<[u8]>,
#[borrows(colr_data)]
#[not_covariant]
pub(crate) colr: ColrTable<'this>,
#[borrows(cpal_data)]
#[not_covariant]
pub(crate) cpal: CpalTable<'this>,
}
#[self_referencing(pub_extras)]
pub struct Sbix {
data: Box<[u8]>,
#[borrows(data)]
#[not_covariant]
pub(crate) table: SbixTable<'this>,
}
#[self_referencing(pub_extras)]
pub struct Svg {
data: Box<[u8]>,
#[borrows(data)]
#[not_covariant]
pub(crate) table: SvgTable<'this>,
}
#[self_referencing(pub_extras)]
pub struct Morx {
data: Box<[u8]>,
#[borrows(data)]
#[not_covariant]
pub(crate) table: MorxTable<'this>,
}
}
bitflags! {
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct GlyphTableFlags: u8 {
const GLYF = 1 << 0;
const CFF = 1 << 1;
const SVG = 1 << 2;
const SBIX = 1 << 3;
const CBDT = 1 << 4;
const EBDT = 1 << 5;
const CFF2 = 1 << 6;
const COLR = 1 << 7;
}
}
const TABLE_TAG_FLAGS: &[(u32, GlyphTableFlags)] = &[
(tag::GLYF, GlyphTableFlags::GLYF),
(tag::CFF, GlyphTableFlags::CFF),
(tag::CFF2, GlyphTableFlags::CFF2),
(tag::COLR, GlyphTableFlags::COLR),
(tag::SVG, GlyphTableFlags::SVG),
(tag::SBIX, GlyphTableFlags::SBIX),
(tag::CBDT, GlyphTableFlags::CBDT),
(tag::EBDT, GlyphTableFlags::EBDT),
];
impl<T: FontTableProvider> Font<T> {
pub fn new(provider: T) -> Result<Font<T>, ParseError> {
let cmap_table = read_and_box_table(&provider, tag::CMAP)?;
match charmap_info(&cmap_table)? {
Some((cmap_subtable_encoding, cmap_subtable_offset)) => {
let head_table =
ReadScope::new(&provider.read_table_data(tag::HEAD)?).read::<HeadTable>()?;
let maxp_table =
ReadScope::new(&provider.read_table_data(tag::MAXP)?).read::<MaxpTable>()?;
let hmtx_table = read_and_box_table(&provider, tag::HMTX)?;
let hhea_table =
ReadScope::new(&provider.read_table_data(tag::HHEA)?).read::<HheaTable>()?;
let fvar_data = provider.table_data(tag::FVAR)?;
let fvar_axis_count = fvar_data
.as_deref()
.map(|data| ReadScope::new(data).read::<FvarAxisCount>())
.transpose()?
.unwrap_or(0);
let embedded_image_filter = GlyphTableFlags::SVG
| GlyphTableFlags::SBIX
| GlyphTableFlags::CBDT
| GlyphTableFlags::COLR;
let mut glyph_table_flags = GlyphTableFlags::empty();
for &(table, flag) in TABLE_TAG_FLAGS {
if provider.has_table(table) {
glyph_table_flags |= flag
}
}
Ok(Font {
font_table_provider: provider,
head_table,
cmap_table,
maxp_table,
hmtx_table,
hhea_table,
vmtx_table: LazyLoad::NotLoaded,
vhea_table: LazyLoad::NotLoaded,
cmap_subtable_offset: usize::safe_from(cmap_subtable_offset),
cmap_subtable_encoding,
gdef_cache: LazyLoad::NotLoaded,
morx_cache: LazyLoad::NotLoaded,
gsub_cache: LazyLoad::NotLoaded,
gpos_cache: LazyLoad::NotLoaded,
kern_cache: LazyLoad::NotLoaded,
os2_us_first_char_index: LazyLoad::NotLoaded,
glyph_cache: GlyphCache::new(),
glyph_table_flags,
loca_glyf: LocaGlyf::new(),
cff_cache: LazyLoad::NotLoaded,
cff2_cache: LazyLoad::NotLoaded,
embedded_image_filter,
embedded_images: LazyLoad::NotLoaded,
axis_count: fvar_axis_count,
})
}
None => Err(ParseError::UnsuitableCmap),
}
}
pub fn num_glyphs(&self) -> u16 {
self.maxp_table.num_glyphs
}
pub fn set_embedded_image_filter(&mut self, flags: GlyphTableFlags) {
self.embedded_image_filter = flags;
}
pub fn lookup_glyph_index(
&mut self,
ch: char,
match_presentation: MatchingPresentation,
variation_selector: Option<VariationSelector>,
) -> (u16, VariationSelector) {
self.glyph_cache.get(ch).unwrap_or_else(|| {
let (glyph_index, used_variation) =
self.map_unicode_to_glyph(ch, match_presentation, variation_selector);
self.glyph_cache.put(ch, glyph_index, used_variation);
(glyph_index, used_variation)
})
}
pub fn shape(
&mut self,
mut glyphs: Vec<RawGlyph<()>>,
script_tag: u32,
opt_lang_tag: Option<u32>,
features: &Features,
tuple: Option<Tuple<'_>>,
kerning: bool,
) -> Result<Vec<Info>, (ShapingError, Vec<Info>)> {
let mut err: Option<ShapingError> = None;
let opt_gsub_cache = check_set_err(self.gsub_cache(), &mut err);
let opt_gpos_cache = check_set_err(self.gpos_cache(), &mut err);
let opt_gdef_table = check_set_err(self.gdef_table(), &mut err);
let opt_morx_table = check_set_err(self.morx_table(), &mut err);
let opt_gdef_table = opt_gdef_table.as_ref().map(Arc::as_ref);
let opt_kern_table = check_set_err(self.kern_table(), &mut err);
let opt_kern_table = opt_kern_table
.as_ref()
.map(|x| kern::KernTable::from(x.as_ref()));
let (dotted_circle_index, _) =
self.lookup_glyph_index(DOTTED_CIRCLE, MatchingPresentation::NotRequired, None);
let num_glyphs = self.num_glyphs();
let mut applied_morx = false;
if let Some(gsub_cache) = opt_gsub_cache {
let res = gsub::apply(
dotted_circle_index,
&gsub_cache,
opt_gdef_table,
script_tag,
opt_lang_tag,
features,
tuple,
num_glyphs,
&mut glyphs,
);
check_set_err(res, &mut err);
} else if let Some(morx_cache) = opt_morx_table {
morx_cache.with_table(|morx_table: &MorxTable<'_>| {
let res = morx::apply(morx_table, &mut glyphs, features, script_tag);
check_set_err(res, &mut err);
applied_morx = true;
})
}
let mut infos = Info::init_from_glyphs(opt_gdef_table, glyphs);
if let Some(gpos_cache) = opt_gpos_cache {
if applied_morx {
infos.retain(|i| i.get_glyph_index() != DELETED_GLYPH);
}
let res = gpos::apply(
&gpos_cache,
opt_gdef_table,
opt_kern_table,
kerning,
features,
tuple,
script_tag,
opt_lang_tag,
&mut infos,
);
check_set_err(res, &mut err);
} else {
let mut has_cross_stream = false;
if let Some(kern_table) = opt_kern_table {
let res = kern::apply(&kern_table, script_tag, &mut infos);
has_cross_stream = check_set_err(res, &mut err);
}
if applied_morx {
infos.retain(|i| i.get_glyph_index() != DELETED_GLYPH);
}
if !has_cross_stream {
gpos::apply_fallback_mark_positioning(&mut infos);
}
}
match err {
Some(err) => Err((err, infos)),
None => Ok(infos),
}
}
pub fn map_glyphs(
&mut self,
text: &str,
script_tag: u32,
match_presentation: MatchingPresentation,
) -> Vec<RawGlyph<()>> {
let mut chars = text.chars().collect();
preprocess_text(&mut chars, script_tag);
let mut glyphs = Vec::with_capacity(chars.len());
let mut chars_iter = chars.into_iter().peekable();
while let Some(ch) = chars_iter.next() {
match VariationSelector::try_from(ch) {
Ok(_) => {} Err(()) => {
let vs = chars_iter
.peek()
.and_then(|&next| VariationSelector::try_from(next).ok());
let (glyph_index, used_variation) =
self.lookup_glyph_index(ch, match_presentation, vs);
let glyph = RawGlyph {
unicodes: tiny_vec![[char; 1] => ch],
glyph_index,
liga_component_pos: 0,
glyph_origin: GlyphOrigin::Char(ch),
flags: RawGlyphFlags::empty(),
extra_data: (),
variation: Some(used_variation),
};
glyphs.push(glyph);
}
}
}
glyphs.shrink_to_fit();
glyphs
}
pub fn is_variable(&self) -> bool {
self.axis_count > 0
}
pub fn variation_axes(&self) -> Result<Vec<VariationAxisRecord>, ParseError> {
let table = self.font_table_provider.read_table_data(tag::FVAR)?;
let fvar = ReadScope::new(&table).read::<FvarTable<'_>>()?;
Ok(fvar.axes().collect())
}
fn map_glyph(&self, char_code: u32) -> u16 {
match ReadScope::new(self.cmap_subtable_data()).read::<CmapSubtable<'_>>() {
Ok(cmap_subtable) => match cmap_subtable.map_glyph(char_code) {
Ok(Some(glyph_index)) => glyph_index,
_ => 0,
},
Err(_err) => 0,
}
}
fn map_unicode_to_glyph(
&mut self,
ch: char,
match_presentation: MatchingPresentation,
variation_selector: Option<VariationSelector>,
) -> (u16, VariationSelector) {
let match_presentation = if matches!(
variation_selector,
Some(VariationSelector::VS15 | VariationSelector::VS16)
) {
MatchingPresentation::Required
} else {
match_presentation
};
let used_selector = Self::resolve_default_presentation(ch, variation_selector);
let glyph_index = match self.cmap_subtable_encoding {
Encoding::Unicode => {
self.lookup_glyph_index_with_variation(ch as u32, match_presentation, used_selector)
}
Encoding::Symbol => {
let char_code = self.legacy_symbol_char_code(ch);
self.lookup_glyph_index_with_variation(char_code, match_presentation, used_selector)
}
Encoding::AppleRoman => match char_to_macroman(ch) {
Some(char_code) => self.lookup_glyph_index_with_variation(
u32::from(char_code),
match_presentation,
used_selector,
),
None => {
let char_code = self.legacy_symbol_char_code(ch);
self.lookup_glyph_index_with_variation(
char_code,
match_presentation,
used_selector,
)
}
},
Encoding::Big5 => match unicode_to_big5(ch) {
Some(char_code) => self.lookup_glyph_index_with_variation(
u32::from(char_code),
match_presentation,
used_selector,
),
None => 0,
},
};
(glyph_index, used_selector)
}
fn legacy_symbol_char_code(&mut self, ch: char) -> u32 {
let char_code0 = if ch < '\u{F000}' || ch > '\u{F0FF}' {
ch as u32
} else {
ch as u32 - 0xF000
};
let provider = &self.font_table_provider;
let first_char = if let Ok(Some(us_first_char_index)) =
self.os2_us_first_char_index.get_or_load(|| {
load_os2_table(provider)?
.map(|os2| Ok(os2.us_first_char_index))
.transpose()
}) {
u32::from(us_first_char_index)
} else {
0x20
};
(char_code0 + first_char) - 0x20 }
fn lookup_glyph_index_with_variation(
&mut self,
char_code: u32,
match_presentation: MatchingPresentation,
variation_selector: VariationSelector,
) -> u16 {
if match_presentation == MatchingPresentation::Required {
if (variation_selector == VariationSelector::VS16 && self.has_embedded_images())
|| (variation_selector == VariationSelector::VS15 && self.has_glyph_outlines())
{
self.map_glyph(char_code)
} else {
0
}
} else {
self.map_glyph(char_code)
}
}
fn resolve_default_presentation(
ch: char,
variation_selector: Option<VariationSelector>,
) -> VariationSelector {
variation_selector.unwrap_or_else(|| {
if unicode::bool_prop_emoji_presentation(ch) {
VariationSelector::VS16
} else {
VariationSelector::VS15
}
})
}
pub fn glyph_names<'a>(&self, ids: &[u16]) -> Vec<Cow<'a, str>> {
let post = read_and_box_optional_table(&self.font_table_provider, tag::POST)
.ok()
.and_then(convert::identity);
let cmap = ReadScope::new(self.cmap_subtable_data())
.read::<CmapSubtable<'_>>()
.ok()
.map(|table| (self.cmap_subtable_encoding, table));
let glyph_namer = GlyphNames::new(&cmap, post);
glyph_namer.unique_glyph_names(ids)
}
pub fn axis_names<'a>(&self) -> Result<Vec<NamedAxis<'a>>, AxisNamesError> {
variations::axis_names(&self.font_table_provider)
}
pub fn lookup_glyph_image(
&mut self,
glyph_index: GlyphId,
target_ppem: u16,
max_bit_depth: BitDepth,
) -> Result<Option<BitmapGlyph>, ParseError> {
let embedded_bitmaps = match self.embedded_images()? {
Some(embedded_bitmaps) => embedded_bitmaps,
None => return Ok(None),
};
match embedded_bitmaps.as_ref() {
Images::Embedded { cblc, cbdt } => cblc.with_table(|cblc: &CBLCTable<'_>| {
let target_ppem = if target_ppem > u16::from(u8::MAX) {
u8::MAX
} else {
target_ppem as u8
};
let bitmap = match cblc.find_strike(glyph_index, target_ppem, max_bit_depth) {
Some(matching_strike) => {
let cbdt = cbdt.borrow_table();
matching_strike.bitmap(cbdt)?.map(|bitmap| {
BitmapGlyph::try_from((
&matching_strike.bitmap_size.inner,
bitmap,
glyph_index,
))
})
}
None => None,
};
bitmap.transpose()
}),
Images::Colr { .. } => Ok(None),
Images::Sbix(sbix) => self.lookup_sbix_glyph_bitmap(
sbix,
false,
false,
glyph_index,
target_ppem,
max_bit_depth,
),
Images::Svg(svg) => self.lookup_svg_glyph(svg, glyph_index),
}
}
pub fn visit_colr_glyph<P>(
&mut self,
glyph_id: u16,
palette_index: u16,
painter: &mut P,
) -> Result<(), P::Error>
where
P: Painter,
P::Error: From<ParseError> + From<cff::CFFError>,
{
let Some(embedded_images) = self.embedded_images()? else {
return Err(ParseError::MissingValue.into());
};
if self.glyph_table_flags.contains(GlyphTableFlags::CFF2) {
let cff2 = self
.cff2_table()?
.ok_or(ParseError::MissingTable(tag::CFF2))?;
cff2.with(|cff2| {
let mut cff2_outlines = CFF2Outlines { table: cff2.table };
Self::visit_colr_glyph_inner(
glyph_id,
palette_index,
painter,
&mut cff2_outlines,
&embedded_images,
)
})
} else if self.glyph_table_flags.contains(GlyphTableFlags::CFF) {
let cff = self
.cff_table()?
.ok_or(ParseError::MissingTable(tag::CFF))?;
cff.with(|cff| {
let mut cff_outlines = CFFOutlines { table: cff.table };
Self::visit_colr_glyph_inner(
glyph_id,
palette_index,
painter,
&mut cff_outlines,
&embedded_images,
)
})
} else if self.glyph_table_flags.contains(GlyphTableFlags::GLYF) {
self.maybe_load_loca_glyf()?;
let mut context = GlyfVisitorContext::new(&mut self.loca_glyf, None);
Self::visit_colr_glyph_inner(
glyph_id,
palette_index,
painter,
&mut context,
&embedded_images,
)
} else {
Err(ParseError::MissingValue.into())
}
}
fn visit_colr_glyph_inner<P, G>(
glyph_id: u16,
palette_index: u16,
painter: &mut P,
glyphs: &mut G,
embedded_images: &Images,
) -> Result<(), P::Error>
where
P: Painter,
P::Error: From<ParseError> + From<G::Error>,
G: OutlineBuilder,
{
match embedded_images {
Images::Colr(tables) => tables.with(|fields| {
let palette = fields
.cpal
.palette(palette_index)
.ok_or(ParseError::BadIndex)?;
let Some(glyph) = fields.colr.lookup(glyph_id)? else {
return Ok(());
};
glyph.visit(painter, glyphs, palette)
}),
_ => Err(ParseError::MissingTable(tag::COLR).into()),
}
}
pub fn has_colr(&mut self, glyph_id: u16) -> Result<bool, ParseError> {
let Some(embedded_images) = self.embedded_images()? else {
return Ok(false);
};
match embedded_images.as_ref() {
Images::Colr(tables) => tables.with_colr(|colr| Ok(colr.lookup(glyph_id)?.is_some())),
_ => Ok(false),
}
}
pub fn colr_clip_box(&mut self, glyph_id: u16) -> Result<Option<RectF>, ParseError> {
let Some(embedded_images) = self.embedded_images()? else {
return Err(ParseError::MissingValue);
};
match embedded_images.as_ref() {
Images::Colr(tables) => tables.with_colr(|colr| {
let glyph = colr.lookup(glyph_id)?.ok_or(ParseError::BadIndex)?;
glyph.clip_box()
}),
_ => Err(ParseError::MissingValue),
}
}
fn lookup_sbix_glyph_bitmap(
&self,
sbix: &tables::Sbix,
dupe: bool,
flip: bool,
glyph_index: GlyphId,
target_ppem: u16,
max_bit_depth: BitDepth,
) -> Result<Option<BitmapGlyph>, ParseError> {
sbix.with_table(|sbix_table: &SbixTable<'_>| {
match sbix_table.find_strike(glyph_index, target_ppem, max_bit_depth) {
Some(strike) => {
match strike.read_glyph(glyph_index)? {
Some(ref glyph) if glyph.graphic_type == tag::DUPE => {
if dupe {
Ok(None)
} else {
let dupe_glyph_index =
ReadScope::new(glyph.data).ctxt().read_u16be()?;
self.lookup_sbix_glyph_bitmap(
sbix,
true,
flip,
dupe_glyph_index,
target_ppem,
max_bit_depth,
)
}
}
Some(ref glyph) if glyph.graphic_type == tag::FLIP => {
if flip {
Ok(None)
} else {
let flip_glyph_index =
ReadScope::new(glyph.data).ctxt().read_u16be()?;
self.lookup_sbix_glyph_bitmap(
sbix,
dupe,
true,
flip_glyph_index,
target_ppem,
max_bit_depth,
)
}
}
Some(glyph) => {
Ok(Some(BitmapGlyph::from((strike, &glyph, glyph_index, flip))))
}
None => Ok(None),
}
}
None => Ok(None),
}
})
}
fn lookup_svg_glyph(
&self,
svg: &tables::Svg,
glyph_index: GlyphId,
) -> Result<Option<BitmapGlyph>, ParseError> {
svg.with_table(
|svg_table: &SvgTable<'_>| match svg_table.lookup_glyph(glyph_index)? {
Some(svg_record) => BitmapGlyph::try_from((&svg_record, glyph_index)).map(Some),
None => Ok(None),
},
)
}
fn embedded_images(&mut self) -> Result<Option<Arc<Images>>, ParseError> {
let provider = &self.font_table_provider;
let num_glyphs = usize::from(self.maxp_table.num_glyphs);
let tables_to_check = self.glyph_table_flags & self.embedded_image_filter;
self.embedded_images.get_or_load(|| {
if tables_to_check.contains(GlyphTableFlags::COLR) {
let images = load_colr_cpal(provider).map(Images::Colr)?;
Ok(Some(Arc::new(images)))
} else if tables_to_check.contains(GlyphTableFlags::SVG) {
let images = load_svg(provider).map(Images::Svg)?;
Ok(Some(Arc::new(images)))
} else if tables_to_check.contains(GlyphTableFlags::CBDT) {
let images = load_cblc_cbdt(provider, tag::CBLC, tag::CBDT)
.map(|(cblc, cbdt)| Images::Embedded { cblc, cbdt })?;
Ok(Some(Arc::new(images)))
} else if tables_to_check.contains(GlyphTableFlags::SBIX) {
let images = load_sbix(provider, num_glyphs).map(Images::Sbix)?;
Ok(Some(Arc::new(images)))
} else if tables_to_check.contains(GlyphTableFlags::EBDT) {
let images =
load_cblc_cbdt(provider, tag::EBLC, tag::EBDT).map(|(eblc, ebdt)| {
Images::Embedded {
cblc: eblc,
cbdt: ebdt,
}
})?;
Ok(Some(Arc::new(images)))
} else {
Ok(None)
}
})
}
pub fn has_embedded_images(&mut self) -> bool {
matches!(self.embedded_images(), Ok(Some(_)))
}
pub fn has_glyph_outlines(&self) -> bool {
self.glyph_table_flags
.intersects(GlyphTableFlags::GLYF | GlyphTableFlags::CFF | GlyphTableFlags::CFF2)
}
pub fn horizontal_advance(&mut self, glyph: GlyphId) -> Option<u16> {
glyph_info::advance(&self.maxp_table, &self.hhea_table, &self.hmtx_table, glyph).ok()
}
pub fn vertical_advance(&mut self, glyph: GlyphId) -> Option<u16> {
let provider = &self.font_table_provider;
let vmtx = self
.vmtx_table
.get_or_load(|| {
read_and_box_optional_table(provider, tag::VMTX).map(|ok| ok.map(Arc::from))
})
.ok()?;
let vhea = self.vhea_table().ok()?;
if let (Some(vhea), Some(vmtx_table)) = (vhea, vmtx) {
Some(glyph_info::advance(&self.maxp_table, &vhea, &vmtx_table, glyph).unwrap())
} else {
None
}
}
pub fn bounding_box(&mut self, glyph_id: u16) -> Result<Option<BoundingBox>, CFFError> {
if self.glyph_table_flags.contains(GlyphTableFlags::CFF2) {
let cff2 = self
.cff2_table()?
.ok_or(ParseError::MissingTable(tag::CFF2))?;
cff2.with(|cff2| {
let mut cff2_outlines = CFF2Outlines { table: cff2.table };
cff2_outlines.visit(glyph_id, None, &mut NullSink)
})
} else if self.glyph_table_flags.contains(GlyphTableFlags::CFF) {
let cff = self
.cff_table()?
.ok_or(ParseError::MissingTable(tag::CFF))?;
cff.with(|cff| {
let mut cff_outlines = CFFOutlines { table: cff.table };
cff_outlines.visit(glyph_id, None, &mut NullSink)
})
} else if self.glyph_table_flags.contains(GlyphTableFlags::GLYF) {
self.maybe_load_loca_glyf()?;
let mut context = GlyfVisitorContext::new(&mut self.loca_glyf, None);
let mut sink = BoundingBoxSink::new();
context.visit(glyph_id, None, &mut sink)?;
Ok(sink.to_bounding_box())
} else {
Err(ParseError::MissingValue.into())
}
}
pub fn cff_table(&mut self) -> Result<Option<Arc<tables::CFF>>, ParseError> {
let provider = &self.font_table_provider;
self.cff_cache.get_or_load(|| {
let cff_data = provider.read_table_data(tag::CFF).map(Box::from)?;
let cff = tables::CFFTryBuilder {
data: cff_data,
table_builder: |data: &Box<[u8]>| ReadScope::new(data).read::<CFF<'_>>(),
}
.try_build()?;
Ok(Some(Arc::new(cff)))
})
}
pub fn cff2_table(&mut self) -> Result<Option<Arc<tables::CFF2>>, ParseError> {
let provider = &self.font_table_provider;
self.cff2_cache.get_or_load(|| {
let cff_data = provider.read_table_data(tag::CFF2).map(Box::from)?;
let cff = tables::CFF2TryBuilder {
data: cff_data,
table_builder: |data: &Box<[u8]>| ReadScope::new(data).read::<CFF2<'_>>(),
}
.try_build()?;
Ok(Some(Arc::new(cff)))
})
}
pub fn os2_table(&self) -> Result<Option<Os2>, ParseError> {
load_os2_table(&self.font_table_provider)
}
pub fn maybe_load_loca_glyf(&mut self) -> Result<(), ParseError> {
if !self.loca_glyf.is_loaded() {
let provider = &self.font_table_provider;
let loca_data = self.font_table_provider.read_table_data(tag::LOCA)?;
let loca = ReadScope::new(&loca_data).read_dep::<LocaTable<'_>>((
self.num_glyphs(),
self.head_table.index_to_loc_format,
))?;
let loca = owned::LocaTable::from(&loca);
let glyf = read_and_box_table(provider, tag::GLYF)?;
self.loca_glyf = LocaGlyf::loaded(loca, glyf);
}
Ok(())
}
pub fn gdef_table(&mut self) -> Result<Option<Arc<GDEFTable>>, ParseError> {
let provider = &self.font_table_provider;
self.gdef_cache.get_or_load(|| {
if let Some(gdef_data) = provider.table_data(tag::GDEF)? {
let gdef = ReadScope::new(&gdef_data).read::<GDEFTable>()?;
Ok(Some(Arc::new(gdef)))
} else {
Ok(None)
}
})
}
pub fn morx_table(&mut self) -> Result<Option<Arc<tables::Morx>>, ParseError> {
let provider = &self.font_table_provider;
let num_glyphs = self.num_glyphs();
self.morx_cache.get_or_load(|| {
if let Some(morx_data) = provider.table_data(tag::MORX)? {
let morx = tables::Morx::try_new(morx_data.into(), |data| {
ReadScope::new(data).read_dep::<MorxTable<'_>>(num_glyphs)
})?;
Ok(Some(Arc::new(morx)))
} else {
Ok(None)
}
})
}
pub fn gsub_cache(&mut self) -> Result<Option<LayoutCache<GSUB>>, ParseError> {
let provider = &self.font_table_provider;
self.gsub_cache.get_or_load(|| {
if let Some(gsub_data) = provider.table_data(tag::GSUB)? {
let gsub = ReadScope::new(&gsub_data).read::<LayoutTable<GSUB>>()?;
let cache = new_layout_cache::<GSUB>(gsub);
Ok(Some(cache))
} else {
Ok(None)
}
})
}
pub fn gpos_cache(&mut self) -> Result<Option<LayoutCache<GPOS>>, ParseError> {
let provider = &self.font_table_provider;
self.gpos_cache.get_or_load(|| {
if let Some(gpos_data) = provider.table_data(tag::GPOS)? {
let gpos = ReadScope::new(&gpos_data).read::<LayoutTable<GPOS>>()?;
let cache = new_layout_cache::<GPOS>(gpos);
Ok(Some(cache))
} else {
Ok(None)
}
})
}
pub fn kern_table(&mut self) -> Result<Option<Arc<KernTable>>, ParseError> {
let provider = &self.font_table_provider;
self.kern_cache.get_or_load(|| {
if let Some(kern_data) = provider.table_data(tag::KERN)? {
match ReadScope::new(&kern_data).read::<kern::KernTable<'_>>() {
Ok(kern) => Ok(Some(Arc::new(kern.to_owned()))),
Err(ParseError::BadVersion) => Ok(None),
Err(err) => Err(err),
}
} else {
Ok(None)
}
})
}
pub fn vhea_table(&mut self) -> Result<Option<Arc<HheaTable>>, ParseError> {
let provider = &self.font_table_provider;
self.vhea_table.get_or_load(|| {
if let Some(vhea_data) = provider.table_data(tag::VHEA)? {
let vhea = ReadScope::new(&vhea_data).read::<HheaTable>()?;
Ok(Some(Arc::new(vhea)))
} else {
Ok(None)
}
})
}
pub fn cmap_subtable_data(&self) -> &[u8] {
&self.cmap_table[self.cmap_subtable_offset..]
}
}
impl<T> LazyLoad<T> {
fn get_or_load(
&mut self,
do_load: impl FnOnce() -> Result<Option<T>, ParseError>,
) -> Result<Option<T>, ParseError>
where
T: Clone,
{
match self {
LazyLoad::Loaded(Some(ref data)) => Ok(Some(data.clone())),
LazyLoad::Loaded(None) => Ok(None),
LazyLoad::NotLoaded => {
let data = do_load()?;
*self = LazyLoad::Loaded(data.clone());
Ok(data)
}
}
}
}
impl GlyphCache {
fn new() -> Self {
GlyphCache(None)
}
fn get(&self, ch: char) -> Option<(u16, VariationSelector)> {
if ch == DOTTED_CIRCLE {
self.0
} else {
None
}
}
fn put(&mut self, ch: char, glyph_index: GlyphId, variation_selector: VariationSelector) {
if ch == DOTTED_CIRCLE {
match self.0 {
Some(_) => panic!("duplicate entry"),
None => self.0 = Some((glyph_index, variation_selector)),
}
}
}
}
struct NullSink;
impl OutlineSink for NullSink {
fn move_to(&mut self, _to: Vector2F) {}
fn line_to(&mut self, _to: Vector2F) {}
fn quadratic_curve_to(&mut self, _ctrl: Vector2F, _to: Vector2F) {}
fn cubic_curve_to(&mut self, _ctrl: LineSegment2F, _to: Vector2F) {}
fn close(&mut self) {}
}
fn load_os2_table(provider: &impl FontTableProvider) -> Result<Option<Os2>, ParseError> {
provider
.table_data(tag::OS_2)?
.map(|data| ReadScope::new(&data).read_dep::<Os2>(data.len()))
.transpose()
}
fn load_cblc_cbdt(
provider: &impl FontTableProvider,
bitmap_location_table_tag: u32,
bitmap_data_table_tag: u32,
) -> Result<(tables::CBLC, tables::CBDT), ParseError> {
let cblc_data = read_and_box_table(provider, bitmap_location_table_tag)?;
let cbdt_data = read_and_box_table(provider, bitmap_data_table_tag)?;
let cblc = tables::CBLC::try_new(cblc_data, |data| {
ReadScope::new(data).read::<CBLCTable<'_>>()
})?;
let cbdt = tables::CBDT::try_new(cbdt_data, |data| {
ReadScope::new(data).read::<CBDTTable<'_>>()
})?;
Ok((cblc, cbdt))
}
fn load_colr_cpal(provider: &impl FontTableProvider) -> Result<tables::ColrCpal, ParseError> {
let colr_data = read_and_box_table(provider, tag::COLR)?;
let cpal_data = read_and_box_table(provider, tag::CPAL)?;
let colr_cpal = ColrCpalTryBuilder {
colr_data,
cpal_data,
colr_builder: |data: &Box<[u8]>| ReadScope::new(data).read::<ColrTable<'_>>(),
cpal_builder: |data: &Box<[u8]>| ReadScope::new(data).read::<CpalTable<'_>>(),
}
.try_build()?;
Ok(colr_cpal)
}
fn load_sbix(
provider: &impl FontTableProvider,
num_glyphs: usize,
) -> Result<tables::Sbix, ParseError> {
let sbix_data = read_and_box_table(provider, tag::SBIX)?;
tables::Sbix::try_new(sbix_data, |data| {
ReadScope::new(data).read_dep::<SbixTable<'_>>(num_glyphs)
})
}
fn load_svg(provider: &impl FontTableProvider) -> Result<tables::Svg, ParseError> {
let svg_data = read_and_box_table(provider, tag::SVG)?;
tables::Svg::try_new(svg_data, |data| ReadScope::new(data).read::<SvgTable<'_>>())
}
fn charmap_info(cmap_buf: &[u8]) -> Result<Option<(Encoding, u32)>, ParseError> {
let cmap = ReadScope::new(cmap_buf).read::<Cmap<'_>>()?;
Ok(find_good_cmap_subtable(&cmap)
.map(|(encoding, encoding_record)| (encoding, encoding_record.offset)))
}
pub fn read_cmap_subtable<'a>(
cmap: &Cmap<'a>,
) -> Result<Option<(Encoding, CmapSubtable<'a>)>, ParseError> {
if let Some((encoding, encoding_record)) = find_good_cmap_subtable(cmap) {
let subtable = cmap
.scope
.offset(usize::try_from(encoding_record.offset)?)
.read::<CmapSubtable<'_>>()?;
Ok(Some((encoding, subtable)))
} else {
Ok(None)
}
}
pub fn find_good_cmap_subtable(cmap: &Cmap<'_>) -> Option<(Encoding, EncodingRecord)> {
if let Some(encoding_record) =
cmap.find_subtable(PlatformId::WINDOWS, EncodingId::WINDOWS_UNICODE_UCS4)
{
return Some((Encoding::Unicode, encoding_record));
}
if let Some(encoding_record) =
cmap.find_subtable(PlatformId::WINDOWS, EncodingId::WINDOWS_UNICODE_BMP_UCS2)
{
return Some((Encoding::Unicode, encoding_record));
}
if let Some(encoding_record) =
cmap.find_subtable(PlatformId::UNICODE, EncodingId::MACINTOSH_UNICODE_UCS4)
{
return Some((Encoding::Unicode, encoding_record));
}
if let Some(encoding_record) = cmap.find_subtable_for_platform(PlatformId::UNICODE) {
return Some((Encoding::Unicode, encoding_record));
}
if let Some(encoding_record) =
cmap.find_subtable(PlatformId::WINDOWS, EncodingId::WINDOWS_SYMBOL)
{
return Some((Encoding::Symbol, encoding_record));
}
if let Some(encoding_record) =
cmap.find_subtable(PlatformId::MACINTOSH, EncodingId::MACINTOSH_APPLE_ROMAN)
{
return Some((Encoding::AppleRoman, encoding_record));
}
if let Some(encoding_record) = cmap.find_subtable(PlatformId::WINDOWS, EncodingId::WINDOWS_BIG5)
{
return Some((Encoding::Big5, encoding_record));
}
None
}
fn check_set_err<T, E>(res: Result<T, E>, err: &mut Option<ShapingError>) -> T
where
E: Into<ShapingError>,
T: Default,
{
match res {
Ok(table) => table,
Err(e) => {
if err.is_none() {
*err = Some(e.into())
}
T::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bitmap::{Bitmap, EncapsulatedBitmap};
use crate::cff::CFFError;
use crate::font_data::{DynamicFontTableProvider, FontData};
use crate::tables::OpenTypeFont;
use crate::tests::read_fixture;
use std::error::Error;
#[test]
fn test_glyph_names() {
let font_buffer = read_fixture("tests/fonts/opentype/TwitterColorEmoji-SVGinOT.ttf");
let opentype_file = ReadScope::new(&font_buffer)
.read::<OpenTypeFont<'_>>()
.unwrap();
let font_table_provider = opentype_file
.table_provider(0)
.expect("error reading font file");
let font = Font::new(Box::new(font_table_provider)).expect("error reading font data");
let names = font.glyph_names(&[0, 5, 45, 71, 1311, 3086]);
assert_eq!(
names,
&[
Cow::from(".notdef"),
Cow::from("copyright"),
Cow::from("uni25B6"),
Cow::from("smileface"),
Cow::from("u1FA95"),
Cow::from("1f468-200d-1f33e")
]
);
}
#[test]
fn test_glyph_names_post_v3() {
let font_buffer = read_fixture("tests/fonts/opentype/Klei.otf");
let opentype_file = ReadScope::new(&font_buffer)
.read::<OpenTypeFont<'_>>()
.unwrap();
let font_table_provider = opentype_file
.table_provider(0)
.expect("error reading font file");
let font = Font::new(Box::new(font_table_provider)).expect("error reading font data");
let names = font.glyph_names(&[0, 5, 45, 100, 763, 1000 ]);
assert_eq!(
names,
&[
Cow::from(".notdef"),
Cow::from("dollar"),
Cow::from("L"),
Cow::from("yen"),
Cow::from("uniFB00"),
Cow::from("g1000") ]
);
}
#[test]
fn test_lookup_sbix() {
let font_buffer = read_fixture("tests/fonts/sbix/sbix-dupe.ttf");
let opentype_file = ReadScope::new(&font_buffer)
.read::<OpenTypeFont<'_>>()
.unwrap();
let font_table_provider = opentype_file
.table_provider(0)
.expect("error reading font file");
let mut font = Font::new(Box::new(font_table_provider)).expect("error reading font data");
match font.lookup_glyph_image(1, 100, BitDepth::ThirtyTwo) {
Ok(Some(BitmapGlyph {
bitmap: Bitmap::Encapsulated(EncapsulatedBitmap { data, .. }),
..
})) => {
assert_eq!(data.len(), 224);
}
_ => panic!("Expected encapsulated bitmap, got something else."),
}
match font.lookup_glyph_image(2, 100, BitDepth::ThirtyTwo) {
Ok(Some(BitmapGlyph {
bitmap: Bitmap::Encapsulated(EncapsulatedBitmap { data, .. }),
..
})) => {
assert_eq!(data.len(), 224);
}
_ => panic!("Expected encapsulated bitmap, got something else."),
}
match font.lookup_glyph_image(3, 100, BitDepth::ThirtyTwo) {
Ok(None) => {}
_ => panic!("Expected Ok(None) got something else"),
}
}
#[test]
fn table_provider_independent_of_font() {
fn load_font<'a>(
scope: ReadScope<'a>,
) -> Result<Font<DynamicFontTableProvider<'a>>, Box<dyn Error>> {
let font_file = scope.read::<FontData<'_>>()?;
let provider = font_file.table_provider(0)?;
Font::new(provider).map_err(Box::from)
}
let buffer =
std::fs::read("tests/fonts/opentype/Klei.otf").expect("unable to read Klei.otf");
let scope = ReadScope::new(&buffer);
assert!(load_font(scope).is_ok());
}
#[test]
fn bounding_box_cff() -> Result<(), CFFError> {
let buffer = std::fs::read("tests/fonts/opentype/Klei.otf").unwrap();
let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>()?;
let font_table_provider = opentype_file
.table_provider(0)
.expect("error reading font file");
let mut font = Font::new(Box::new(font_table_provider))?;
let bbox = font.bounding_box(1)?; assert_eq!(bbox, None);
let bbox = font.bounding_box(80)?; assert_eq!(
bbox,
Some(BoundingBox {
x_min: 40,
x_max: 488,
y_min: -11,
y_max: 511
})
);
let bbox = font.bounding_box(109)?; assert_eq!(
bbox,
Some(BoundingBox {
x_min: 67,
x_max: 341,
y_min: 506,
y_max: 550
})
);
Ok(())
}
}