Made accurate text layout work correctly using HarfBuzz

This commit is contained in:
Leon Styhre 2024-08-03 19:04:45 +02:00
parent 7a8bd97226
commit c873441851
3 changed files with 26 additions and 47 deletions

View file

@ -484,7 +484,7 @@ void TextComponent::onTextChanged()
std::shared_ptr<Font> font {mFont}; std::shared_ptr<Font> font {mFont};
// Used to initialize all glyphs, which is needed to populate mMaxGlyphHeight. // Used to initialize all glyphs, which is needed to populate mMaxGlyphHeight.
lineHeight = mFont->loadGlyphs(text + "\n") * mLineSpacing; lineHeight = mFont->loadGlyphs(text) * mLineSpacing;
const bool isMultiline {mAutoCalcExtent.y == 1 || mSize.y * mRelativeScale > lineHeight}; const bool isMultiline {mAutoCalcExtent.y == 1 || mSize.y * mRelativeScale > lineHeight};
float offsetY {0.0f}; float offsetY {0.0f};

View file

@ -106,8 +106,7 @@ glm::vec2 Font::sizeText(std::string text, float lineSpacing)
for (auto& segment : segmentsHB) { for (auto& segment : segmentsHB) {
for (size_t i {0}; i < segment.glyphIndexes.size(); ++i) { for (size_t i {0}; i < segment.glyphIndexes.size(); ++i) {
const unsigned int character {segment.glyphIndexes[i]}; const unsigned int character {segment.glyphIndexes[i].first};
Glyph* glyph {nullptr};
// Invalid character. // Invalid character.
if (!segment.doShape && character == 0) if (!segment.doShape && character == 0)
@ -122,13 +121,7 @@ glm::vec2 Font::sizeText(std::string text, float lineSpacing)
continue; continue;
} }
if (segment.doShape) lineWidth += segment.glyphIndexes[i].second;
glyph = getGlyphByIndex(character, segment.fontHB);
else
glyph = getGlyph(character);
if (glyph)
lineWidth += glyph->advance.x;
} }
if (lineWidth > highestWidth) if (lineWidth > highestWidth)
@ -146,7 +139,7 @@ int Font::loadGlyphs(const std::string& text)
for (auto& segment : segmentsHB) { for (auto& segment : segmentsHB) {
for (size_t i {0}; i < segment.glyphIndexes.size(); ++i) { for (size_t i {0}; i < segment.glyphIndexes.size(); ++i) {
const unsigned int character {segment.glyphIndexes[i]}; const unsigned int character {segment.glyphIndexes[i].first};
Glyph* glyph {nullptr}; Glyph* glyph {nullptr};
// Invalid character. // Invalid character.
@ -154,7 +147,7 @@ int Font::loadGlyphs(const std::string& text)
continue; continue;
if (segment.doShape) if (segment.doShape)
glyph = getGlyphByIndex(character, segment.fontHB); glyph = getGlyphByIndex(character, segment.fontHB, segment.glyphIndexes[i].second);
else else
glyph = getGlyph(character); glyph = getGlyph(character);
@ -207,7 +200,7 @@ TextCache* Font::buildTextCache(const std::string& text,
for (auto& segment : segmentsHB) { for (auto& segment : segmentsHB) {
for (size_t cursor {0}; cursor < segment.glyphIndexes.size(); ++cursor) { for (size_t cursor {0}; cursor < segment.glyphIndexes.size(); ++cursor) {
const unsigned int character {segment.glyphIndexes[cursor]}; const unsigned int character {segment.glyphIndexes[cursor].first};
Glyph* glyph {nullptr}; Glyph* glyph {nullptr};
// Invalid character. // Invalid character.
@ -225,7 +218,8 @@ TextCache* Font::buildTextCache(const std::string& text,
} }
if (segment.doShape) if (segment.doShape)
glyph = getGlyphByIndex(character, segment.fontHB); glyph =
getGlyphByIndex(character, segment.fontHB, segment.glyphIndexes[cursor].second);
else else
glyph = getGlyph(character); glyph = getGlyph(character);
@ -480,8 +474,7 @@ glm::vec2 Font::getWrappedTextCursorOffset(const std::string& wrappedText,
// if (totalPos > stop) // if (totalPos > stop)
// break; // break;
// const unsigned int character {segment.glyphIndexes[i]}; // const unsigned int character {segment.glyphIndexes[i].first};
// Glyph* glyph {nullptr};
// // Invalid character. // // Invalid character.
// if (!segment.doShape && character == 0) // if (!segment.doShape && character == 0)
@ -493,13 +486,7 @@ glm::vec2 Font::getWrappedTextCursorOffset(const std::string& wrappedText,
// continue; // continue;
// } // }
// if (segment.doShape) // lineWidth += segment.glyphIndexes[i].second;
// glyph = getGlyphByIndex(character, segment.fontHB);
// else
// glyph = getGlyph(character);
// if (glyph)
// lineWidth += glyph->advance.x;
// } // }
// } // }
@ -853,13 +840,12 @@ std::vector<Font::ShapeSegment> Font::shapeText(const std::string& text)
if (segment.doShape) { if (segment.doShape) {
character = glyphInfo[cursor].codepoint; character = glyphInfo[cursor].codepoint;
// As HarfBuzz sometimes incorrectly indicates a zero advance we need to get the getGlyphByIndex(character, segment.fontHB == nullptr ? mFontHB : segment.fontHB,
// advance value from the glyph entry as it will in this case fall back to the glyphPos[cursor].x_advance);
// built-in font advance value for the glyph. const int advanceX {static_cast<int>(
Glyph* glyph {getGlyphByIndex(character, std::round(static_cast<float>(glyphPos[cursor].x_advance) / 256.0f))};
segment.fontHB == nullptr ? mFontHB : segment.fontHB, segment.glyphsWidth += advanceX;
glyphPos[cursor].x_advance)}; segment.glyphIndexes.emplace_back(std::make_pair(character, advanceX));
segment.glyphsWidth += glyph->advance.x;
++cursor; ++cursor;
} }
else { else {
@ -867,9 +853,8 @@ std::vector<Font::ShapeSegment> Font::shapeText(const std::string& text)
character = Utils::String::chars2Unicode(segment.substring, cursor); character = Utils::String::chars2Unicode(segment.substring, cursor);
Glyph* glyph {getGlyph(character)}; Glyph* glyph {getGlyph(character)};
segment.glyphsWidth += glyph->advance.x; segment.glyphsWidth += glyph->advance.x;
segment.glyphIndexes.emplace_back(std::make_pair(character, glyph->advance.x));
} }
segment.glyphIndexes.emplace_back(character);
} }
} }
@ -904,11 +889,11 @@ void Font::rebuildTextures()
} }
for (auto it = mGlyphMapByIndex.cbegin(); it != mGlyphMapByIndex.cend(); ++it) { for (auto it = mGlyphMapByIndex.cbegin(); it != mGlyphMapByIndex.cend(); ++it) {
FT_Face* face {getFaceForGlyphIndex(it->first.first, it->first.second)}; FT_Face* face {getFaceForGlyphIndex(std::get<0>(it->first), std::get<1>(it->first))};
FT_GlyphSlot glyphSlot {(*face)->glyph}; FT_GlyphSlot glyphSlot {(*face)->glyph};
// Load the glyph bitmap through FreeType. // Load the glyph bitmap through FreeType.
FT_Load_Glyph(*face, it->first.first, FT_LOAD_RENDER); FT_Load_Glyph(*face, std::get<0>(it->first), FT_LOAD_RENDER);
const glm::ivec2 glyphSize {glyphSlot->bitmap.width, glyphSlot->bitmap.rows}; const glm::ivec2 glyphSize {glyphSlot->bitmap.width, glyphSlot->bitmap.rows};
const glm::ivec2 cursor { const glm::ivec2 cursor {
@ -1071,8 +1056,8 @@ Font::Glyph* Font::getGlyph(const unsigned int id)
Font::Glyph* Font::getGlyphByIndex(const unsigned int id, hb_font_t* fontArg, int xAdvance) Font::Glyph* Font::getGlyphByIndex(const unsigned int id, hb_font_t* fontArg, int xAdvance)
{ {
// Check if the glyph has already been loaded. // Check if the glyph has already been loaded.
auto it = mGlyphMapByIndex.find(std::make_pair(id, fontArg)); auto it = mGlyphMapByIndex.find(std::make_tuple(id, fontArg, xAdvance));
if (it != mGlyphMapByIndex.cend()) if (it != mGlyphMapByIndex.end())
return &it->second; return &it->second;
// We need to create a new entry. // We need to create a new entry.
@ -1117,7 +1102,7 @@ Font::Glyph* Font::getGlyphByIndex(const unsigned int id, hb_font_t* fontArg, in
mLetterHeight = static_cast<float>(glyphSize.y); mLetterHeight = static_cast<float>(glyphSize.y);
// Create glyph. // Create glyph.
Glyph& glyph {mGlyphMapByIndex[std::make_pair(id, mLastFontHB)]}; Glyph& glyph {mGlyphMapByIndex[std::make_tuple(id, mLastFontHB, xAdvance)]};
glyph.fontHB = mLastFontHB; glyph.fontHB = mLastFontHB;
glyph.texture = tex; glyph.texture = tex;
@ -1125,13 +1110,7 @@ Font::Glyph* Font::getGlyphByIndex(const unsigned int id, hb_font_t* fontArg, in
cursor.y / static_cast<float>(tex->textureSize.y)}; cursor.y / static_cast<float>(tex->textureSize.y)};
glyph.texSize = {glyphSize.x / static_cast<float>(tex->textureSize.x), glyph.texSize = {glyphSize.x / static_cast<float>(tex->textureSize.x),
glyphSize.y / static_cast<float>(tex->textureSize.y)}; glyphSize.y / static_cast<float>(tex->textureSize.y)};
// Sometimes HarfBuzz incorrectly indicates a zero advance so in this case we need to fall back glyph.advance = {xAdvance, glyphSlot->metrics.vertAdvance >> 6};
// to the font-default advance value for the glyph.
if (xAdvance == 0)
glyph.advance = {glyphSlot->metrics.horiAdvance >> 6, glyphSlot->metrics.vertAdvance >> 6};
else
glyph.advance = {static_cast<int>(std::round(static_cast<float>(xAdvance) / 256.0f)),
glyphSlot->metrics.vertAdvance >> 6};
glyph.bearing = {glyphSlot->metrics.horiBearingX >> 6, glyphSlot->metrics.horiBearingY >> 6}; glyph.bearing = {glyphSlot->metrics.horiBearingX >> 6, glyphSlot->metrics.horiBearingY >> 6};
glyph.rows = glyphSize.y; glyph.rows = glyphSize.y;

View file

@ -196,7 +196,7 @@ private:
bool doShape; bool doShape;
bool rightToLeft; bool rightToLeft;
std::string substring; std::string substring;
std::vector<unsigned int> glyphIndexes; std::vector<std::pair<unsigned int, int>> glyphIndexes;
ShapeSegment() ShapeSegment()
: startPos {0} : startPos {0}
@ -224,7 +224,7 @@ private:
FT_Face* getFaceForChar(unsigned int id); FT_Face* getFaceForChar(unsigned int id);
FT_Face* getFaceForGlyphIndex(unsigned int id, hb_font_t* fontArg); FT_Face* getFaceForGlyphIndex(unsigned int id, hb_font_t* fontArg);
Glyph* getGlyph(const unsigned int id); Glyph* getGlyph(const unsigned int id);
Glyph* getGlyphByIndex(const unsigned int id, hb_font_t* fontArg, int xAdvance = 0); Glyph* getGlyphByIndex(const unsigned int id, hb_font_t* fontArg, int xAdvance);
float getNewlineStartOffset(const std::string& text, float getNewlineStartOffset(const std::string& text,
const unsigned int& charStart, const unsigned int& charStart,
@ -239,7 +239,7 @@ private:
std::unique_ptr<FontFace> mFontFace; std::unique_ptr<FontFace> mFontFace;
std::vector<std::unique_ptr<FontTexture>> mTextures; std::vector<std::unique_ptr<FontTexture>> mTextures;
std::map<unsigned int, Glyph> mGlyphMap; std::map<unsigned int, Glyph> mGlyphMap;
std::map<std::pair<unsigned int, hb_font_t*>, Glyph> mGlyphMapByIndex; std::map<std::tuple<unsigned int, hb_font_t*, int>, Glyph> mGlyphMapByIndex;
const std::string mPath; const std::string mPath;
hb_font_t* mFontHB; hb_font_t* mFontHB;