Part: Add HarfBuzz text shaping for proper kerning in makeTextWires

Replace the character-by-character positioning loop with HarfBuzz
text shaping. The old code used FT_Get_Kerning which only reads
legacy kern tables. HarfBuzz handles both GPOS kerning and legacy
kern tables, as well as ligatures and complex script shaping.
This commit is contained in:
Benjamin Nauck
2026-03-08 20:56:23 +01:00
parent e7e5e39e7b
commit a0c97a102e
3 changed files with 56 additions and 24 deletions
+16
View File
@@ -7,6 +7,22 @@ macro(SetupFreetype)
message("===============================================================\n"
"FreeType2 not found. Part module will lack of makeWireString().\n"
"===============================================================\n")
else()
# find_package(harfbuzz CONFIG) fails on windows
# lets do it the complicated way instead.
find_path(HARFBUZZ_INCLUDE_DIR hb.h PATH_SUFFIXES harfbuzz)
find_library(HARFBUZZ_LIBRARY NAMES harfbuzz)
if(HARFBUZZ_INCLUDE_DIR AND HARFBUZZ_LIBRARY)
if(NOT TARGET harfbuzz::harfbuzz)
add_library(harfbuzz::harfbuzz UNKNOWN IMPORTED)
set_target_properties(harfbuzz::harfbuzz PROPERTIES
IMPORTED_LOCATION "${HARFBUZZ_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${HARFBUZZ_INCLUDE_DIR}"
)
endif()
else()
message(FATAL_ERROR "HarfBuzz not found")
endif()
endif(NOT FREETYPE_FOUND)
endif(FREECAD_USE_FREETYPE)
+2
View File
@@ -41,6 +41,8 @@ if(FREETYPE_FOUND)
${Part_LIBS}
${FREETYPE_LIBRARIES}
)
set(Part_LIBS ${Part_LIBS} harfbuzz::harfbuzz)
endif(FREETYPE_FOUND)
generate_from_py(Arc)
+38 -24
View File
@@ -126,8 +126,8 @@
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_OUTLINE_H
#include <codecvt>
#include <locale>
#include <hb.h>
// headers to scale text correctly
#include <BRepBndLib.hxx>
@@ -7659,32 +7659,42 @@ std::vector<TopoDS_Shape> makeTextWires(
FT_Outline_Funcs ftCallbacks = {move_cb, line_cb, quad_cb, cubic_cb, 0, 0};
FT_UInt ftLoadFlags = FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP;
// Use HarfBuzz for text shaping. Positions are in font design units (upem),
// matching our FT_LOAD_NO_SCALE outline decomposition.
auto* hbBlob = hb_blob_create(
fontBuffer.data(),
static_cast<unsigned int>(fontBuffer.size()),
HB_MEMORY_MODE_READONLY,
nullptr,
nullptr
);
auto* hbFace = hb_face_create(hbBlob, 0);
auto* hbFont = hb_font_create(hbFace);
auto* hbBuf = hb_buffer_create();
hb_buffer_add_utf8(hbBuf, text.c_str(), -1, 0, -1);
hb_buffer_guess_segment_properties(hbBuf);
hb_shape(hbFont, hbBuf, nullptr, 0);
unsigned int glyphCount = 0;
auto* glyphInfos = hb_buffer_get_glyph_infos(hbBuf, &glyphCount);
auto* glyphPositions = hb_buffer_get_glyph_positions(hbBuf, &glyphCount);
double penPos = 0.0;
double currentTracking = 0.0;
FT_ULong prevCharcode = 0;
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;
std::u32string wide_text = converter.from_bytes(text);
for (unsigned int i = 0; i < glyphCount; ++i) {
FT_UInt glyphIndex = glyphInfos[i].codepoint;
double xOffset = glyphPositions[i].x_offset;
double xAdvance = glyphPositions[i].x_advance;
for (size_t i = 0; i < wide_text.length(); ++i) {
FT_ULong charcode = wide_text[i];
if (FT_Load_Char(ftFace, charcode, ftLoadFlags) != 0) {
if (FT_Load_Glyph(ftFace, glyphIndex, ftLoadFlags) != 0) {
penPos += xAdvance;
currentTracking += tracking;
continue;
}
if (prevCharcode != 0 && FT_HAS_KERNING(ftFace)) {
FT_Vector kern;
FT_Get_Kerning(
ftFace,
FT_Get_Char_Index(ftFace, prevCharcode),
FT_Get_Char_Index(ftFace, charcode),
FT_KERNING_UNSCALED,
&kern
);
penPos += kern.x;
}
if (ftFace->glyph->format == FT_GLYPH_FORMAT_OUTLINE
&& ftFace->glyph->outline.n_contours > 0) {
FTDC_Ctx ctx;
@@ -7696,7 +7706,7 @@ std::vector<TopoDS_Shape> makeTextWires(
if (!ctx.Wires.empty()) {
gp_Trsf charTransform;
charTransform.SetScale(gp::Origin(), scaleFactor);
gp_Vec translation(penPos * scaleFactor + currentTracking, 0.0, 0.0);
gp_Vec translation((penPos + xOffset) * scaleFactor + currentTracking, 0.0, 0.0);
charTransform.SetTranslationPart(translation);
for (const auto& wire : ctx.Wires) {
@@ -7709,11 +7719,15 @@ std::vector<TopoDS_Shape> makeTextWires(
}
}
penPos += ftFace->glyph->advance.x;
penPos += xAdvance;
currentTracking += tracking;
prevCharcode = charcode;
}
hb_buffer_destroy(hbBuf);
hb_font_destroy(hbFont);
hb_face_destroy(hbFace);
hb_blob_destroy(hbBlob);
FT_Done_Face(ftFace);
FT_Done_FreeType(ftLib);
return allWires;