// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #include "hb_wrappers.h" hb_codepoint_t ParseCodepoint(std::string_view arg, bool& optional) { constexpr std::string_view kOptionalPrefix = "optional:"; if (arg.substr(0, kOptionalPrefix.length()) == kOptionalPrefix) { optional = true; arg = arg.substr(kOptionalPrefix.length()); } else { optional = false; } uint64_t value = 0; // Check for \u123, u123, otherwise let strtoul work it out. if (arg[0] == 'u') { value = strtoul(arg.data() + 1, nullptr, 16); } else if (arg[0] == '\\' && arg[1] == 'u') { value = strtoul(arg.data() + 2, nullptr, 16); } else { value = strtoul(arg.data(), nullptr, 0); } if (value == 0 || value > std::numeric_limits::max()) { std::cerr << "The value '" << arg << "' (" << value << ") could not be parsed as a valid unicode codepoint; aborting." << std::endl; exit(-1); } return value; } void Usage() { std::cout << "Usage:" << std::endl; std::cout << "font-subset " << std::endl; std::cout << std::endl; std::cout << "The output.ttf file will be overwritten if it exists already " "and the subsetting operation succeeds." << std::endl; std::cout << "Codepoints should be specified on stdin, separated by spaces, " "and must be input as decimal numbers (123), hexadecimal " "numbers (0x7B), or unicode hexadecimal characters (\\u7B)." << std::endl; std::cout << "Codepoints can be prefixed by the string \"optional:\" to " "specify that the codepoint can be omitted if it isn't found " "in the input font file." << std::endl; std::cout << "Input terminates with a newline." << std::endl; std::cout << "This program will de-duplicate codepoints if the same codepoint is " "specified multiple times, e.g. '123 123' will be treated as '123'." << std::endl; } template using void_t = void; template struct HarfBuzzSubset { // This is the HarfBuzz 3.0 interface. static HarfbuzzWrappers::HbFacePtr Make(hb_face_t* face, T input) { // The prior version of harfbuzz automatically dropped layout tables, // but in the new version they are kept by default. So re-add them to the // drop list to retain the same behaviour. if (!hb_ot_var_has_data(face) || hb_ot_var_get_axis_count(face) == 0) { // we can only drop GSUB/GPOS/GDEF for non variable fonts, they may be // needed for variable fonts (guessing we need to keep all of these, but // in Material Symbols Icon variable fonts if we drop the GSUB table (they // do not have GPOS/DEF) then the Fill=1,Weight=100 variation is rendered // incorrect. (and other variations are probably less noticibly // incorrect)) hb_set_add(hb_subset_input_set(input, HB_SUBSET_SETS_DROP_TABLE_TAG), HB_TAG('G', 'S', 'U', 'B')); hb_set_add(hb_subset_input_set(input, HB_SUBSET_SETS_DROP_TABLE_TAG), HB_TAG('G', 'P', 'O', 'S')); hb_set_add(hb_subset_input_set(input, HB_SUBSET_SETS_DROP_TABLE_TAG), HB_TAG('G', 'D', 'E', 'F')); } return HarfbuzzWrappers::HbFacePtr(hb_subset_or_fail(face, input)); } }; int main(int argc, char** argv) { if (argc != 3) { Usage(); return -1; } std::string output_file_path(argv[1]); std::string input_file_path(argv[2]); std::cout << "Using output file: " << output_file_path << std::endl; std::cout << "Using source file: " << input_file_path << std::endl; HarfbuzzWrappers::HbBlobPtr font_blob( hb_blob_create_from_file(input_file_path.c_str())); if (!hb_blob_get_length(font_blob.get())) { std::cerr << "Failed to load input font " << input_file_path << "; aborting. This error indicates that the font is invalid or " "the current version of Harfbuzz is unable to process it." << std::endl; return -1; } HarfbuzzWrappers::HbFacePtr font_face(hb_face_create(font_blob.get(), 0)); if (font_face.get() == hb_face_get_empty()) { std::cerr << "Failed to load input font face " << input_file_path << "; aborting. This error indicates that the font is invalid or " "the current version of Harfbuzz is unable to process it." << std::endl; return -1; } HarfbuzzWrappers::HbSubsetInputPtr input(hb_subset_input_create_or_fail()); { hb_set_t* desired_codepoints = hb_subset_input_unicode_set(input.get()); HarfbuzzWrappers::HbSetPtr actual_codepoints(hb_set_create()); hb_face_collect_unicodes(font_face.get(), actual_codepoints.get()); std::string raw_codepoint; while (std::cin >> raw_codepoint) { bool optional = false; auto codepoint = ParseCodepoint(std::string_view(raw_codepoint), optional); if (!codepoint) { std::cerr << "Invalid codepoint for " << raw_codepoint << "; exiting." << std::endl; return -1; } if (!hb_set_has(actual_codepoints.get(), codepoint)) { if (optional) { // Code point is optional, so omit it. continue; } std::cerr << "Codepoint " << raw_codepoint << " not found in font, aborting." << std::endl; return -1; } hb_set_add(desired_codepoints, codepoint); } if (hb_set_is_empty(desired_codepoints)) { std::cerr << "No codepoints specified, exiting." << std::endl; return -1; } } HarfbuzzWrappers::HbFacePtr new_face = HarfBuzzSubset::Make(font_face.get(), input.get()); if (!new_face || new_face.get() == hb_face_get_empty()) { std::cerr << "Failed to subset font; aborting. This error normally indicates " "the current version of Harfbuzz is unable to process it." << std::endl; return -1; } HarfbuzzWrappers::HbBlobPtr result(hb_face_reference_blob(new_face.get())); if (!hb_blob_get_length(result.get())) { std::cerr << "Failed get new font bytes; aborting. This error may indicate " "low availability of memory or a bug in Harfbuzz." << std::endl; return -1; } unsigned int data_length; const char* data = hb_blob_get_data(result.get(), &data_length); std::ofstream output_font_file; output_font_file.open(output_file_path, std::ios::out | std::ios::trunc | std::ios::binary); if (!output_font_file.is_open()) { std::cerr << "Failed to open output file '" << output_file_path << "'. The parent directory may not exist, or the user does not " "have permission to create this file." << std::endl; return -1; } output_font_file.write(data, data_length); output_font_file.flush(); output_font_file.close(); std::cout << "Wrote " << data_length << " bytes to " << output_file_path << std::endl; return 0; }