Skip to content

Commit ac8ccca

Browse files
committed
Add support for encoding CFF2 fonts.
1 parent d8d5b80 commit ac8ccca

File tree

3 files changed

+277
-17
lines changed

3 files changed

+277
-17
lines changed

ift/encoder/encoder.cc

Lines changed: 173 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ StatusOr<FontData> Encoder::FullyExpandedSubset(
103103
// TODO(garretrieger): once union works correctly remove this.
104104
all.design_space.clear();
105105

106-
return CutSubset(context, face_.get(), all);
106+
return CutSubset(context, face_.get(), all, false);
107107
}
108108

109109
bool is_subset(const flat_hash_set<uint32_t>& a,
@@ -329,9 +329,9 @@ Status Encoder::EnsureGlyphKeyedPatchesPopulated(
329329
instance.shallow_copy(*result);
330330
}
331331

332-
GlyphKeyedDiff differ(
333-
instance, compat_id,
334-
{FontHelper::kGlyf, FontHelper::kGvar, FontHelper::kCFF});
332+
GlyphKeyedDiff differ(instance, compat_id,
333+
{FontHelper::kGlyf, FontHelper::kGvar, FontHelper::kCFF,
334+
FontHelper::kCFF2});
335335

336336
for (uint32_t index : reachable_segments) {
337337
auto e = glyph_data_patches_.find(index);
@@ -404,7 +404,7 @@ StatusOr<FontData> Encoder::Encode(ProcessingContext& context,
404404
// The first subset forms the base file, the remaining subsets are made
405405
// reachable via patches.
406406
auto full_face = context.fully_expanded_subset_.face();
407-
auto base = CutSubset(context, full_face.get(), base_subset);
407+
auto base = CutSubset(context, full_face.get(), base_subset, IsMixedMode());
408408
if (!base.ok()) {
409409
return base.status();
410410
}
@@ -520,7 +520,7 @@ StatusOr<std::unique_ptr<const BinaryDiff>> Encoder::GetDifferFor(
520520
Encoder::MixedModeTableKeyedDiff(compat_id));
521521
}
522522

523-
StatusOr<hb_face_unique_ptr> Encoder::CutSubsetFaceBuilder(
523+
StatusOr<hb_subset_plan_t*> Encoder::CreateSubsetPlan(
524524
const ProcessingContext& context, hb_face_t* font,
525525
const SubsetDefinition& def) const {
526526
hb_subset_input_t* input = hb_subset_input_create_or_fail();
@@ -529,15 +529,29 @@ StatusOr<hb_face_unique_ptr> Encoder::CutSubsetFaceBuilder(
529529
}
530530

531531
def.ConfigureInput(input, font);
532-
533532
SetMixedModeSubsettingFlagsIfNeeded(context, input);
534533

535-
hb_face_unique_ptr result = make_hb_face(hb_subset_or_fail(font, input));
534+
hb_subset_plan_t* plan = hb_subset_plan_create_or_fail(font, input);
535+
hb_subset_input_destroy(input);
536+
if (!plan) {
537+
return absl::InternalError("Harfbuzz subsetting plan generation failed.");
538+
}
539+
540+
return plan;
541+
}
542+
543+
StatusOr<hb_face_unique_ptr> Encoder::CutSubsetFaceBuilder(
544+
const ProcessingContext& context, hb_face_t* font,
545+
const SubsetDefinition& def) const {
546+
hb_subset_plan_t* plan = TRY(CreateSubsetPlan(context, font, def));
547+
548+
hb_face_unique_ptr result =
549+
make_hb_face(hb_subset_plan_execute_or_fail(plan));
550+
hb_subset_plan_destroy(plan);
536551
if (!result.get()) {
537552
return absl::InternalError("Harfbuzz subsetting operation failed.");
538553
}
539554

540-
hb_subset_input_destroy(input);
541555
return result;
542556
}
543557

@@ -587,6 +601,127 @@ StatusOr<FontData> Encoder::GenerateBaseGvar(
587601
return result;
588602
}
589603

604+
// Generate a CFF2 CharStrings index that retains glyph ids, but contains
605+
// glyph data from face only for gids.
606+
absl::StatusOr<std::string> GenerateCharStringsTable(hb_face_t* face,
607+
const hb_set_t* gids) {
608+
// Create the per glyph data and offsets
609+
std::string charstrings_per_glyph;
610+
611+
uint32_t glyph_count = hb_face_get_glyph_count(face);
612+
uint32_t current_offset = 1;
613+
std::vector<uint32_t> offsets;
614+
for (uint32_t gid = 0; gid < glyph_count; gid++) {
615+
offsets.push_back(current_offset);
616+
if (!hb_set_has(gids, gid)) {
617+
continue;
618+
}
619+
620+
auto glyph_data = FontHelper::Cff2Data(face, gid);
621+
charstrings_per_glyph += glyph_data.str();
622+
current_offset += glyph_data.size();
623+
}
624+
offsets.push_back(current_offset); // one extra offset at the end.
625+
626+
if (offsets.size() != glyph_count + 1) {
627+
return absl::InternalError("Wrong number of offsets generated.");
628+
}
629+
630+
// Determine offset size
631+
uint64_t offset_size = 1;
632+
for (; offset_size <= 4; offset_size++) {
633+
uint64_t max_value = (1 << (8 * offset_size)) - 1;
634+
if (current_offset <= max_value) {
635+
break;
636+
}
637+
}
638+
if (offset_size > 4) {
639+
return absl::InvalidArgumentError(
640+
"Offset overflow generating CFF2 charstrings.");
641+
}
642+
643+
// Serialization, reference:
644+
// https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#index-data
645+
std::string charstrings;
646+
647+
FontHelper::WriteUInt32(glyph_count, charstrings);
648+
FontHelper::WriteUInt8(offset_size, charstrings);
649+
650+
for (uint32_t offset : offsets) {
651+
switch (offset_size) {
652+
case 1:
653+
FontHelper::WriteUInt8(offset, charstrings);
654+
break;
655+
case 2:
656+
FontHelper::WriteUInt16(offset, charstrings);
657+
break;
658+
case 3:
659+
FontHelper::WriteUInt24(offset, charstrings);
660+
break;
661+
case 4:
662+
default:
663+
FontHelper::WriteUInt32(offset, charstrings);
664+
break;
665+
}
666+
}
667+
668+
charstrings += charstrings_per_glyph;
669+
return charstrings;
670+
}
671+
672+
StatusOr<FontData> Encoder::GenerateBaseCff2(
673+
const ProcessingContext& context, hb_face_t* font,
674+
const design_space_t& design_space) const {
675+
// The base CFF2 table is made by combining all of the non charstrings data
676+
// from 'font' which has only been instanced to 'design_space' with the
677+
// charstrings data for any glyphs retained by the base subset definition.
678+
//
679+
// To accomplish this we manually craft a new charstring table. This works
680+
// because the IFT spec requires charstrings data is at the end of the table
681+
// and doesn't overlap. so we are free to replace the charstrings table with
682+
// our own.
683+
684+
// Step 1: Instancing
685+
auto instance = Instance(context, font, design_space);
686+
if (!instance.ok()) {
687+
return instance.status();
688+
}
689+
auto instance_face = instance->face();
690+
691+
// Step 2: find the glyph closure for the base subset.
692+
SubsetDefinition subset = context.base_subset_;
693+
hb_subset_plan_t* plan = TRY(CreateSubsetPlan(context, font, subset));
694+
hb_map_t* old_to_new = hb_subset_plan_old_to_new_glyph_mapping(plan);
695+
696+
int index = -1;
697+
uint32_t old_gid = HB_MAP_VALUE_INVALID;
698+
uint32_t new_gid = HB_MAP_VALUE_INVALID;
699+
hb_set_unique_ptr gids = make_hb_set();
700+
while (hb_map_next(old_to_new, &index, &old_gid, &new_gid)) {
701+
hb_set_add(gids.get(), old_gid);
702+
}
703+
hb_subset_plan_destroy(plan);
704+
705+
// Step 3: locate charstrings data
706+
FontData instance_non_charstrings;
707+
FontData instance_charstrings;
708+
TRYV(FontHelper::Cff2GetCharstrings(
709+
instance_face.get(), instance_non_charstrings, instance_charstrings));
710+
711+
// Step 4: construct a new charstrings table.
712+
// This charstring table includes charstring data from "instance_face" for all
713+
// glyphs in "gids".
714+
std::string charstrings =
715+
TRY(GenerateCharStringsTable(instance_face.get(), gids.get()));
716+
717+
// Step 5: assemble the composite table.
718+
std::string composite_table = instance_non_charstrings.string() + charstrings;
719+
720+
FontData result;
721+
result.copy(composite_table);
722+
return result;
723+
}
724+
590725
void Encoder::SetMixedModeSubsettingFlagsIfNeeded(
591726
const ProcessingContext& context, hb_subset_input_t* input) const {
592727
if (IsMixedMode()) {
@@ -614,16 +749,32 @@ void Encoder::SetMixedModeSubsettingFlagsIfNeeded(
614749
}
615750
}
616751

752+
// Creates a subset for a given subset definition.
753+
//
754+
// If 'generate_glyph_keyed_bases' is true then for tables such as gvar and CFF2
755+
// which have common data, the subsetted tables will be generated in a way that
756+
// preserves that common data in order to retain compatibility with glyph keyed
757+
// patching. See the comments in this function for more details.
758+
//
759+
// Additionally the set of glyphs in these tables will be set to the set of
760+
// glyphs in the base subset rather then what's in def since glyph keyed patches
761+
// are responsible for populating those.
762+
//
763+
// Special casing isn't needed for glyf or CFF since those are never patched
764+
// by table keyed patches and don't have common data (CFF is desubroutinized)
765+
// so we can just ignore them here.
617766
StatusOr<FontData> Encoder::CutSubset(const ProcessingContext& context,
618767
hb_face_t* font,
619-
const SubsetDefinition& def) const {
768+
const SubsetDefinition& def,
769+
bool generate_glyph_keyed_bases) const {
620770
auto result = CutSubsetFaceBuilder(context, font, def);
621771
if (!result.ok()) {
622772
return result.status();
623773
}
624774

625775
auto tags = FontHelper::GetTags(font);
626-
if (IsMixedMode() && def.IsVariable() && tags.contains(FontHelper::kGvar)) {
776+
if (generate_glyph_keyed_bases && def.IsVariable() &&
777+
tags.contains(FontHelper::kGvar)) {
627778
// In mixed mode glyph keyed patches handles gvar, except for when design
628779
// space is expanded, in which case a gvar table should be patched in that
629780
// only has coverage of the base (root) subset definition + the current
@@ -638,6 +789,17 @@ StatusOr<FontData> Encoder::CutSubset(const ProcessingContext& context,
638789
gvar_blob.get());
639790
}
640791

792+
if (generate_glyph_keyed_bases && tags.contains(FontHelper::kCFF2)) {
793+
// In mixed mode glyph keyed patches handles CFF2 per glyph data. However,
794+
// the CFF2 table may contain shared variation data outside of the glyphs.
795+
// So when creating a subsetted CFF2 table here we need to ensure the shared
796+
// variation data will match whatever the glyph keyed patches were cut from.
797+
auto base_cff2 = TRY(GenerateBaseCff2(context, font, def.design_space));
798+
hb_blob_unique_ptr cff2_blob = base_cff2.blob();
799+
hb_face_builder_add_table(result->get(), FontHelper::kCFF2,
800+
cff2_blob.get());
801+
}
802+
641803
hb_blob_unique_ptr blob = make_hb_blob(hb_face_reference_blob(result->get()));
642804

643805
FontData subset(blob.get());

ift/encoder/encoder.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ class Encoder {
181181
absl::Status PopulateGlyphKeyedPatchMap(
182182
ift::proto::PatchMap& patch_map) const;
183183

184+
absl::StatusOr<hb_subset_plan_t*> CreateSubsetPlan(
185+
const ProcessingContext& context, hb_face_t* font,
186+
const SubsetDefinition& def) const;
187+
184188
absl::StatusOr<common::hb_face_unique_ptr> CutSubsetFaceBuilder(
185189
const ProcessingContext& context, hb_face_t* font,
186190
const SubsetDefinition& def) const;
@@ -189,12 +193,16 @@ class Encoder {
189193
const ProcessingContext& context, hb_face_t* font,
190194
const design_space_t& design_space) const;
191195

196+
absl::StatusOr<common::FontData> GenerateBaseCff2(
197+
const ProcessingContext& context, hb_face_t* font,
198+
const design_space_t& design_space) const;
199+
192200
void SetMixedModeSubsettingFlagsIfNeeded(const ProcessingContext& context,
193201
hb_subset_input_t* input) const;
194202

195-
absl::StatusOr<common::FontData> CutSubset(const ProcessingContext& context,
196-
hb_face_t* font,
197-
const SubsetDefinition& def) const;
203+
absl::StatusOr<common::FontData> CutSubset(
204+
const ProcessingContext& context, hb_face_t* font,
205+
const SubsetDefinition& def, bool generate_glyph_keyed_bases) const;
198206

199207
absl::StatusOr<common::FontData> Instance(
200208
const ProcessingContext& context, hb_face_t* font,
@@ -212,7 +220,7 @@ class Encoder {
212220
static ift::TableKeyedDiff* MixedModeTableKeyedDiff(
213221
common::CompatId base_compat_id) {
214222
return new TableKeyedDiff(base_compat_id,
215-
{"IFTX", "glyf", "loca", "gvar", "CFF "});
223+
{"IFTX", "glyf", "loca", "gvar", "CFF ", "CFF2"});
216224
}
217225

218226
static ift::TableKeyedDiff* ReplaceIftMapTableKeyedDiff(
@@ -222,7 +230,7 @@ class Encoder {
222230
// space. Glyph segment patches for all prev loaded glyphs will be
223231
// downloaded to repopulate variation data for any already loaded glyphs.
224232
return new TableKeyedDiff(base_compat_id, {"glyf", "loca", "CFF "},
225-
{"IFTX", "gvar"});
233+
{"IFTX", "gvar", "CFF2"});
226234
}
227235

228236
bool AllocatePatchSet(ProcessingContext& context,

0 commit comments

Comments
 (0)