Skip to content

Commit

Permalink
Rough in CPAL
Browse files Browse the repository at this point in the history
  • Loading branch information
rsheeter committed Mar 4, 2025
1 parent 71d753e commit d83dda9
Show file tree
Hide file tree
Showing 18 changed files with 337 additions and 17 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ rayon = "1.6"
icu_properties = "2.0.0-beta1"

# fontations etc
write-fonts = { version = "0.36.2", features = ["serde", "read"] }
skrifa = "0.28.0"
write-fonts = { path = "../fontations/write-fonts", features = ["serde", "read"] }
skrifa = { path = "../fontations/skrifa" }

norad = { version = "0.15.0", default-features = false }

# dev dependencies
Expand Down
87 changes: 87 additions & 0 deletions fontbe/src/cpal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! Generates a [cpal](https://learn.microsoft.com/en-us/typography/opentype/spec/cpal) table.
use crate::{
error::Error,
orchestration::{AnyWorkId, BeWork, Context, WorkId},
};
use fontdrasil::orchestration::{Access, Work};
use fontir::orchestration::WorkId as FeWorkId;
use log::debug;
use write_fonts::{tables::cpal::ColorRecord, tables::cpal::Cpal, NullableOffsetMarker};

#[derive(Debug)]
struct CpalWork {}

pub fn create_cpal_work() -> Box<BeWork> {
Box::new(CpalWork {})
}

impl Work<Context, AnyWorkId, Error> for CpalWork {
fn id(&self) -> AnyWorkId {
WorkId::Cpal.into()
}

fn read_access(&self) -> Access<AnyWorkId> {
Access::Variant(AnyWorkId::Fe(FeWorkId::ColorPalettes))
}

/// Generate [cpal](https://learn.microsoft.com/en-us/typography/opentype/spec/cpal)
fn exec(&self, context: &Context) -> Result<(), Error> {
let colors = context.ir.colors.try_get();
// Guard clause: no colors
if !colors
.as_ref()
.map(|c| c.palettes.iter().any(|p| !p.is_empty()))
.unwrap_or_default()
{
debug!("Skip cpal; no colors");
return Ok(());
}
let colors = colors.unwrap();
let sz0 = colors.palettes[0].len();
if colors.palettes.iter().any(|p| p.len() != sz0) {
return Err(Error::InconsistentPaletteLength(
colors.palettes.iter().map(Vec::len).collect(),
));
}

let color_records = colors
.palettes
.iter()
.flat_map(|p| p.iter())
.map(|c| ColorRecord {
red: c.r,
green: c.g,
blue: c.b,
alpha: c.a,
})
.collect::<Vec<_>>();

if color_records.len() > u16::MAX.into() {
return Err(Error::OutOfBounds {
what: "Too many CPAL colorRecords".to_string(),
value: format!("{}", color_records.len()),
});
}

let entries_per_palette = colors.palettes[0].len();
debug!(
"CPAL has {} color records in {} palette(s) of {}",
color_records.len(),
colors.palettes.len(),
entries_per_palette
);
context.cpal.set(Cpal {
num_palette_entries: entries_per_palette as u16,
num_palettes: colors.palettes.len() as u16,
num_color_records: color_records.len() as u16,
color_records_array: NullableOffsetMarker::new(Some(color_records)),
color_record_indices: (0..colors.palettes.len())
.map(|i| (i * entries_per_palette) as u16)
.collect(),
..Default::default()
});

Ok(())
}
}
2 changes: 2 additions & 0 deletions fontbe/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ pub enum Error {
CmapConflict(#[from] CmapConflict),
#[error("Progress stalled computing composite bbox: {0:?}")]
CompositesStalled(Vec<GlyphName>),
#[error("Inconsistent palette lengths observed: {0:?}")]
InconsistentPaletteLength(Vec<usize>),
}

#[derive(Debug)]
Expand Down
11 changes: 8 additions & 3 deletions fontbe/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ use log::debug;
use write_fonts::{
read::TopLevelTable,
tables::{
avar::Avar, cmap::Cmap, fvar::Fvar, gasp::Gasp, gdef::Gdef, glyf::Glyf, gpos::Gpos,
gsub::Gsub, gvar::Gvar, head::Head, hhea::Hhea, hmtx::Hmtx, hvar::Hvar, loca::Loca,
maxp::Maxp, meta::Meta, mvar::Mvar, name::Name, os2::Os2, post::Post, stat::Stat,
avar::Avar, cmap::Cmap, cpal::Cpal, fvar::Fvar, gasp::Gasp, gdef::Gdef, glyf::Glyf,
gpos::Gpos, gsub::Gsub, gvar::Gvar, head::Head, hhea::Hhea, hmtx::Hmtx, hvar::Hvar,
loca::Loca, maxp::Maxp, meta::Meta, mvar::Mvar, name::Name, os2::Os2, post::Post,
stat::Stat,
},
types::Tag,
FontBuilder,
Expand All @@ -34,6 +35,7 @@ enum TableType {
const TABLES_TO_MERGE: &[(WorkId, Tag, TableType)] = &[
(WorkId::Avar, Avar::TAG, TableType::Variable),
(WorkId::Cmap, Cmap::TAG, TableType::Static),
(WorkId::Cpal, Cpal::TAG, TableType::Static),
(WorkId::Fvar, Fvar::TAG, TableType::Variable),
(WorkId::Head, Head::TAG, TableType::Static),
(WorkId::Hhea, Hhea::TAG, TableType::Static),
Expand All @@ -59,6 +61,7 @@ fn has(context: &Context, id: WorkId) -> bool {
match id {
WorkId::Avar => context.avar.try_get().is_some(),
WorkId::Cmap => context.cmap.try_get().is_some(),
WorkId::Cpal => context.cpal.try_get().is_some(),
WorkId::Fvar => context.fvar.try_get().is_some(),
WorkId::Head => context.head.try_get().is_some(),
WorkId::Hhea => context.hhea.try_get().is_some(),
Expand Down Expand Up @@ -87,6 +90,7 @@ fn bytes_for(context: &Context, id: WorkId) -> Result<Option<Vec<u8>>, Error> {
let bytes = match id {
WorkId::Avar => context.avar.get().as_ref().as_ref().and_then(to_bytes),
WorkId::Cmap => to_bytes(context.cmap.get().as_ref()),
WorkId::Cpal => to_bytes(context.cpal.get().as_ref()),
WorkId::Fvar => to_bytes(context.fvar.get().as_ref()),
WorkId::Head => to_bytes(context.head.get().as_ref()),
WorkId::Hhea => to_bytes(context.hhea.get().as_ref()),
Expand Down Expand Up @@ -120,6 +124,7 @@ impl Work<Context, AnyWorkId, Error> for FontWork {
AccessBuilder::new()
.variant(WorkId::Avar)
.variant(WorkId::Cmap)
.variant(WorkId::Cpal)
.variant(WorkId::Fvar)
.variant(WorkId::Head)
.variant(WorkId::Hhea)
Expand Down
1 change: 1 addition & 0 deletions fontbe/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Backend of the `fontc` font compiler.
pub mod avar;
pub mod cmap;
pub mod cpal;
pub mod error;
pub mod features;
pub mod font;
Expand Down
6 changes: 6 additions & 0 deletions fontbe/src/orchestration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use write_fonts::{
tables::{
base::Base,
cmap::Cmap,
cpal::Cpal,
fvar::Fvar,
gasp::Gasp,
gdef::{Gdef, GlyphClassDef},
Expand Down Expand Up @@ -79,6 +80,7 @@ pub enum WorkId {
FeaturesAst,
Avar,
Cmap,
Cpal,
Font,
Fvar,
Gasp,
Expand Down Expand Up @@ -125,6 +127,7 @@ impl Identifier for WorkId {
WorkId::FeaturesAst => "BeFeaturesAst",
WorkId::Avar => "BeAvar",
WorkId::Cmap => "BeCmap",
WorkId::Cpal => "BeCpal",
WorkId::Font => "BeFont",
WorkId::Fvar => "BeFvar",
WorkId::Gasp => "BeGasp",
Expand Down Expand Up @@ -859,6 +862,7 @@ pub struct Context {
// Allow avar to be explicitly None to record a noop avar being generated
pub avar: BeContextItem<PossiblyEmptyAvar>,
pub cmap: BeContextItem<Cmap>,
pub cpal: BeContextItem<Cpal>,
pub fvar: BeContextItem<Fvar>,
pub gasp: BeContextItem<Gasp>,
pub glyf: BeContextItem<Bytes>,
Expand Down Expand Up @@ -899,6 +903,7 @@ impl Context {
gvar_fragments: self.gvar_fragments.clone_with_acl(acl.clone()),
avar: self.avar.clone_with_acl(acl.clone()),
cmap: self.cmap.clone_with_acl(acl.clone()),
cpal: self.cpal.clone_with_acl(acl.clone()),
fvar: self.fvar.clone_with_acl(acl.clone()),
gasp: self.gasp.clone_with_acl(acl.clone()),
glyf: self.glyf.clone_with_acl(acl.clone()),
Expand Down Expand Up @@ -943,6 +948,7 @@ impl Context {
gvar_fragments: ContextMap::new(acl.clone(), persistent_storage.clone()),
avar: ContextItem::new(WorkId::Avar.into(), acl.clone(), persistent_storage.clone()),
cmap: ContextItem::new(WorkId::Cmap.into(), acl.clone(), persistent_storage.clone()),
cpal: ContextItem::new(WorkId::Cpal.into(), acl.clone(), persistent_storage.clone()),
fvar: ContextItem::new(WorkId::Fvar.into(), acl.clone(), persistent_storage.clone()),
gasp: ContextItem::new(WorkId::Gasp.into(), acl.clone(), persistent_storage.clone()),
glyf: ContextItem::new(WorkId::Glyf.into(), acl.clone(), persistent_storage.clone()),
Expand Down
1 change: 1 addition & 0 deletions fontbe/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ impl Paths {
WorkId::GlyfFragment(name) => self.glyph_glyf_file(name.as_str()),
WorkId::GvarFragment(name) => self.glyph_gvar_file(name.as_str()),
WorkId::Avar => self.build_dir.join("avar.table"),
WorkId::Cpal => self.build_dir.join("cpal.table"),
WorkId::Gasp => self.build_dir.join("gasp.table"),
WorkId::Glyf => self.build_dir.join("glyf.table"),
WorkId::Gsub => self.build_dir.join("gsub.table"),
Expand Down
67 changes: 67 additions & 0 deletions fontc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ mod tests {
read::{
tables::{
cmap::{Cmap, CmapSubtable},
cpal::ColorRecord,
gasp::GaspRangeBehavior,
glyf::{self, CompositeGlyph, CurvePoint, Glyf},
gpos::{AnchorTable, Gpos, MarkBasePosFormat1Marker, PositionLookup},
Expand Down Expand Up @@ -345,6 +346,7 @@ mod tests {

let mut expected = vec![
AnyWorkId::Fe(FeWorkIdentifier::StaticMetadata),
FeWorkIdentifier::ColorPalettes.into(),
FeWorkIdentifier::GlobalMetrics.into(),
FeWorkIdentifier::PreliminaryGlyphOrder.into(),
FeWorkIdentifier::GlyphOrder.into(),
Expand All @@ -356,6 +358,7 @@ mod tests {
BeWorkIdentifier::FeaturesAst.into(),
BeWorkIdentifier::Avar.into(),
BeWorkIdentifier::Cmap.into(),
BeWorkIdentifier::Cpal.into(),
BeWorkIdentifier::Font.into(),
BeWorkIdentifier::Fvar.into(),
BeWorkIdentifier::Gasp.into(),
Expand Down Expand Up @@ -2872,4 +2875,68 @@ mod tests {
let result = TestCompile::compile_source("glyphs3/EmptyMetaTable.glyphs");
assert!(result.be_context.meta.try_get().is_none())
}

#[test]
fn cpal_grayscale() {
let result = TestCompile::compile_source("glyphs3/COLRv1-grayscale.glyphs");
let cpal = result.font().cpal().unwrap();
assert_eq!(
(
1,
2,
[
ColorRecord {
red: 0,
green: 0,
blue: 0,
alpha: 255
},
ColorRecord {
red: 64,
green: 64,
blue: 64,
alpha: 255
}
]
.as_slice()
),
(
cpal.num_palettes(),
cpal.num_palette_entries(),
cpal.color_records_array().unwrap().unwrap()
)
);
}

#[test]
fn cpal_color() {
let result = TestCompile::compile_source("glyphs3/COLRv1-simple.glyphs");
let cpal = result.font().cpal().unwrap();
assert_eq!(
(
1,
2,
[
ColorRecord {
red: 0,
green: 0,
blue: 255,
alpha: 255
},
ColorRecord {
red: 255,
green: 0,
blue: 0,
alpha: 255
}
]
.as_slice()
),
(
cpal.num_palettes(),
cpal.num_palette_entries(),
cpal.color_records_array().unwrap().unwrap()
)
);
}
}
2 changes: 2 additions & 0 deletions fontc/src/timing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ impl JobTimer {
fn short_name(id: &AnyWorkId) -> &'static str {
match id {
AnyWorkId::Fe(FeWorkIdentifier::Anchor(..)) => "anchor",
AnyWorkId::Fe(FeWorkIdentifier::ColorPalettes) => "cpal",
AnyWorkId::Fe(FeWorkIdentifier::Features) => "fea",
AnyWorkId::Fe(FeWorkIdentifier::GlobalMetrics) => "metrics",
AnyWorkId::Fe(FeWorkIdentifier::Glyph(..)) => "glyph",
Expand All @@ -133,6 +134,7 @@ fn short_name(id: &AnyWorkId) -> &'static str {
AnyWorkId::Fe(FeWorkIdentifier::StaticMetadata) => "static-meta",
AnyWorkId::Be(BeWorkIdentifier::Avar) => "avar",
AnyWorkId::Be(BeWorkIdentifier::Cmap) => "cmap",
AnyWorkId::Be(BeWorkIdentifier::Cpal) => "cpal-be",
AnyWorkId::Be(BeWorkIdentifier::Features) => "fea",
AnyWorkId::Be(BeWorkIdentifier::FeaturesAst) => "fea.ast",
AnyWorkId::Be(BeWorkIdentifier::Font) => "font",
Expand Down
3 changes: 3 additions & 0 deletions fontc/src/workload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crossbeam_channel::{Receiver, TryRecvError};
use fontbe::{
avar::create_avar_work,
cmap::create_cmap_work,
cpal::create_cpal_work,
features::{
create_gather_ir_kerning_work, create_kern_segment_work, create_kerns_work,
create_mark_work, FeatureCompilationWork, FeatureFirstPassWork,
Expand Down Expand Up @@ -154,6 +155,7 @@ impl Workload {
.into_iter()
.for_each(|w| workload.add(w));
workload.add(create_glyph_order_work());
workload.add(workload.source.create_color_palette_work()?);

// BE: f(IR, maybe other BE work) => binary
workload.add_skippable_feature_work(FeatureFirstPassWork::create());
Expand All @@ -175,6 +177,7 @@ impl Workload {
workload.add(create_stat_work());
workload.add(create_meta_work());
workload.add(create_cmap_work());
workload.add(create_cpal_work());
workload.add(create_fvar_work());
workload.add(create_gvar_work());
workload.add(create_head_work());
Expand Down
Loading

0 comments on commit d83dda9

Please sign in to comment.