From 563ea5a052d0ec179561d8febfa45579fc322360 Mon Sep 17 00:00:00 2001 From: Oliver Wipfli Date: Thu, 26 Dec 2024 15:09:12 +0100 Subject: [PATCH] Use HarfBuzz to get top-to-bottom glyph variants --- CMakeLists.txt | 5 ++- CONTRIBUTING.md | 6 --- Dockerfile | 7 +++ main.cpp | 113 ++++++++++++++++++++++++++++++++++++++---------- run.sh | 2 + 5 files changed, 102 insertions(+), 31 deletions(-) create mode 100644 Dockerfile create mode 100755 run.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index eb66bfd..ea020fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,8 @@ set(CMAKE_CXX_FLAGS "-Wno-c++11-narrowing") find_package(Boost 1.73 REQUIRED) find_package(Freetype REQUIRED) +find_package(PkgConfig REQUIRED) +pkg_check_modules(HARFBUZZ REQUIRED harfbuzz) include_directories(vendor/cxxopts/include) include_directories(vendor/filesystem/include) @@ -15,8 +17,9 @@ include_directories(vendor/sdf-glyph-foundry/include) include_directories(vendor/protozero/include) include_directories(${Boost_INCLUDE_DIRS}) include_directories(${FREETYPE_INCLUDE_DIRS}) +include_directories(${HARFBUZZ_INCLUDE_DIRS}) add_executable(font-maker main.cpp) -target_link_libraries(font-maker ${FREETYPE_LIBRARIES}) +target_link_libraries(font-maker ${FREETYPE_LIBRARIES} ${HARFBUZZ_LIBRARIES}) set_property(TARGET font-maker PROPERTY CXX_STANDARD 17) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7012583..7533574 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,10 +23,4 @@ Use `./build_wasm.sh PATH_TO_INCLUDE_DIR` to build the WASM output, where `PATH_ ``` cmake . make -``` - -# Running command line - -``` -./font-maker --name "Noto Sans" output File1.ttf File2.ttf ``` \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..04cc71d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM ubuntu:22.04 + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y libboost-all-dev cmake clang libfreetype6-dev pkg-config g++ libharfbuzz-dev git && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /root diff --git a/main.cpp b/main.cpp index 43cce79..3beb6dc 100644 --- a/main.cpp +++ b/main.cpp @@ -13,24 +13,76 @@ #include "cxxopts.hpp" #endif #include +#include +#include using namespace std; -void do_codepoint(protozero::pbf_writer &parent, std::vector &faces, FT_ULong char_code) { - for (auto const &face : faces) { - FT_UInt char_index = FT_Get_Char_Index(face, char_code); - if (char_index > 0) { +FT_UInt get_glyph_index(hb_font_t *hb_font, hb_buffer_t *buffer, FT_ULong char_code, bool top_to_bottom) { + FT_UInt result = 0; + uint32_t utf32_char = static_cast(char_code); + hb_buffer_clear_contents(buffer); + hb_buffer_add_utf32(buffer, &utf32_char, 1, 0, 1); + if (top_to_bottom) { + hb_buffer_set_direction(buffer, HB_DIRECTION_TTB); + } + hb_buffer_guess_segment_properties(buffer); + hb_shape(hb_font, buffer, nullptr, 0); + unsigned int glyph_count; + hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(buffer, &glyph_count); + if (glyph_count > 0) { + result = glyph_info[0].codepoint; + } + return result; +} + + +FT_UInt get_glyph_index(const std::string &font_path, FT_ULong char_code, bool top_to_bottom) { + FT_UInt result = 0; + FT_Library ft_library; + if (FT_Init_FreeType(&ft_library)) { + std::cerr << "Failed to initialize FreeType library." << std::endl; + return result; + } + FT_Face face; + if (FT_New_Face(ft_library, font_path.c_str(), 0, &face)) { + std::cerr << "Failed to load font: " << font_path << std::endl; + FT_Done_FreeType(ft_library); + return result; + } + hb_font_t *hb_font = hb_ft_font_create(face, nullptr); + hb_buffer_t *buffer = hb_buffer_create(); + + result = get_glyph_index(hb_font, buffer, char_code, top_to_bottom); + + hb_buffer_destroy(buffer); + hb_font_destroy(hb_font); + FT_Done_Face(face); + FT_Done_FreeType(ft_library); + return result; +} + + + +void do_codepoint(protozero::pbf_writer &parent, std::vector hb_fonts, hb_buffer_t *hb_buffer, FT_ULong char_code) { + + for (auto hb_font : hb_fonts) { + bool top_to_bottom = true; + FT_UInt glyph_index = get_glyph_index(hb_font, hb_buffer, char_code, top_to_bottom); + // FT_UInt char_index = 0; //get_glyph_index(face, char_code, false); + FT_Face face = hb_ft_font_get_face(hb_font); + if (glyph_index > 0) { sdf_glyph_foundry::glyph_info glyph; - glyph.glyph_index = char_index; + glyph.glyph_index = glyph_index; sdf_glyph_foundry::RenderSDF(glyph, 24, 3, 0.25, face); string glyph_data; protozero::pbf_writer glyph_message{glyph_data}; // direct type conversions, no need for checking or casting - glyph_message.add_uint32(3,glyph.width); - glyph_message.add_uint32(4,glyph.height); - glyph_message.add_sint32(5,glyph.left); + glyph_message.add_uint32(3, glyph.width); + glyph_message.add_uint32(4, glyph.height); + glyph_message.add_sint32(5, glyph.left); // conversions requiring checks, for safety and correctness @@ -38,7 +90,7 @@ void do_codepoint(protozero::pbf_writer &parent, std::vector &faces, FT if (char_code > numeric_limits::max()) { throw runtime_error("Invalid value for char_code: too large"); } else { - glyph_message.add_uint32(1,static_cast(char_code)); + glyph_message.add_uint32(1, static_cast(char_code)); } // node-fontnik uses glyph.top - glyph.ascender, assuming that the baseline @@ -48,36 +100,36 @@ void do_codepoint(protozero::pbf_writer &parent, std::vector &faces, FT if (top < numeric_limits::min() || top > numeric_limits::max()) { throw runtime_error("Invalid value for glyph.top-25"); } else { - glyph_message.add_sint32(6,top); + glyph_message.add_sint32(6, top); } // double to uint if (glyph.advance < numeric_limits::min() || glyph.advance > numeric_limits::max()) { throw runtime_error("Invalid value for glyph.top-glyph.ascender"); } else { - glyph_message.add_uint32(7,static_cast(glyph.advance)); + glyph_message.add_uint32(7, static_cast(glyph.advance)); } if (glyph.width > 0) { - glyph_message.add_bytes(2,glyph.bitmap); + glyph_message.add_bytes(2, glyph.bitmap); } - parent.add_message(3,glyph_data); + parent.add_message(3, glyph_data); return; } } } -string do_range(std::vector &faces, std::string name, unsigned start, unsigned end) { +string do_range(std::vector hb_fonts, hb_buffer_t *hb_buffer, std::string name, unsigned start, unsigned end) { string fontstack_data; { protozero::pbf_writer fontstack{fontstack_data}; - fontstack.add_string(1,name); - fontstack.add_string(2,to_string(start) + "-" + to_string(end)); + fontstack.add_string(1, name); + fontstack.add_string(2, to_string(start) + "-" + to_string(end)); for (unsigned x = start; x <= end; x++) { FT_ULong char_code = x; - do_codepoint(fontstack,faces, x); + do_codepoint(fontstack, hb_fonts, hb_buffer, x); } } @@ -92,6 +144,8 @@ string do_range(std::vector &faces, std::string name, unsigned start, u struct fontstack { FT_Library library; std::vector *faces; + std::vector *hb_fonts; + hb_buffer_t *hb_buffer; std::vector *data; std::set *seen_face_names; std::string *name; @@ -107,6 +161,8 @@ extern "C" { fontstack *create_fontstack(const char *name) { fontstack *f = (fontstack *)malloc(sizeof(fontstack)); f->faces = new std::vector; + f->hb_fonts = new std::vector; + f->hb_buffer = hb_buffer_create(); f->data = new std::vector; f->seen_face_names = new std::set; @@ -141,6 +197,7 @@ extern "C" { double size = 24 * scale_factor; FT_Set_Char_Size(face, 0, static_cast(size * (1 << 6)), 0, 0); f->faces->push_back(face); + f->hb_fonts->push_back(hb_ft_font_create(face, nullptr)); if (f->auto_name) { std::string combined_name = std::string(face->family_name); @@ -159,6 +216,11 @@ extern "C" { } void free_fontstack(fontstack *f) { + hb_buffer_destroy(f->hb_buffer); + + for (auto hb_font : *f->hb_fonts) { + hb_font_destroy(hb_font); + } for (auto fc : *f->faces) { FT_Done_Face(fc); } @@ -173,17 +235,17 @@ extern "C" { } char *fontstack_name(fontstack *f) { - char *fname = (char *)malloc((f->name->size()+1) * sizeof(char)); - strcpy(fname,f->name->c_str()); + char *fname = (char *)malloc((f->name->size() + 1) * sizeof(char)); + strcpy(fname, f->name->c_str()); return fname; } glyph_buffer *generate_glyph_buffer(fontstack *f, uint32_t start_codepoint) { - string result = do_range(*f->faces,*f->name,start_codepoint,start_codepoint+255); + string result = do_range(*f->hb_fonts, f->hb_buffer, *f->name, start_codepoint, start_codepoint + 255); glyph_buffer *g = (glyph_buffer *)malloc(sizeof(glyph_buffer)); char *result_ptr = (char *)malloc(result.size()); - result.copy(result_ptr,result.size()); + result.copy(result_ptr, result.size()); g->data = result_ptr; g->size = result.size(); return g; @@ -206,6 +268,9 @@ extern "C" { #ifndef EMSCRIPTEN int main(int argc, char *argv[]) { + // cout << get_glyph_index("NotoSansJP-Regular.ttf", 0x30FC, false) << endl; + // return; + cxxopts::Options cmd_options("font-maker", "Create font PBFs from TTFs or OTFs."); cmd_options.add_options() ("output", "Output directory (to be created, must not already exist)", cxxopts::value()) @@ -213,7 +278,7 @@ int main(int argc, char *argv[]) ("name", "Override output fontstack name", cxxopts::value()) ("help", "Print usage") ; - cmd_options.positional_help(" [INPUT_FONT2 ...]"); + cmd_options.positional_help("--output OUTPUT_DIR --fonts FONT1 FONT1 --name FONTSTACK_NAME"); cmd_options.parse_positional({"output","fonts"}); auto result = cmd_options.parse(argc, argv); if (result.count("help")) @@ -246,7 +311,7 @@ int main(int argc, char *argv[]) f->data->push_back(buffer); file.read(buffer, size); std::cout << "Adding " << font << std::endl; - fontstack_add_face(f,(FT_Byte *)buffer,size); + fontstack_add_face(f, (FT_Byte *)buffer,size); } std::string fname{fontstack_name(f)}; @@ -254,7 +319,7 @@ int main(int argc, char *argv[]) ghc::filesystem::create_directory(output_dir + "/" + fname); for (int i = 0; i < 65536; i += 256) { - glyph_buffer *g = generate_glyph_buffer(f,i); + glyph_buffer *g = generate_glyph_buffer(f, i); char *data = glyph_buffer_data(g); uint32_t buffer_size = glyph_buffer_size(g); diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..62c8ab6 --- /dev/null +++ b/run.sh @@ -0,0 +1,2 @@ +rm -rf output_dir +./font-maker \ No newline at end of file