@@ -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
109109bool 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+
590725void 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.
617766StatusOr<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 ());
0 commit comments