From d650f80fed87216ee9446fa9a60bfa81c420c5f6 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Tue, 27 Oct 2020 16:14:19 -0700 Subject: [PATCH 1/3] Add a transformer which can apply woff2 glyf and loca transformations to a font. --- patch_subset/BUILD | 5 + patch_subset/do_subset.cc | 410 ++++++++++++++++++++ patch_subset/glyf_transformer.h | 20 + patch_subset/woff2_glyf_transformer.cc | 69 ++++ patch_subset/woff2_glyf_transformer.h | 18 + patch_subset/woff2_glyf_transformer_test.cc | 59 +++ third_party/woff2.BUILD | 3 + 7 files changed, 584 insertions(+) create mode 100644 patch_subset/do_subset.cc create mode 100644 patch_subset/glyf_transformer.h create mode 100644 patch_subset/woff2_glyf_transformer.cc create mode 100644 patch_subset/woff2_glyf_transformer.h create mode 100644 patch_subset/woff2_glyf_transformer_test.cc diff --git a/patch_subset/BUILD b/patch_subset/BUILD index fa6bf12..a4bf2cc 100644 --- a/patch_subset/BUILD +++ b/patch_subset/BUILD @@ -24,6 +24,7 @@ cc_library( "noop_codepoint_predictor.h", "patch_subset_server_impl.cc", "simple_codepoint_mapper.cc", + "woff2_glyf_transformer.cc", ], hdrs = [ "codepoint_mapper.h", @@ -31,10 +32,12 @@ cc_library( "codepoint_mapping_checksum_impl.h", "codepoint_predictor.h", "frequency_codepoint_predictor.h", + "glyf_transformer.h", "harfbuzz_subsetter.h", "patch_subset_server_impl.h", "simple_codepoint_mapper.h", "subsetter.h", + "woff2_glyf_transformer.h", ], data = [ "//analysis/pfe_methods/unicode_range_data:slicing_strategies", @@ -52,6 +55,7 @@ cc_library( "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/strings", "@harfbuzz", + "@woff2", ], ) @@ -153,6 +157,7 @@ cc_test( "server_integration_test.cc", "simple_codepoint_mapper_test.cc", "sparse_bit_set_test.cc", + "woff2_glyf_transformer_test.cc", ], data = [ ":testdata", diff --git a/patch_subset/do_subset.cc b/patch_subset/do_subset.cc new file mode 100644 index 0000000..1b78e7d --- /dev/null +++ b/patch_subset/do_subset.cc @@ -0,0 +1,410 @@ +#include "common/status.h" +#include "hb.h" +#include "patch_subset/file_font_provider.h" +#include "patch_subset/harfbuzz_subsetter.h" + +using patch_subset::FileFontProvider; +using patch_subset::FontData; +using patch_subset::HarfbuzzSubsetter; +using patch_subset::StatusCode; + +int main(int argc, char** argv) { + FileFontProvider font_provider( + "/usr/local/google/home/grieger/src/font_lib/"); + HarfbuzzSubsetter subsetter; + + FontData font; + if (font_provider.GetFont("ofl/firasans/FiraSans-Light.ttf", &font) != + StatusCode::kOk) { + printf("Font lookup failed\n"); + return -1; + } + + hb_set_t* codepoints = hb_set_create(); + hb_set_add(codepoints, 13); + hb_set_add(codepoints, 32); + hb_set_add(codepoints, 33); + hb_set_add(codepoints, 34); + hb_set_add(codepoints, 35); + hb_set_add(codepoints, 36); + hb_set_add(codepoints, 37); + hb_set_add(codepoints, 38); + hb_set_add(codepoints, 39); + hb_set_add(codepoints, 40); + hb_set_add(codepoints, 41); + hb_set_add(codepoints, 42); + hb_set_add(codepoints, 43); + hb_set_add(codepoints, 44); + hb_set_add(codepoints, 45); + hb_set_add(codepoints, 46); + hb_set_add(codepoints, 47); + hb_set_add(codepoints, 48); + hb_set_add(codepoints, 49); + hb_set_add(codepoints, 50); + hb_set_add(codepoints, 51); + hb_set_add(codepoints, 52); + hb_set_add(codepoints, 53); + hb_set_add(codepoints, 54); + hb_set_add(codepoints, 55); + hb_set_add(codepoints, 56); + hb_set_add(codepoints, 57); + hb_set_add(codepoints, 58); + hb_set_add(codepoints, 59); + hb_set_add(codepoints, 60); + hb_set_add(codepoints, 61); + hb_set_add(codepoints, 62); + hb_set_add(codepoints, 63); + hb_set_add(codepoints, 64); + hb_set_add(codepoints, 65); + hb_set_add(codepoints, 66); + hb_set_add(codepoints, 67); + hb_set_add(codepoints, 68); + hb_set_add(codepoints, 69); + hb_set_add(codepoints, 70); + hb_set_add(codepoints, 71); + hb_set_add(codepoints, 72); + hb_set_add(codepoints, 73); + hb_set_add(codepoints, 74); + hb_set_add(codepoints, 75); + hb_set_add(codepoints, 76); + hb_set_add(codepoints, 77); + hb_set_add(codepoints, 78); + hb_set_add(codepoints, 79); + hb_set_add(codepoints, 80); + hb_set_add(codepoints, 81); + hb_set_add(codepoints, 82); + hb_set_add(codepoints, 83); + hb_set_add(codepoints, 84); + hb_set_add(codepoints, 85); + hb_set_add(codepoints, 86); + hb_set_add(codepoints, 87); + hb_set_add(codepoints, 88); + hb_set_add(codepoints, 89); + hb_set_add(codepoints, 90); + hb_set_add(codepoints, 91); + hb_set_add(codepoints, 92); + hb_set_add(codepoints, 93); + hb_set_add(codepoints, 94); + hb_set_add(codepoints, 95); + hb_set_add(codepoints, 96); + hb_set_add(codepoints, 97); + hb_set_add(codepoints, 98); + hb_set_add(codepoints, 99); + hb_set_add(codepoints, 100); + hb_set_add(codepoints, 101); + hb_set_add(codepoints, 102); + hb_set_add(codepoints, 103); + hb_set_add(codepoints, 104); + hb_set_add(codepoints, 105); + hb_set_add(codepoints, 106); + hb_set_add(codepoints, 107); + hb_set_add(codepoints, 108); + hb_set_add(codepoints, 109); + hb_set_add(codepoints, 110); + hb_set_add(codepoints, 111); + hb_set_add(codepoints, 112); + hb_set_add(codepoints, 113); + hb_set_add(codepoints, 114); + hb_set_add(codepoints, 115); + hb_set_add(codepoints, 116); + hb_set_add(codepoints, 117); + hb_set_add(codepoints, 118); + hb_set_add(codepoints, 119); + hb_set_add(codepoints, 120); + hb_set_add(codepoints, 121); + hb_set_add(codepoints, 122); + hb_set_add(codepoints, 123); + hb_set_add(codepoints, 124); + hb_set_add(codepoints, 125); + hb_set_add(codepoints, 126); + hb_set_add(codepoints, 160); + hb_set_add(codepoints, 161); + hb_set_add(codepoints, 162); + hb_set_add(codepoints, 163); + hb_set_add(codepoints, 164); + hb_set_add(codepoints, 165); + hb_set_add(codepoints, 166); + hb_set_add(codepoints, 167); + hb_set_add(codepoints, 168); + hb_set_add(codepoints, 169); + hb_set_add(codepoints, 170); + hb_set_add(codepoints, 171); + hb_set_add(codepoints, 172); + hb_set_add(codepoints, 174); + hb_set_add(codepoints, 175); + hb_set_add(codepoints, 176); + hb_set_add(codepoints, 177); + hb_set_add(codepoints, 178); + hb_set_add(codepoints, 179); + hb_set_add(codepoints, 180); + hb_set_add(codepoints, 181); + hb_set_add(codepoints, 182); + hb_set_add(codepoints, 183); + hb_set_add(codepoints, 185); + hb_set_add(codepoints, 186); + hb_set_add(codepoints, 187); + hb_set_add(codepoints, 188); + hb_set_add(codepoints, 189); + hb_set_add(codepoints, 190); + hb_set_add(codepoints, 191); + hb_set_add(codepoints, 192); + hb_set_add(codepoints, 193); + hb_set_add(codepoints, 194); + hb_set_add(codepoints, 195); + hb_set_add(codepoints, 196); + hb_set_add(codepoints, 197); + hb_set_add(codepoints, 198); + hb_set_add(codepoints, 199); + hb_set_add(codepoints, 200); + hb_set_add(codepoints, 201); + hb_set_add(codepoints, 202); + hb_set_add(codepoints, 203); + hb_set_add(codepoints, 204); + hb_set_add(codepoints, 205); + hb_set_add(codepoints, 206); + hb_set_add(codepoints, 207); + hb_set_add(codepoints, 208); + hb_set_add(codepoints, 209); + hb_set_add(codepoints, 210); + hb_set_add(codepoints, 211); + hb_set_add(codepoints, 212); + hb_set_add(codepoints, 213); + hb_set_add(codepoints, 214); + hb_set_add(codepoints, 215); + hb_set_add(codepoints, 216); + hb_set_add(codepoints, 217); + hb_set_add(codepoints, 218); + hb_set_add(codepoints, 220); + hb_set_add(codepoints, 221); + hb_set_add(codepoints, 223); + hb_set_add(codepoints, 224); + hb_set_add(codepoints, 225); + hb_set_add(codepoints, 226); + hb_set_add(codepoints, 227); + hb_set_add(codepoints, 228); + hb_set_add(codepoints, 229); + hb_set_add(codepoints, 230); + hb_set_add(codepoints, 231); + hb_set_add(codepoints, 232); + hb_set_add(codepoints, 233); + hb_set_add(codepoints, 234); + hb_set_add(codepoints, 235); + hb_set_add(codepoints, 236); + hb_set_add(codepoints, 237); + hb_set_add(codepoints, 238); + hb_set_add(codepoints, 239); + hb_set_add(codepoints, 240); + hb_set_add(codepoints, 241); + hb_set_add(codepoints, 242); + hb_set_add(codepoints, 243); + hb_set_add(codepoints, 244); + hb_set_add(codepoints, 245); + hb_set_add(codepoints, 246); + hb_set_add(codepoints, 247); + hb_set_add(codepoints, 248); + hb_set_add(codepoints, 249); + hb_set_add(codepoints, 250); + hb_set_add(codepoints, 251); + hb_set_add(codepoints, 252); + hb_set_add(codepoints, 253); + hb_set_add(codepoints, 257); + hb_set_add(codepoints, 258); + hb_set_add(codepoints, 259); + hb_set_add(codepoints, 260); + hb_set_add(codepoints, 261); + hb_set_add(codepoints, 262); + hb_set_add(codepoints, 263); + hb_set_add(codepoints, 268); + hb_set_add(codepoints, 269); + hb_set_add(codepoints, 270); + hb_set_add(codepoints, 271); + hb_set_add(codepoints, 272); + hb_set_add(codepoints, 273); + hb_set_add(codepoints, 275); + hb_set_add(codepoints, 279); + hb_set_add(codepoints, 280); + hb_set_add(codepoints, 281); + hb_set_add(codepoints, 282); + hb_set_add(codepoints, 283); + hb_set_add(codepoints, 286); + hb_set_add(codepoints, 287); + hb_set_add(codepoints, 297); + hb_set_add(codepoints, 299); + hb_set_add(codepoints, 301); + hb_set_add(codepoints, 302); + hb_set_add(codepoints, 303); + hb_set_add(codepoints, 304); + hb_set_add(codepoints, 305); + hb_set_add(codepoints, 314); + hb_set_add(codepoints, 316); + hb_set_add(codepoints, 317); + hb_set_add(codepoints, 318); + hb_set_add(codepoints, 321); + hb_set_add(codepoints, 322); + hb_set_add(codepoints, 323); + hb_set_add(codepoints, 324); + hb_set_add(codepoints, 326); + hb_set_add(codepoints, 328); + hb_set_add(codepoints, 333); + hb_set_add(codepoints, 336); + hb_set_add(codepoints, 337); + hb_set_add(codepoints, 338); + hb_set_add(codepoints, 339); + hb_set_add(codepoints, 341); + hb_set_add(codepoints, 344); + hb_set_add(codepoints, 345); + hb_set_add(codepoints, 346); + hb_set_add(codepoints, 347); + hb_set_add(codepoints, 350); + hb_set_add(codepoints, 351); + hb_set_add(codepoints, 352); + hb_set_add(codepoints, 353); + hb_set_add(codepoints, 355); + hb_set_add(codepoints, 356); + hb_set_add(codepoints, 357); + hb_set_add(codepoints, 361); + hb_set_add(codepoints, 363); + hb_set_add(codepoints, 366); + hb_set_add(codepoints, 367); + hb_set_add(codepoints, 369); + hb_set_add(codepoints, 371); + hb_set_add(codepoints, 377); + hb_set_add(codepoints, 378); + hb_set_add(codepoints, 379); + hb_set_add(codepoints, 380); + hb_set_add(codepoints, 381); + hb_set_add(codepoints, 382); + hb_set_add(codepoints, 402); + hb_set_add(codepoints, 416); + hb_set_add(codepoints, 417); + hb_set_add(codepoints, 431); + hb_set_add(codepoints, 432); + hb_set_add(codepoints, 536); + hb_set_add(codepoints, 537); + hb_set_add(codepoints, 538); + hb_set_add(codepoints, 539); + hb_set_add(codepoints, 601); + hb_set_add(codepoints, 606); + hb_set_add(codepoints, 614); + hb_set_add(codepoints, 642); + hb_set_add(codepoints, 666); + hb_set_add(codepoints, 699); + hb_set_add(codepoints, 730); + hb_set_add(codepoints, 768); + hb_set_add(codepoints, 769); + hb_set_add(codepoints, 777); + hb_set_add(codepoints, 803); + hb_set_add(codepoints, 902); + hb_set_add(codepoints, 904); + hb_set_add(codepoints, 905); + hb_set_add(codepoints, 908); + hb_set_add(codepoints, 912); + hb_set_add(codepoints, 913); + hb_set_add(codepoints, 914); + hb_set_add(codepoints, 915); + hb_set_add(codepoints, 916); + hb_set_add(codepoints, 917); + hb_set_add(codepoints, 918); + hb_set_add(codepoints, 919); + hb_set_add(codepoints, 920); + hb_set_add(codepoints, 921); + hb_set_add(codepoints, 922); + hb_set_add(codepoints, 923); + hb_set_add(codepoints, 924); + hb_set_add(codepoints, 925); + hb_set_add(codepoints, 926); + hb_set_add(codepoints, 927); + hb_set_add(codepoints, 928); + hb_set_add(codepoints, 929); + hb_set_add(codepoints, 931); + hb_set_add(codepoints, 932); + hb_set_add(codepoints, 933); + hb_set_add(codepoints, 934); + hb_set_add(codepoints, 935); + hb_set_add(codepoints, 936); + hb_set_add(codepoints, 937); + hb_set_add(codepoints, 940); + hb_set_add(codepoints, 941); + hb_set_add(codepoints, 942); + hb_set_add(codepoints, 943); + hb_set_add(codepoints, 945); + hb_set_add(codepoints, 946); + hb_set_add(codepoints, 947); + hb_set_add(codepoints, 948); + hb_set_add(codepoints, 949); + hb_set_add(codepoints, 950); + hb_set_add(codepoints, 951); + hb_set_add(codepoints, 952); + hb_set_add(codepoints, 953); + hb_set_add(codepoints, 954); + hb_set_add(codepoints, 955); + hb_set_add(codepoints, 956); + hb_set_add(codepoints, 957); + hb_set_add(codepoints, 958); + hb_set_add(codepoints, 959); + hb_set_add(codepoints, 960); + hb_set_add(codepoints, 961); + hb_set_add(codepoints, 962); + hb_set_add(codepoints, 963); + hb_set_add(codepoints, 964); + hb_set_add(codepoints, 965); + hb_set_add(codepoints, 966); + hb_set_add(codepoints, 967); + hb_set_add(codepoints, 968); + hb_set_add(codepoints, 969); + hb_set_add(codepoints, 970); + hb_set_add(codepoints, 972); + hb_set_add(codepoints, 973); + hb_set_add(codepoints, 974); + hb_set_add(codepoints, 1040); + hb_set_add(codepoints, 1041); + hb_set_add(codepoints, 1042); + hb_set_add(codepoints, 1043); + hb_set_add(codepoints, 1044); + hb_set_add(codepoints, 1045); + hb_set_add(codepoints, 1046); + hb_set_add(codepoints, 1047); + hb_set_add(codepoints, 1048); + hb_set_add(codepoints, 1049); + hb_set_add(codepoints, 1050); + hb_set_add(codepoints, 1051); + hb_set_add(codepoints, 1052); + hb_set_add(codepoints, 1053); + hb_set_add(codepoints, 1054); + hb_set_add(codepoints, 1055); + hb_set_add(codepoints, 1056); + hb_set_add(codepoints, 1057); + hb_set_add(codepoints, 1058); + hb_set_add(codepoints, 1059); + hb_set_add(codepoints, 1060); + hb_set_add(codepoints, 1061); + hb_set_add(codepoints, 1062); + hb_set_add(codepoints, 1063); + hb_set_add(codepoints, 1064); + hb_set_add(codepoints, 1067); + hb_set_add(codepoints, 1068); + hb_set_add(codepoints, 1075); + hb_set_add(codepoints, 1076); + hb_set_add(codepoints, 1077); + hb_set_add(codepoints, 1078); + hb_set_add(codepoints, 1079); + hb_set_add(codepoints, 1081); + hb_set_add(codepoints, 1082); + hb_set_add(codepoints, 1083); + hb_set_add(codepoints, 1084); + hb_set_add(codepoints, 1085); + hb_set_add(codepoints, 1086); + hb_set_add(codepoints, 1087); + hb_set_add(codepoints, 1088); + hb_set_add(codepoints, 1089); + + FontData subset; + if (subsetter.Subset(font, *codepoints, &subset) != StatusCode::kOk) { + printf("Subset call failed\n"); + hb_set_destroy(codepoints); + return -1; + } + + hb_set_destroy(codepoints); + return 0; +} diff --git a/patch_subset/glyf_transformer.h b/patch_subset/glyf_transformer.h new file mode 100644 index 0000000..a4784ec --- /dev/null +++ b/patch_subset/glyf_transformer.h @@ -0,0 +1,20 @@ +#ifndef PATCH_SUBSET_GLYF_TRANSFORMER_H_ +#define PATCH_SUBSET_GLYF_TRANSFORMER_H_ + +#include "common/status.h" +#include "patch_subset/font_data.h" + +namespace patch_subset { + +// Interface to an object which can apply transformations to the glyf +// and loca table. For example the WOFF2 glyf and loca transformation. +class GlyfTransformer { + public: + virtual ~GlyfTransformer() = default; + + virtual StatusCode Encode(FontData* font /* IN/OUT */) const = 0; +}; + +} // namespace patch_subset + +#endif // PATCH_SUBSET_GLYF_TRANSFORMER_H_ diff --git a/patch_subset/woff2_glyf_transformer.cc b/patch_subset/woff2_glyf_transformer.cc new file mode 100644 index 0000000..97ce27d --- /dev/null +++ b/patch_subset/woff2_glyf_transformer.cc @@ -0,0 +1,69 @@ +#include "patch_subset/woff2_glyf_transformer.h" + +#include "common/logging.h" +#include "common/status.h" +#include "font.h" +#include "hb.h" +#include "transform.h" + +using ::woff2::Font; + +namespace patch_subset { + +static const uint32_t kGlyfTableTag = 0x676c7966; +static const uint32_t kLocaTableTag = 0x6c6f6361; +static const uint32_t kTransformed = 0x80808080; + +void SerializeFont(const Font& font, FontData* font_data /* OUT */) { + hb_face_t* out = hb_face_builder_create(); + + for (const auto tag : font.OutputOrderedTags()) { + if (tag & kTransformed) { + continue; + } + const Font::Table* original = font.FindTable(tag); + const Font::Table* table_to_store = font.FindTable(tag ^ kTransformed); + if (table_to_store == nullptr) { + table_to_store = original; + } + + hb_blob_t* table_blob = hb_blob_create( + reinterpret_cast(table_to_store->data), + table_to_store->length, HB_MEMORY_MODE_READONLY, nullptr, nullptr); + hb_face_builder_add_table(out, tag, table_blob); + hb_blob_destroy(table_blob); + } + + hb_blob_t* result = hb_face_reference_blob(out); + hb_face_destroy(out); + + font_data->set(result); + hb_blob_destroy(result); +} + +StatusCode Woff2GlyfTransformer::Encode( + FontData* font_data /* IN/OUT */) const { + Font font; + if (!woff2::ReadFont(reinterpret_cast(font_data->data()), + font_data->size(), &font)) { + LOG(WARNING) << "Failed to parse font for glyf and loca transform."; + return StatusCode::kInternal; + } + + if (!woff2::TransformGlyfAndLocaTables(&font)) { + LOG(WARNING) << "Glyf and loca transformation failed."; + return StatusCode::kInternal; + } + + if (!font.FindTable(kGlyfTableTag ^ kTransformed) || + !font.FindTable(kLocaTableTag ^ kTransformed)) { + // No transformation was created (typically that means this is + // a CFF font). So no further work to do. + return StatusCode::kOk; + } + + SerializeFont(font, font_data); + return StatusCode::kOk; +} + +} // namespace patch_subset diff --git a/patch_subset/woff2_glyf_transformer.h b/patch_subset/woff2_glyf_transformer.h new file mode 100644 index 0000000..0df85f6 --- /dev/null +++ b/patch_subset/woff2_glyf_transformer.h @@ -0,0 +1,18 @@ +#ifndef PATCH_SUBSET_WOFF2_GLYF_TRANSFORMER_H_ +#define PATCH_SUBSET_WOFF2_GLYF_TRANSFORMER_H_ + +#include "common/status.h" +#include "patch_subset/font_data.h" +#include "patch_subset/glyf_transformer.h" + +namespace patch_subset { + +// Applies the woff2 glyf and loca transformations to a font. +class Woff2GlyfTransformer : public GlyfTransformer { + public: + StatusCode Encode(FontData* font /* IN/OUT */) const override; +}; + +} // namespace patch_subset + +#endif // PATCH_SUBSET_WOFF2_GLYF_TRANSFORMER_H_ diff --git a/patch_subset/woff2_glyf_transformer_test.cc b/patch_subset/woff2_glyf_transformer_test.cc new file mode 100644 index 0000000..ab3c8b4 --- /dev/null +++ b/patch_subset/woff2_glyf_transformer_test.cc @@ -0,0 +1,59 @@ +#include "patch_subset/woff2_glyf_transformer.h" + +#include "common/status.h" +#include "font.h" +#include "gtest/gtest.h" +#include "patch_subset/file_font_provider.h" +#include "patch_subset/font_provider.h" + +namespace patch_subset { + +static const uint32_t kGlyfTableTag = 0x676c7966; +static const uint32_t kLocaTableTag = 0x6c6f6361; + +class Woff2GlyfTransformerTest : public ::testing::Test { + protected: + Woff2GlyfTransformerTest() + : font_provider_(new FileFontProvider("patch_subset/testdata/")) {} + + ~Woff2GlyfTransformerTest() override {} + + void SetUp() override { + ASSERT_EQ(font_provider_->GetFont("Roboto-Regular.Awesome.ttf", &a_font_), + StatusCode::kOk); + ASSERT_GT(a_font_.size(), 0); + } + + std::unique_ptr font_provider_; + Woff2GlyfTransformer transformer_; + FontData a_font_; +}; + +TEST_F(Woff2GlyfTransformerTest, TransformModifiesGlyf) { + ::woff2::Font orig_font; + ASSERT_TRUE(woff2::ReadFont(reinterpret_cast(a_font_.data()), + a_font_.size(), &orig_font)); + + ASSERT_EQ(transformer_.Encode(&a_font_), StatusCode::kOk); + + ::woff2::Font transformed_font; + ASSERT_TRUE(woff2::ReadFont(reinterpret_cast(a_font_.data()), + a_font_.size(), &transformed_font)); + + // Some basic sanity checks on the transformation: + EXPECT_EQ(orig_font.tables.size(), transformed_font.tables.size()); + EXPECT_TRUE(orig_font.FindTable(kGlyfTableTag)); + EXPECT_TRUE(orig_font.FindTable(kLocaTableTag)); + EXPECT_TRUE(transformed_font.FindTable(kGlyfTableTag)); + EXPECT_TRUE(transformed_font.FindTable(kLocaTableTag)); + + EXPECT_GT(orig_font.FindTable(kLocaTableTag)->length, 0); + EXPECT_EQ(transformed_font.FindTable(kLocaTableTag)->length, 0); + + EXPECT_GT(orig_font.FindTable(kGlyfTableTag)->length, 0); + EXPECT_GT(transformed_font.FindTable(kGlyfTableTag)->length, 0); + EXPECT_NE(orig_font.FindTable(kGlyfTableTag)->length, + transformed_font.FindTable(kGlyfTableTag)->length); +} + +} // namespace patch_subset diff --git a/third_party/woff2.BUILD b/third_party/woff2.BUILD index d99bebd..d10b183 100644 --- a/third_party/woff2.BUILD +++ b/third_party/woff2.BUILD @@ -19,6 +19,8 @@ cc_library( "include/woff2/decode.h", "include/woff2/encode.h", "include/woff2/output.h", + "src/transform.h", + "src/font.h", ], copts = [ "-Wno-unused-variable", @@ -27,6 +29,7 @@ cc_library( ], includes = [ "include/", + "src/", ], visibility = ["//visibility:public"], deps = [ From 3f192195c7268d6d3c3b59e0c1795cdc44eac86a Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Wed, 28 Oct 2020 11:54:57 -0700 Subject: [PATCH 2/3] Add glyf/loca transformation support to the patch subset server. --- patch_subset/BUILD | 2 ++ .../client_server_integration_test.cc | 7 ++-- patch_subset/mock_glyf_transformer.h | 25 +++++++++++++++ patch_subset/noop_glyf_transformer.h | 20 ++++++++++++ patch_subset/patch_subset_server_impl.cc | 32 ++++++++++++++++--- patch_subset/patch_subset_server_impl.h | 29 +++++++++++++++-- patch_subset/patch_subset_server_impl_test.cc | 31 +++++++++++++++--- patch_subset/server_integration_test.cc | 4 ++- 8 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 patch_subset/mock_glyf_transformer.h create mode 100644 patch_subset/noop_glyf_transformer.h diff --git a/patch_subset/BUILD b/patch_subset/BUILD index a4bf2cc..ee7c2cb 100644 --- a/patch_subset/BUILD +++ b/patch_subset/BUILD @@ -22,6 +22,7 @@ cc_library( "frequency_codepoint_predictor.cc", "harfbuzz_subsetter.cc", "noop_codepoint_predictor.h", + "noop_glyf_transformer.h", "patch_subset_server_impl.cc", "simple_codepoint_mapper.cc", "woff2_glyf_transformer.cc", @@ -150,6 +151,7 @@ cc_test( "mock_codepoint_mapping_checksum.h", "mock_codepoint_predictor.h", "mock_font_provider.h", + "mock_glyf_transformer.h", "mock_hasher.h", "mock_patch_subset_server.h", "patch_subset_client_test.cc", diff --git a/patch_subset/client_server_integration_test.cc b/patch_subset/client_server_integration_test.cc index 384b50b..905802f 100644 --- a/patch_subset/client_server_integration_test.cc +++ b/patch_subset/client_server_integration_test.cc @@ -10,6 +10,7 @@ #include "patch_subset/harfbuzz_subsetter.h" #include "patch_subset/hb_set_unique_ptr.h" #include "patch_subset/noop_codepoint_predictor.h" +#include "patch_subset/noop_glyf_transformer.h" #include "patch_subset/null_request_logger.h" #include "patch_subset/patch_subset.pb.h" #include "patch_subset/patch_subset_client.h" @@ -35,7 +36,8 @@ class PatchSubsetClientServerIntegrationTest : public ::testing::Test { std::unique_ptr(new FarmHasher()), std::unique_ptr(nullptr), std::unique_ptr(nullptr), - std::unique_ptr(new NoopCodepointPredictor())), + std::unique_ptr(new NoopCodepointPredictor()), + std::unique_ptr(new NoopGlyfTransformer())), client_(&server_, &request_logger_, std::unique_ptr(new BrotliBinaryPatch()), std::unique_ptr(new FarmHasher())), @@ -49,7 +51,8 @@ class PatchSubsetClientServerIntegrationTest : public ::testing::Test { std::unique_ptr(new SimpleCodepointMapper()), std::unique_ptr( new CodepointMappingChecksumImpl(hasher_.get())), - std::unique_ptr(new NoopCodepointPredictor())), + std::unique_ptr(new NoopCodepointPredictor()), + std::unique_ptr(new NoopGlyfTransformer())), client_with_mapping_( &server_with_mapping_, &request_logger_, std::unique_ptr(new BrotliBinaryPatch()), diff --git a/patch_subset/mock_glyf_transformer.h b/patch_subset/mock_glyf_transformer.h new file mode 100644 index 0000000..f3dc58c --- /dev/null +++ b/patch_subset/mock_glyf_transformer.h @@ -0,0 +1,25 @@ +#ifndef PATCH_SUBSET_MOCK_GLYF_TRANSFORMER_H_ +#define PATCH_SUBSET_MOCK_GLYF_TRANSFORMER_H_ + +#include "common/status.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "patch_subset/font_data.h" +#include "patch_subset/glyf_transformer.h" + +namespace patch_subset { + +// Interface to an object which can apply transformations to the glyf +// and loca table. For example the WOFF2 glyf and loca transformation. +class MockGlyfTransformer : public GlyfTransformer { + public: + + MOCK_METHOD(StatusCode, + Encode, + (FontData* font /* IN/OUT */), + (const override)); +}; + +} // namespace patch_subset + +#endif // PATCH_SUBSET_MOCK_GLYF_TRANSFORMER_H_ diff --git a/patch_subset/noop_glyf_transformer.h b/patch_subset/noop_glyf_transformer.h new file mode 100644 index 0000000..1561149 --- /dev/null +++ b/patch_subset/noop_glyf_transformer.h @@ -0,0 +1,20 @@ +#ifndef PATCH_SUBSET_NOOP_GLYF_TRANSFORMER_H_ +#define PATCH_SUBSET_NOOP_GLYF_TRANSFORMER_H_ + +#include "common/status.h" +#include "patch_subset/font_data.h" +#include "patch_subset/glyf_transformer.h" + +namespace patch_subset { + +// Applies no glyf transformation. +class NoopGlyfTransformer : public GlyfTransformer { + public: + StatusCode Encode(FontData* font /* IN/OUT */) const override { + return StatusCode::kOk; + } +}; + +} // namespace patch_subset + +#endif // PATCH_SUBSET_NOOP_GLYF_TRANSFORMER_H_ diff --git a/patch_subset/patch_subset_server_impl.cc b/patch_subset/patch_subset_server_impl.cc index e217753..9849c53 100644 --- a/patch_subset/patch_subset_server_impl.cc +++ b/patch_subset/patch_subset_server_impl.cc @@ -175,19 +175,41 @@ void PatchSubsetServerImpl::AddPredictedCodepoints(RequestState* state) const { StatusCode PatchSubsetServerImpl::ComputeSubsets(const std::string& font_id, RequestState* state) const { + StatusCode result = ComputeSubset(font_id, + state->font_data, + *state->codepoints_have, + &state->client_subset); + if (result != StatusCode::kOk) { + return result; + } + + result = ComputeSubset(font_id, + state->font_data, + *state->codepoints_needed, + &state->client_target_subset); + if (result != StatusCode::kOk) { + return result; + } + + return result; +} + +StatusCode PatchSubsetServerImpl::ComputeSubset(const std::string& font_id, + const FontData& base_font, + const hb_set_t& codepoints, + FontData* output) const { StatusCode result = subsetter_->Subset( - state->font_data, *state->codepoints_have, &state->client_subset); + base_font, codepoints, output); if (result != StatusCode::kOk) { - LOG(WARNING) << "Subsetting for client_subset " + LOG(WARNING) << "Subsetting for " << "(font_id = " << font_id << ")" << "failed."; return result; } - result = subsetter_->Subset(state->font_data, *state->codepoints_needed, - &state->client_target_subset); + result = glyf_transformer_->Encode(output); if (result != StatusCode::kOk) { - LOG(WARNING) << "Subsetting for client_target_subset " + LOG(WARNING) << "Glyf transformation for " << "(font_id = " << font_id << ")" << "failed."; return result; diff --git a/patch_subset/patch_subset_server_impl.h b/patch_subset/patch_subset_server_impl.h index 4dcea15..f51b440 100644 --- a/patch_subset/patch_subset_server_impl.h +++ b/patch_subset/patch_subset_server_impl.h @@ -19,10 +19,12 @@ #include "patch_subset/harfbuzz_subsetter.h" #include "patch_subset/hasher.h" #include "patch_subset/noop_codepoint_predictor.h" +#include "patch_subset/noop_glyf_transformer.h" #include "patch_subset/patch_subset.pb.h" #include "patch_subset/patch_subset_server.h" #include "patch_subset/simple_codepoint_mapper.h" #include "patch_subset/subsetter.h" +#include "patch_subset/woff2_glyf_transformer.h" namespace patch_subset { @@ -44,6 +46,16 @@ class ServerConfig { // remap codepoints bool remap_codepoints = false; + // Apply woff2 glyf transform + bool woff2_glyf_transform = false; + + GlyfTransformer* CreateGlyfTransformer() const { + if (woff2_glyf_transform) { + return new Woff2GlyfTransformer(); + } + return new NoopGlyfTransformer(); + } + CodepointMapper* CreateCodepointMapper() const { if (remap_codepoints) { return new SimpleCodepointMapper(); @@ -93,7 +105,9 @@ class PatchSubsetServerImpl : public PatchSubsetServer { std::unique_ptr( config.CreateMappingChecksum(hasher)), std::unique_ptr( - config.CreateCodepointPredictor()))); + config.CreateCodepointPredictor()), + std::unique_ptr( + config.CreateGlyfTransformer()))); } // Takes ownership of font_provider, subsetter, and binary_diff. @@ -103,7 +117,8 @@ class PatchSubsetServerImpl : public PatchSubsetServer { std::unique_ptr binary_diff, std::unique_ptr hasher, std::unique_ptr codepoint_mapper, std::unique_ptr codepoint_mapping_checksum, - std::unique_ptr codepoint_predictor) + std::unique_ptr codepoint_predictor, + std::unique_ptr glyf_transformer) : max_predicted_codepoints_(max_predicted_codepoints), font_provider_(std::move(font_provider)), subsetter_(std::move(subsetter)), @@ -111,7 +126,9 @@ class PatchSubsetServerImpl : public PatchSubsetServer { hasher_(std::move(hasher)), codepoint_mapper_(std::move(codepoint_mapper)), codepoint_mapping_checksum_(std::move(codepoint_mapping_checksum)), - codepoint_predictor_(std::move(codepoint_predictor)) {} + codepoint_predictor_(std::move(codepoint_predictor)), + glyf_transformer_(std::move(glyf_transformer)) + {} // Handle a patch request from a client. Writes the resulting response // into response. @@ -136,6 +153,11 @@ class PatchSubsetServerImpl : public PatchSubsetServer { StatusCode ComputeSubsets(const std::string& font_id, RequestState* state) const; + StatusCode ComputeSubset(const std::string& font_id, + const FontData& base_font, + const hb_set_t& codepoints, + FontData* output) const; + void ValidatePatchBase(uint64_t base_fingerprint, RequestState* state) const; void ConstructResponse(const RequestState& state, @@ -161,6 +183,7 @@ class PatchSubsetServerImpl : public PatchSubsetServer { std::unique_ptr codepoint_mapper_; std::unique_ptr codepoint_mapping_checksum_; std::unique_ptr codepoint_predictor_; + std::unique_ptr glyf_transformer_; }; } // namespace patch_subset diff --git a/patch_subset/patch_subset_server_impl_test.cc b/patch_subset/patch_subset_server_impl_test.cc index 4232b99..66d6e3f 100644 --- a/patch_subset/patch_subset_server_impl_test.cc +++ b/patch_subset/patch_subset_server_impl_test.cc @@ -18,6 +18,7 @@ #include "patch_subset/mock_font_provider.h" #include "patch_subset/mock_hasher.h" #include "patch_subset/simple_codepoint_mapper.h" +#include "patch_subset/mock_glyf_transformer.h" using ::absl::string_view; using ::google::protobuf::util::MessageDifferencer; @@ -39,6 +40,17 @@ StatusCode returnFontId(const std::string& id, FontData* out) { return StatusCode::kOk; } +StatusCode transform(FontData* out) { + if (!out->size()) { + return StatusCode::kOk; + } + + std::string result(out->data(), out->size()); + result.append(":transformed"); + out->copy(result); + return StatusCode::kOk; +} + StatusCode diff(const FontData& font_base, const FontData& font_derived, FontData* out /* OUT */) { if (font_base.empty()) { @@ -60,6 +72,7 @@ class PatchSubsetServerImplTestBase : public ::testing::Test { binary_diff_(new MockBinaryDiff()), hasher_(new MockHasher()), codepoint_predictor_(new MockCodepointPredictor()), + glyf_transformer_(new MockGlyfTransformer()), set_abcd_(make_hb_set_from_ranges(1, 0x61, 0x64)), set_ab_(make_hb_set_from_ranges(1, 0x61, 0x62)) {} @@ -79,6 +92,12 @@ class PatchSubsetServerImplTestBase : public ::testing::Test { EXPECT_CALL(*hasher_, Checksum(value)).WillRepeatedly(Return(checksum)); } + void DoTransform() { + EXPECT_CALL(*glyf_transformer_, Encode(_)) + .Times(2) + .WillRepeatedly(Invoke(transform)); + } + void AddPredictedCodepoints(const hb_set_t* font_codepoints, const hb_set_t* have_codepoints, const hb_set_t* requested_codepoints, @@ -94,6 +113,7 @@ class PatchSubsetServerImplTestBase : public ::testing::Test { MockBinaryDiff* binary_diff_; MockHasher* hasher_; MockCodepointPredictor* codepoint_predictor_; + MockGlyfTransformer* glyf_transformer_; hb_set_unique_ptr set_abcd_; hb_set_unique_ptr set_ab_; }; @@ -107,7 +127,8 @@ class PatchSubsetServerImplTest : public PatchSubsetServerImplTestBase { std::unique_ptr(hasher_), std::unique_ptr(nullptr), std::unique_ptr(nullptr), - std::unique_ptr(codepoint_predictor_)) {} + std::unique_ptr(codepoint_predictor_), + std::unique_ptr(glyf_transformer_)) {} PatchSubsetServerImpl server_; }; @@ -124,7 +145,8 @@ class PatchSubsetServerImplWithCodepointRemappingTest std::unique_ptr(new SimpleCodepointMapper()), std::unique_ptr( codepoint_mapping_checksum_), - std::unique_ptr(codepoint_predictor_)), + std::unique_ptr(codepoint_predictor_), + std::unique_ptr(glyf_transformer_)), set_abcd_encoded_(make_hb_set_from_ranges(1, 0, 3)), set_ab_encoded_(make_hb_set_from_ranges(1, 0, 1)) {} @@ -152,9 +174,10 @@ class PatchSubsetServerImplWithCodepointRemappingTest TEST_F(PatchSubsetServerImplTest, NewRequest) { ExpectRoboto(); ExpectDiff(); + DoTransform(); ExpectChecksum("Roboto-Regular.ttf", 42); - ExpectChecksum("Roboto-Regular.ttf:abcd", 43); + ExpectChecksum("Roboto-Regular.ttf:abcd:transformed", 43); PatchRequestProto request; PatchResponseProto response; @@ -164,7 +187,7 @@ TEST_F(PatchSubsetServerImplTest, NewRequest) { StatusCode::kOk); EXPECT_EQ(response.original_font_fingerprint(), 42); EXPECT_EQ(response.type(), ResponseType::REBASE); - EXPECT_EQ(response.patch().patch(), "Roboto-Regular.ttf:abcd"); + EXPECT_EQ(response.patch().patch(), "Roboto-Regular.ttf:abcd:transformed"); EXPECT_EQ(response.patch().patched_fingerprint(), 43); EXPECT_EQ(response.patch().format(), PatchFormat::BROTLI_SHARED_DICT); diff --git a/patch_subset/server_integration_test.cc b/patch_subset/server_integration_test.cc index 6d4f59c..2ca123f 100644 --- a/patch_subset/server_integration_test.cc +++ b/patch_subset/server_integration_test.cc @@ -9,6 +9,7 @@ #include "patch_subset/harfbuzz_subsetter.h" #include "patch_subset/hb_set_unique_ptr.h" #include "patch_subset/noop_codepoint_predictor.h" +#include "patch_subset/noop_glyf_transformer.h" #include "patch_subset/patch_subset_server_impl.h" using ::absl::string_view; @@ -27,7 +28,8 @@ class PatchSubsetServerIntegrationTest : public ::testing::Test { std::unique_ptr(new FarmHasher()), std::unique_ptr(nullptr), std::unique_ptr(nullptr), - std::unique_ptr(new NoopCodepointPredictor())) { + std::unique_ptr(new NoopCodepointPredictor()), + std::unique_ptr(new NoopGlyfTransformer())) { font_provider_->GetFont("Roboto-Regular.abcd.ttf", &roboto_abcd_); font_provider_->GetFont("Roboto-Regular.ab.ttf", &roboto_ab_); From 147ef44b076edf616c506be2290bedb42fe9490a Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Wed, 28 Oct 2020 14:44:31 -0700 Subject: [PATCH 3/3] Handle empty subsets in the glyf transformer. --- patch_subset/woff2_glyf_transformer.cc | 6 ++++++ patch_subset/woff2_glyf_transformer_test.cc | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/patch_subset/woff2_glyf_transformer.cc b/patch_subset/woff2_glyf_transformer.cc index 97ce27d..ebb2aef 100644 --- a/patch_subset/woff2_glyf_transformer.cc +++ b/patch_subset/woff2_glyf_transformer.cc @@ -43,6 +43,12 @@ void SerializeFont(const Font& font, FontData* font_data /* OUT */) { StatusCode Woff2GlyfTransformer::Encode( FontData* font_data /* IN/OUT */) const { + if (!font_data->size()) { + // May be called on an empty font (ie. when patching against nothing) + // no action needed in this case. + return StatusCode::kOk; + } + Font font; if (!woff2::ReadFont(reinterpret_cast(font_data->data()), font_data->size(), &font)) { diff --git a/patch_subset/woff2_glyf_transformer_test.cc b/patch_subset/woff2_glyf_transformer_test.cc index ab3c8b4..1bfbd3a 100644 --- a/patch_subset/woff2_glyf_transformer_test.cc +++ b/patch_subset/woff2_glyf_transformer_test.cc @@ -56,4 +56,10 @@ TEST_F(Woff2GlyfTransformerTest, TransformModifiesGlyf) { transformed_font.FindTable(kGlyfTableTag)->length); } +TEST_F(Woff2GlyfTransformerTest, TransformEmptyFont) { + FontData empty_font; + ASSERT_EQ(transformer_.Encode(&empty_font), StatusCode::kOk); + EXPECT_EQ(empty_font.size(), 0); +} + } // namespace patch_subset