use crate::{
from_obj::{FromObjRef, FromTableRef, ToOwnedTable},
FontWrite,
};
use read_fonts::{tables::glyf::CompositeGlyphFlags, types::GlyphId, FontRead};
use super::Bbox;
pub use read_fonts::tables::glyf::{Anchor, Transform};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CompositeGlyph {
pub bbox: Bbox,
components: Vec<Component>,
_instructions: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Component {
pub glyph: GlyphId,
pub anchor: Anchor,
pub flags: ComponentFlags,
pub transform: Transform,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct ComponentFlags {
pub round_xy_to_grid: bool,
pub use_my_metrics: bool,
pub scaled_component_offset: bool,
pub unscaled_component_offset: bool,
pub overlap_compound: bool,
}
impl<'a> FromObjRef<read_fonts::tables::glyf::CompositeGlyph<'a>> for CompositeGlyph {
fn from_obj_ref(
from: &read_fonts::tables::glyf::CompositeGlyph,
_data: read_fonts::FontData,
) -> Self {
let bbox = Bbox {
x_min: from.x_min(),
y_min: from.y_min(),
x_max: from.x_max(),
y_max: from.y_max(),
};
let components = from
.components()
.map(|c| Component {
glyph: c.glyph,
anchor: c.anchor,
flags: c.flags.into(),
transform: c.transform,
})
.collect();
Self {
bbox,
components,
_instructions: from
.instructions()
.map(|v| v.to_owned())
.unwrap_or_default(),
}
}
}
impl<'a> FromTableRef<read_fonts::tables::glyf::CompositeGlyph<'a>> for CompositeGlyph {}
impl<'a> FontRead<'a> for CompositeGlyph {
fn read(data: read_fonts::FontData<'a>) -> Result<Self, read_fonts::ReadError> {
read_fonts::tables::glyf::CompositeGlyph::read(data).map(|g| g.to_owned_table())
}
}
impl Component {
pub fn new(
glyph: GlyphId,
anchor: Anchor,
transform: Transform,
flags: impl Into<ComponentFlags>,
) -> Self {
Component {
glyph,
anchor,
flags: flags.into(),
transform,
}
}
fn compute_flag(&self) -> CompositeGlyphFlags {
self.anchor.compute_flags() | self.transform.compute_flags() | self.flags.into()
}
fn write_into(&self, writer: &mut crate::TableWriter, extra_flags: CompositeGlyphFlags) {
let flags = self.compute_flag() | extra_flags;
flags.bits().write_into(writer);
self.glyph.write_into(writer);
self.anchor.write_into(writer);
self.transform.write_into(writer);
}
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct NoComponents;
impl std::fmt::Display for NoComponents {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "A composite glyph must contain at least one component")
}
}
impl std::error::Error for NoComponents {}
impl CompositeGlyph {
pub fn new(component: Component, bbox: impl Into<Bbox>) -> Self {
Self {
bbox: bbox.into(),
components: vec![component],
_instructions: Default::default(),
}
}
pub fn add_component(&mut self, component: Component, bbox: impl Into<Bbox>) {
self.components.push(component);
self.bbox = self.bbox.union(bbox.into());
}
pub fn try_from_iter(
source: impl IntoIterator<Item = (Component, Bbox)>,
) -> Result<Self, NoComponents> {
let mut components = Vec::new();
let mut union_box: Option<Bbox> = None;
for (component, bbox) in source {
components.push(component);
union_box.get_or_insert(bbox).union(bbox);
}
if components.is_empty() {
Err(NoComponents)
} else {
Ok(CompositeGlyph {
bbox: union_box.unwrap(),
components,
_instructions: Default::default(),
})
}
}
pub fn components(&self) -> &[Component] {
&self.components
}
}
impl FontWrite for CompositeGlyph {
fn write_into(&self, writer: &mut crate::TableWriter) {
const N_CONTOURS: i16 = -1;
N_CONTOURS.write_into(writer);
self.bbox.write_into(writer);
let (last, rest) = self
.components
.split_last()
.expect("empty composites checked in validation");
for comp in rest {
comp.write_into(writer, CompositeGlyphFlags::MORE_COMPONENTS);
}
let last_flags = if self._instructions.is_empty() {
CompositeGlyphFlags::empty()
} else {
CompositeGlyphFlags::WE_HAVE_INSTRUCTIONS
};
last.write_into(writer, last_flags);
if !self._instructions.is_empty() {
(self._instructions.len() as u16).write_into(writer);
self._instructions.write_into(writer);
}
writer.pad_to_2byte_aligned();
}
}
impl crate::validate::Validate for CompositeGlyph {
fn validate_impl(&self, ctx: &mut crate::codegen_prelude::ValidationCtx) {
if self.components.is_empty() {
ctx.report("composite glyph must have components");
}
if self._instructions.len() > u16::MAX as usize {
ctx.report("instructions len overflows");
}
}
}
impl FontWrite for Anchor {
fn write_into(&self, writer: &mut crate::TableWriter) {
let two_bytes = self
.compute_flags()
.contains(CompositeGlyphFlags::ARG_1_AND_2_ARE_WORDS);
match self {
Anchor::Offset { x, y } if !two_bytes => [*x as i8, *y as i8].write_into(writer),
Anchor::Offset { x, y } => [*x, *y].write_into(writer),
Anchor::Point { base, component } if !two_bytes => {
[*base as u8, *component as u8].write_into(writer)
}
Anchor::Point { base, component } => [*base, *component].write_into(writer),
}
}
}
impl FontWrite for Transform {
fn write_into(&self, writer: &mut crate::TableWriter) {
let flags = self.compute_flags();
if flags.contains(CompositeGlyphFlags::WE_HAVE_A_TWO_BY_TWO) {
[self.xx, self.yx, self.xy, self.yy].write_into(writer);
} else if flags.contains(CompositeGlyphFlags::WE_HAVE_AN_X_AND_Y_SCALE) {
[self.xx, self.yy].write_into(writer);
} else if flags.contains(CompositeGlyphFlags::WE_HAVE_A_SCALE) {
self.xx.write_into(writer)
}
}
}
impl From<CompositeGlyphFlags> for ComponentFlags {
fn from(src: CompositeGlyphFlags) -> ComponentFlags {
ComponentFlags {
round_xy_to_grid: src.contains(CompositeGlyphFlags::ROUND_XY_TO_GRID),
use_my_metrics: src.contains(CompositeGlyphFlags::USE_MY_METRICS),
scaled_component_offset: src.contains(CompositeGlyphFlags::SCALED_COMPONENT_OFFSET),
unscaled_component_offset: src.contains(CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET),
overlap_compound: src.contains(CompositeGlyphFlags::OVERLAP_COMPOUND),
}
}
}
impl From<ComponentFlags> for CompositeGlyphFlags {
fn from(value: ComponentFlags) -> Self {
value
.round_xy_to_grid
.then_some(CompositeGlyphFlags::ROUND_XY_TO_GRID)
.unwrap_or_default()
| value
.use_my_metrics
.then_some(CompositeGlyphFlags::USE_MY_METRICS)
.unwrap_or_default()
| value
.scaled_component_offset
.then_some(CompositeGlyphFlags::SCALED_COMPONENT_OFFSET)
.unwrap_or_default()
| value
.unscaled_component_offset
.then_some(CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET)
.unwrap_or_default()
| value
.overlap_compound
.then_some(CompositeGlyphFlags::OVERLAP_COMPOUND)
.unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use read_fonts::{
tables::glyf as read_glyf, types::GlyphId, FontData, FontRead, FontRef, TableProvider,
};
use super::*;
#[test]
fn roundtrip_composite() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let loca = font.loca(None).unwrap();
let glyf = font.glyf().unwrap();
let read_glyf::Glyph::Composite(orig) =
loca.get_glyf(GlyphId::new(2), &glyf).unwrap().unwrap()
else {
panic!("not a composite glyph")
};
let bbox = Bbox {
x_min: orig.x_min(),
y_min: orig.y_min(),
x_max: orig.x_max(),
y_max: orig.y_max(),
};
let mut iter = orig
.components()
.map(|comp| Component::new(comp.glyph, comp.anchor, comp.transform, comp.flags));
let mut composite = CompositeGlyph::new(iter.next().unwrap(), bbox);
composite.add_component(iter.next().unwrap(), bbox);
composite._instructions = orig.instructions().unwrap_or_default().to_vec();
assert!(iter.next().is_none());
let bytes = crate::dump_table(&composite).unwrap();
let ours = read_fonts::tables::glyf::CompositeGlyph::read(FontData::new(&bytes)).unwrap();
let our_comps = ours.components().collect::<Vec<_>>();
let orig_comps = orig.components().collect::<Vec<_>>();
assert_eq!(our_comps.len(), orig_comps.len());
assert_eq!(our_comps.len(), 2);
assert_eq!(&our_comps[0], &orig_comps[0]);
assert_eq!(&our_comps[1], &orig_comps[1]);
assert_eq!(ours.instructions(), orig.instructions());
assert_eq!(orig.offset_data().len(), bytes.len());
assert_eq!(orig.offset_data().as_ref(), bytes);
}
}