@@ -72,6 +72,10 @@ class IntegrationTest : public ::testing::Test {
7272 hb_blob_create_from_file (" ift/testdata/NotoSansJP-Regular.subset.ttf" ));
7373 noto_sans_jp_.set (blob.get ());
7474
75+ blob = make_hb_blob (
76+ hb_blob_create_from_file (" common/testdata/NotoSansJP-Regular.otf" ));
77+ noto_sans_jp_cff_.set (blob.get ());
78+
7579 // Noto Sans JP VF
7680 blob = make_hb_blob (
7781 hb_blob_create_from_file (" ift/testdata/NotoSansJP[wght].subset.ttf" ));
@@ -119,6 +123,20 @@ class IntegrationTest : public ::testing::Test {
119123 return init_segment;
120124 }
121125
126+ Status InitEncoderForMixedModeCff (Encoder& encoder) {
127+ auto face = noto_sans_jp_cff_.face ();
128+ encoder.SetFace (face.get ());
129+
130+ auto sc = encoder.AddGlyphDataPatch (1 , {34 , 35 , 46 , 47 }); // A, B, M, N
131+ sc.Update (encoder.AddGlyphDataPatch (2 , {41 , 42 , 43 , 59 })); // H, I, J, Z
132+
133+ if (!sc.ok ()) {
134+ return sc;
135+ }
136+
137+ return absl::OkStatus ();
138+ }
139+
122140 StatusOr<btree_set<uint32_t >> InitEncoderForVfMixedMode (Encoder& encoder) {
123141 auto face = noto_sans_vf_.face ();
124142 encoder.SetFace (face.get ());
@@ -202,6 +220,7 @@ class IntegrationTest : public ::testing::Test {
202220 }
203221
204222 FontData noto_sans_jp_;
223+ FontData noto_sans_jp_cff_;
205224 FontData noto_sans_vf_;
206225
207226 FontData feature_test_;
@@ -1160,4 +1179,92 @@ TEST_F(IntegrationTest, MixedMode_DesignSpaceAugmentation_DropsUnusedPatches) {
11601179 ASSERT_GT (FontHelper::GvarData (extended_face.get (), chunk4_gid)->size (), 0 );
11611180}
11621181
1182+ StatusOr<FontData> desubroutinize (hb_face_t * font) {
1183+ hb_subset_input_t * input = hb_subset_input_create_or_fail ();
1184+ if (!input) {
1185+ return absl::InternalError (" failed to create subset input." );
1186+ }
1187+
1188+ hb_subset_input_keep_everything (input);
1189+ hb_subset_input_set_flags (
1190+ input, hb_subset_input_get_flags (input) | HB_SUBSET_FLAGS_DESUBROUTINIZE);
1191+
1192+ hb_face_t * subset = hb_subset_or_fail (font, input);
1193+ hb_subset_input_destroy (input);
1194+
1195+ if (!subset) {
1196+ return absl::InternalError (" subset operation failed." );
1197+ }
1198+
1199+ FontData result (subset);
1200+ hb_face_destroy (subset);
1201+
1202+ return result;
1203+ }
1204+
1205+ TEST_F (IntegrationTest, MixedMode_Cff) {
1206+ Encoder encoder;
1207+ auto sc = InitEncoderForMixedModeCff (encoder);
1208+ ASSERT_TRUE (sc.ok ()) << sc;
1209+
1210+ ASSERT_TRUE (encoder.SetBaseSubset (btree_set<uint32_t >()).ok ());
1211+
1212+ auto all_codepoints =
1213+ btree_set<uint32_t >{' A' , ' B' , ' H' , ' I' , ' J' , ' M' , ' N' , ' Z' };
1214+ auto face = noto_sans_jp_cff_.face ();
1215+ encoder.AddNonGlyphDataSegment (all_codepoints);
1216+
1217+ // Setup activations for patches 1 and 2
1218+ sc.Update (encoder.AddGlyphDataPatchCondition (Condition::SimpleCondition (
1219+ SubsetDefinition::Codepoints (btree_set<uint32_t >{' A' , ' B' , ' M' , ' N' }),
1220+ 1 )));
1221+ sc.Update (encoder.AddGlyphDataPatchCondition (Condition::SimpleCondition (
1222+ SubsetDefinition::Codepoints (btree_set<uint32_t >{' H' , ' I' , ' J' , ' Z' }),
1223+ 2 )));
1224+
1225+ auto encoding = encoder.Encode ();
1226+ ASSERT_TRUE (encoding.ok ()) << encoding.status ();
1227+ auto encoded_face = encoding->init_font .face ();
1228+
1229+ ASSERT_EQ (FontHelper::CffData (encoded_face.get (), 34 ).size (),
1230+ 1 ); // empty glyphs in cff are one byte long
1231+ ASSERT_EQ (FontHelper::CffData (encoded_face.get (), 43 ).size (),
1232+ 1 ); // empty glyphs in cff are one byte long
1233+
1234+ auto codepoints = FontHelper::ToCodepointsSet (encoded_face.get ());
1235+ ASSERT_TRUE (codepoints.empty ());
1236+
1237+ auto extended = Extend (*encoding, {' M' });
1238+ ASSERT_TRUE (extended.ok ()) << extended.status ();
1239+ auto extended_face = extended->face ();
1240+
1241+ // The encoder desubroutinizes CFF fonts, so generate a desubroutinized
1242+ // copy of the input face to use for comparisons.
1243+ auto desubroutinized = desubroutinize (face.get ());
1244+ ASSERT_TRUE (desubroutinized.ok ()) << desubroutinized.status ();
1245+ auto desubroutinized_face = desubroutinized->face ();
1246+
1247+ codepoints = FontHelper::ToCodepointsSet (extended_face.get ());
1248+ ASSERT_EQ (codepoints, all_codepoints);
1249+
1250+ // patch 2 gids not present
1251+ ASSERT_EQ (FontHelper::CffData (encoded_face.get (), 43 ).size (),
1252+ 1 ); // empty glyphs in cff are one byte long
1253+
1254+ // patch 1 gids present and match the desubroutinized face.
1255+ ASSERT_EQ (FontHelper::CffData (extended_face.get (), 34 ).span (),
1256+ FontHelper::CffData (desubroutinized_face.get (), 34 ).span ());
1257+
1258+ // Second extension
1259+ encoding->init_font .shallow_copy (*extended);
1260+ extended = Extend (*encoding, {' H' });
1261+ ASSERT_TRUE (extended.ok ()) << extended.status ();
1262+ extended_face = extended->face ();
1263+
1264+ ASSERT_EQ (FontHelper::CffData (extended_face.get (), 43 ).span (),
1265+ FontHelper::CffData (desubroutinized_face.get (), 43 ).span ());
1266+ ASSERT_EQ (FontHelper::CffData (extended_face.get (), 34 ).span (),
1267+ FontHelper::CffData (desubroutinized_face.get (), 34 ).span ());
1268+ }
1269+
11631270} // namespace ift
0 commit comments