Skip to content
Closed
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
8 changes: 8 additions & 0 deletions skera/src/cff/subset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,9 +403,17 @@ pub(super) fn assemble<F: CffFlavor>(
let mut strings = Vec::new();
let mut sid_map = BTreeMap::new();
let mut codes = vec![0];
let identity_charset = source.cid
&& plan
.subset_flags
.contains(SubsetFlags::SUBSET_FLAGS_CFF_IDENTITY_CHARSET);
if !F::CFF2 {
let cs = source.font.charset().ok_or(Error)?;
for &old in old_gids.iter().skip(1) {
if identity_charset {
codes.push(codes.len());
continue;
}
let code = cs
.string_id(GlyphId::new(old as u32))
.ok_or(Error)?
Expand Down
4 changes: 4 additions & 0 deletions skera/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ impl SubsetFlags {
/// If set do not pull mirrored versions of input codepoints into the subset.
pub const SUBSET_FLAGS_NO_BIDI_CLOSURE: Self = Self(0x0800);

/// If set CID-keyed CFF charsets use sequential CIDs equal to the output
/// glyph IDs. Name-keyed CFF and CFF2 fonts are unaffected.
pub const SUBSET_FLAGS_CFF_IDENTITY_CHARSET: Self = Self(0x8000);

/// Returns `true` if all of the flags in `other` are contained within `self`.
#[inline]
pub const fn contains(&self, other: Self) -> bool {
Expand Down
7 changes: 7 additions & 0 deletions skera/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ struct Args {
#[arg(long)]
optimize: bool,

/// Emit an identity CFF charset (CID = output GID) for CID-keyed CFF
#[arg(long)]
cff_identity_charset: bool,

///run subsetter N times
#[arg(short, long)]
num_iterations: Option<u32>,
Expand Down Expand Up @@ -342,5 +346,8 @@ fn parse_subset_flags(args: &Args) -> SubsetFlags {
if args.optimize {
flags |= SubsetFlags::SUBSET_FLAGS_OPTIMIZE_IUP_DELTAS;
}
if args.cff_identity_charset {
flags |= SubsetFlags::SUBSET_FLAGS_CFF_IDENTITY_CHARSET;
}
flags
}
69 changes: 69 additions & 0 deletions skera/tests/cff_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,75 @@ fn retained_gid_holes_and_notdef_are_empty() {
}
}

#[test]
fn identity_charsets_use_output_gids_only_for_cid_keyed_cff() {
for file in [
"SourceHanSans-Regular_subset.otf",
"SourceSansPro-Regular.otf",
"AdobeVFPrototype.otf",
] {
let source = std::fs::read(format!("test-data/fonts/{file}")).unwrap();
let font = FontRef::new(&source).unwrap();
for retain in [false, true] {
let flags = SubsetFlags::SUBSET_FLAGS_NOTDEF_OUTLINE
| if retain {
SubsetFlags::SUBSET_FLAGS_RETAIN_GIDS
} else {
SubsetFlags::default()
};
let dense_plan = plan(&font, flags, "U+0041,U+0043,U+0061,U+4E00");
let identity_plan = plan(
&font,
flags | SubsetFlags::SUBSET_FLAGS_CFF_IDENTITY_CHARSET,
"U+0041,U+0043,U+0061,U+4E00",
);
let ordinary = subset_font(&font, &dense_plan).unwrap();
let identity = subset_font(&font, &identity_plan).unwrap();
let ordinary = FontRef::new(&ordinary).unwrap();
let identity = FontRef::new(&identity).unwrap();
let tag = if identity.cff2().is_ok() {
Tag::new(b"CFF2")
} else {
Tag::new(b"CFF ")
};
let a_data = ordinary.data_for_tag(tag).unwrap().as_bytes();
let b_data = identity.data_for_tag(tag).unwrap().as_bytes();
let a = CffFontRef::new(a_data, 0, None).unwrap();
let b = CffFontRef::new(b_data, 0, None).unwrap();
assert_eq!(a.num_glyphs(), b.num_glyphs());
if tag == Tag::new(b"CFF ") && b.is_cid() {
for gid in 0..b.num_glyphs() {
let gid = GlyphId::new(gid);
assert_eq!(
b.charset().unwrap().string_id(gid).unwrap().to_u16() as u32,
gid.to_u32()
);
assert_eq!(
a.charstrings().get(gid.to_u32() as usize),
b.charstrings().get(gid.to_u32() as usize)
);
assert_eq!(a.subfont_index(gid), b.subfont_index(gid));
}
assert!(
identity_plan
.old_to_new_glyph_mapping()
.any(|(old, new)| old != new)
|| retain
);
} else {
assert_eq!(a_data, b_data, "{file}");
}
for tag in [b"cmap", b"hmtx", b"GPOS", b"GSUB"] {
let tag = Tag::new(tag);
assert_eq!(
ordinary.data_for_tag(tag).map(|d| d.as_bytes()),
identity.data_for_tag(tag).map(|d| d.as_bytes())
);
}
}
}
}

#[test]
fn cff2_subsetting_preserves_variation_locations() {
use write_fonts::types::F2Dot14;
Expand Down
Loading