diff options
Diffstat (limited to 'Source/WebCore/platform/graphics/harfbuzz/ng')
6 files changed, 149 insertions, 77 deletions
diff --git a/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFace.cpp b/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFace.cpp index 7dd048b3b..50dbd81bd 100644 --- a/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFace.cpp +++ b/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFace.cpp @@ -32,17 +32,44 @@ #include "HarfBuzzNGFace.h" #include "FontPlatformData.h" +#include "hb-ot.h" #include "hb.h" -#include <wtf/HashMap.h> namespace WebCore { +const hb_tag_t HarfBuzzNGFace::vertTag = HB_TAG('v', 'e', 'r', 't'); +const hb_tag_t HarfBuzzNGFace::vrt2Tag = HB_TAG('v', 'r', 't', '2'); + // Though we have FontCache class, which provides the cache mechanism for // WebKit's font objects, we also need additional caching layer for HarfBuzz // to reduce the memory consumption because hb_face_t should be associated with // underling font data (e.g. CTFontRef, FTFace). -typedef pair<hb_face_t*, unsigned> FaceCacheEntry; -typedef HashMap<uint64_t, FaceCacheEntry, WTF::IntHash<uint64_t>, WTF::UnsignedWithZeroKeyHashTraits<uint64_t> > HarfBuzzNGFaceCache; + +class FaceCacheEntry : public RefCounted<FaceCacheEntry> { +public: + static PassRefPtr<FaceCacheEntry> create(hb_face_t* face) + { + ASSERT(face); + return adoptRef(new FaceCacheEntry(face)); + } + ~FaceCacheEntry() + { + hb_face_destroy(m_face); + } + + hb_face_t* face() { return m_face; } + HashMap<uint32_t, uint16_t>* glyphCache() { return &m_glyphCache; } + +private: + explicit FaceCacheEntry(hb_face_t* face) + : m_face(face) + { } + + hb_face_t* m_face; + HashMap<uint32_t, uint16_t> m_glyphCache; +}; + +typedef HashMap<uint64_t, RefPtr<FaceCacheEntry>, WTF::IntHash<uint64_t>, WTF::UnsignedWithZeroKeyHashTraits<uint64_t> > HarfBuzzNGFaceCache; static HarfBuzzNGFaceCache* harfbuzzFaceCache() { @@ -53,28 +80,52 @@ static HarfBuzzNGFaceCache* harfbuzzFaceCache() HarfBuzzNGFace::HarfBuzzNGFace(FontPlatformData* platformData, uint64_t uniqueID) : m_platformData(platformData) , m_uniqueID(uniqueID) + , m_scriptForVerticalText(HB_SCRIPT_INVALID) { - HarfBuzzNGFaceCache::iterator result = harfbuzzFaceCache()->find(m_uniqueID); - if (result == harfbuzzFaceCache()->end()) { - m_face = createFace(); - ASSERT(m_face); - harfbuzzFaceCache()->set(m_uniqueID, FaceCacheEntry(m_face, 1)); - } else { - ++(result.get()->second.second); - m_face = result.get()->second.first; - } + HarfBuzzNGFaceCache::AddResult result = harfbuzzFaceCache()->add(m_uniqueID, 0); + if (result.isNewEntry) + result.iterator->value = FaceCacheEntry::create(createFace()); + result.iterator->value->ref(); + m_face = result.iterator->value->face(); + m_glyphCacheForFaceCacheEntry = result.iterator->value->glyphCache(); } HarfBuzzNGFace::~HarfBuzzNGFace() { HarfBuzzNGFaceCache::iterator result = harfbuzzFaceCache()->find(m_uniqueID); ASSERT(result != harfbuzzFaceCache()->end()); - ASSERT(result.get()->second.second > 0); - --(result.get()->second.second); - if (!(result.get()->second.second)) { - hb_face_destroy(result.get()->second.first); + ASSERT(result.get()->value->refCount() > 1); + result.get()->value->deref(); + if (result.get()->value->refCount() == 1) harfbuzzFaceCache()->remove(m_uniqueID); +} + +static hb_script_t findScriptForVerticalGlyphSubstitution(hb_face_t* face) +{ + static const unsigned maxCount = 32; + + unsigned scriptCount = maxCount; + hb_tag_t scriptTags[maxCount]; + hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, 0, &scriptCount, scriptTags); + for (unsigned scriptIndex = 0; scriptIndex < scriptCount; ++scriptIndex) { + unsigned languageCount = maxCount; + hb_tag_t languageTags[maxCount]; + hb_ot_layout_script_get_language_tags(face, HB_OT_TAG_GSUB, scriptIndex, 0, &languageCount, languageTags); + for (unsigned languageIndex = 0; languageIndex < languageCount; ++languageIndex) { + unsigned featureIndex; + if (hb_ot_layout_language_find_feature(face, HB_OT_TAG_GSUB, scriptIndex, languageIndex, HarfBuzzNGFace::vertTag, &featureIndex) + || hb_ot_layout_language_find_feature(face, HB_OT_TAG_GSUB, scriptIndex, languageIndex, HarfBuzzNGFace::vrt2Tag, &featureIndex)) + return hb_ot_tag_to_script(scriptTags[scriptIndex]); + } } + return HB_SCRIPT_INVALID; +} + +void HarfBuzzNGFace::setScriptForVerticalGlyphSubstitution(hb_buffer_t* buffer) +{ + if (m_scriptForVerticalText == HB_SCRIPT_INVALID) + m_scriptForVerticalText = findScriptForVerticalGlyphSubstitution(m_face); + hb_buffer_set_script(buffer, m_scriptForVerticalText); } } // namespace WebCore diff --git a/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFace.h b/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFace.h index cbc8f8ed4..360daf9cf 100644 --- a/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFace.h +++ b/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFace.h @@ -33,6 +33,7 @@ #include <hb.h> +#include <wtf/HashMap.h> #include <wtf/PassRefPtr.h> #include <wtf/RefCounted.h> #include <wtf/RefPtr.h> @@ -43,6 +44,9 @@ class FontPlatformData; class HarfBuzzNGFace : public RefCounted<HarfBuzzNGFace> { public: + static const hb_tag_t vertTag; + static const hb_tag_t vrt2Tag; + static PassRefPtr<HarfBuzzNGFace> create(FontPlatformData* platformData, uint64_t uniqueID) { return adoptRef(new HarfBuzzNGFace(platformData, uniqueID)); @@ -51,6 +55,8 @@ public: hb_font_t* createFont(); + void setScriptForVerticalGlyphSubstitution(hb_buffer_t*); + private: HarfBuzzNGFace(FontPlatformData*, uint64_t); @@ -59,6 +65,9 @@ private: FontPlatformData* m_platformData; uint64_t m_uniqueID; hb_face_t* m_face; + WTF::HashMap<uint32_t, uint16_t>* m_glyphCacheForFaceCacheEntry; + + hb_script_t m_scriptForVerticalText; }; } diff --git a/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFaceCairo.cpp b/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFaceCairo.cpp index 52fca7baf..2db3d2780 100644 --- a/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFaceCairo.cpp +++ b/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFaceCairo.cpp @@ -155,9 +155,7 @@ static hb_font_funcs_t* harfbuzzCairoTextGetFontFuncs() static hb_blob_t* harfbuzzCairoGetTable(hb_face_t*, hb_tag_t tag, void* userData) { - FontPlatformData* font = reinterpret_cast<FontPlatformData*>(userData); - - cairo_scaled_font_t* scaledFont = font->scaledFont(); + cairo_scaled_font_t* scaledFont = reinterpret_cast<cairo_scaled_font_t*>(userData); if (!scaledFont) return 0; @@ -186,7 +184,7 @@ static hb_blob_t* harfbuzzCairoGetTable(hb_face_t*, hb_tag_t tag, void* userData hb_face_t* HarfBuzzNGFace::createFace() { - hb_face_t* face = hb_face_create_for_tables(harfbuzzCairoGetTable, m_platformData, 0); + hb_face_t* face = hb_face_create_for_tables(harfbuzzCairoGetTable, m_platformData->scaledFont(), 0); ASSERT(face); return face; } diff --git a/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFaceCoreText.cpp b/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFaceCoreText.cpp index f2baba73f..e4c65cd41 100644 --- a/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFaceCoreText.cpp +++ b/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFaceCoreText.cpp @@ -108,10 +108,7 @@ static void releaseTableData(void* userData) static hb_blob_t* harfbuzzCoreTextGetTable(hb_face_t* face, hb_tag_t tag, void* userData) { - FontPlatformData* platformData = reinterpret_cast<FontPlatformData*>(userData); - // It seems that CTFontCopyTable of MacOSX10.5 sdk doesn't work for - // OpenType layout tables(GDEF, GSUB, GPOS). Use CGFontCopyTableForTag instead. - CGFontRef cgFont = platformData->cgFont(); + CGFontRef cgFont = reinterpret_cast<CGFontRef>(userData); CFDataRef cfData = CGFontCopyTableForTag(cgFont, tag); if (!cfData) return 0; @@ -125,7 +122,9 @@ static hb_blob_t* harfbuzzCoreTextGetTable(hb_face_t* face, hb_tag_t tag, void* hb_face_t* HarfBuzzNGFace::createFace() { - hb_face_t* face = hb_face_create_for_tables(harfbuzzCoreTextGetTable, m_platformData, 0); + // It seems that CTFontCopyTable of MacOSX10.5 sdk doesn't work for + // OpenType layout tables(GDEF, GSUB, GPOS). Use CGFontCopyTableForTag instead. + hb_face_t* face = hb_face_create_for_tables(harfbuzzCoreTextGetTable, m_platformData->cgFont(), 0); ASSERT(face); return face; } diff --git a/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFaceSkia.cpp b/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFaceSkia.cpp index 53acb292c..5d39efa73 100644 --- a/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFaceSkia.cpp +++ b/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzNGFaceSkia.cpp @@ -43,12 +43,21 @@ #include "SkUtils.h" #include "hb.h" +#include <wtf/HashMap.h> namespace WebCore { // Our implementation of the callbacks which Harfbuzz requires by using Skia // calls. See the Harfbuzz source for references about what these callbacks do. +struct HarfBuzzFontData { + HarfBuzzFontData(WTF::HashMap<uint32_t, uint16_t>* glyphCacheForFaceCacheEntry) + : m_glyphCacheForFaceCacheEntry(glyphCacheForFaceCacheEntry) + { } + SkPaint m_paint; + WTF::HashMap<uint32_t, uint16_t>* m_glyphCacheForFaceCacheEntry; +}; + static hb_position_t SkiaScalarToHarfbuzzPosition(SkScalar value) { return SkScalarToFixed(value); @@ -56,8 +65,7 @@ static hb_position_t SkiaScalarToHarfbuzzPosition(SkScalar value) static void SkiaGetGlyphWidthAndExtents(SkPaint* paint, hb_codepoint_t codepoint, hb_position_t* width, hb_glyph_extents_t* extents) { - if (codepoint > 0xFFFF) - return; + ASSERT(codepoint <= 0xFFFF); paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); SkScalar skWidth; @@ -78,23 +86,27 @@ static void SkiaGetGlyphWidthAndExtents(SkPaint* paint, hb_codepoint_t codepoint static hb_bool_t harfbuzzGetGlyph(hb_font_t* hbFont, void* fontData, hb_codepoint_t unicode, hb_codepoint_t variationSelector, hb_codepoint_t* glyph, void* userData) { - SkPaint* paint = reinterpret_cast<SkPaint*>(fontData); - - paint->setTextEncoding(SkPaint::kUTF16_TextEncoding); - uint16_t text[4]; - size_t length = SkUTF16_FromUnichar(unicode, text); - uint16_t glyph16; - paint->textToGlyphs(text, length, &glyph16); - *glyph = glyph16; + HarfBuzzFontData* hbFontData = reinterpret_cast<HarfBuzzFontData*>(fontData); + + WTF::HashMap<uint32_t, uint16_t>::AddResult result = hbFontData->m_glyphCacheForFaceCacheEntry->add(unicode, 0); + if (result.isNewEntry) { + SkPaint* paint = &hbFontData->m_paint; + paint->setTextEncoding(SkPaint::kUTF32_TextEncoding); + uint16_t glyph16; + paint->textToGlyphs(&unicode, sizeof(hb_codepoint_t), &glyph16); + result.iterator->value = glyph16; + *glyph = glyph16; + } + *glyph = result.iterator->value; return !!*glyph; } static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* hbFont, void* fontData, hb_codepoint_t glyph, void* userData) { - SkPaint* paint = reinterpret_cast<SkPaint*>(fontData); + HarfBuzzFontData* hbFontData = reinterpret_cast<HarfBuzzFontData*>(fontData); hb_position_t advance = 0; - SkiaGetGlyphWidthAndExtents(paint, glyph, &advance, 0); + SkiaGetGlyphWidthAndExtents(&hbFontData->m_paint, glyph, &advance, 0); return advance; } @@ -107,9 +119,9 @@ static hb_bool_t harfbuzzGetGlyphHorizontalOrigin(hb_font_t* hbFont, void* fontD static hb_bool_t harfbuzzGetGlyphExtents(hb_font_t* hbFont, void* fontData, hb_codepoint_t glyph, hb_glyph_extents_t* extents, void* userData) { - SkPaint* paint = reinterpret_cast<SkPaint*>(fontData); + HarfBuzzFontData* hbFontData = reinterpret_cast<HarfBuzzFontData*>(fontData); - SkiaGetGlyphWidthAndExtents(paint, glyph, 0, extents); + SkiaGetGlyphWidthAndExtents(&hbFontData->m_paint, glyph, 0, extents); return true; } @@ -132,16 +144,16 @@ static hb_font_funcs_t* harfbuzzSkiaGetFontFuncs() static hb_blob_t* harfbuzzSkiaGetTable(hb_face_t* face, hb_tag_t tag, void* userData) { - FontPlatformData* font = reinterpret_cast<FontPlatformData*>(userData); + SkFontID uniqueID = static_cast<SkFontID>(reinterpret_cast<uint64_t>(userData)); - const size_t tableSize = SkFontHost::GetTableSize(font->uniqueID(), tag); + const size_t tableSize = SkFontHost::GetTableSize(uniqueID, tag); if (!tableSize) return 0; char* buffer = reinterpret_cast<char*>(fastMalloc(tableSize)); if (!buffer) return 0; - size_t actualSize = SkFontHost::GetTableData(font->uniqueID(), tag, 0, tableSize, buffer); + size_t actualSize = SkFontHost::GetTableData(uniqueID, tag, 0, tableSize, buffer); if (tableSize != actualSize) { fastFree(buffer); return 0; @@ -151,25 +163,25 @@ static hb_blob_t* harfbuzzSkiaGetTable(hb_face_t* face, hb_tag_t tag, void* user HB_MEMORY_MODE_WRITABLE, buffer, fastFree); } -static void destroyPaint(void* userData) +static void destroyHarfBuzzFontData(void* userData) { - SkPaint* paint = reinterpret_cast<SkPaint*>(userData); - delete paint; + HarfBuzzFontData* hbFontData = reinterpret_cast<HarfBuzzFontData*>(userData); + delete hbFontData; } hb_face_t* HarfBuzzNGFace::createFace() { - hb_face_t* face = hb_face_create_for_tables(harfbuzzSkiaGetTable, m_platformData, 0); + hb_face_t* face = hb_face_create_for_tables(harfbuzzSkiaGetTable, reinterpret_cast<void*>(m_platformData->uniqueID()), 0); ASSERT(face); return face; } hb_font_t* HarfBuzzNGFace::createFont() { + HarfBuzzFontData* hbFontData = new HarfBuzzFontData(m_glyphCacheForFaceCacheEntry); + m_platformData->setupPaint(&hbFontData->m_paint); hb_font_t* font = hb_font_create(m_face); - SkPaint* paint = new SkPaint; - m_platformData->setupPaint(paint); - hb_font_set_funcs(font, harfbuzzSkiaGetFontFuncs(), paint, destroyPaint); + hb_font_set_funcs(font, harfbuzzSkiaGetFontFuncs(), hbFontData, destroyHarfBuzzFontData); float size = m_platformData->size(); int scale = SkiaScalarToHarfbuzzPosition(size); hb_font_set_scale(font, scale, scale); diff --git a/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzShaper.cpp b/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzShaper.cpp index 9e55fc411..796b37c95 100644 --- a/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzShaper.cpp +++ b/Source/WebCore/platform/graphics/harfbuzz/ng/HarfBuzzShaper.cpp @@ -87,10 +87,6 @@ void HarfBuzzShaper::HarfBuzzRun::applyShapeResult(hb_buffer_t* harfbuzzBuffer) m_advances.resize(m_numGlyphs); m_glyphToCharacterIndexes.resize(m_numGlyphs); m_offsets.resize(m_numGlyphs); - - hb_glyph_info_t* infos = hb_buffer_get_glyph_infos(harfbuzzBuffer, 0); - for (unsigned i = 0; i < m_numGlyphs; ++i) - m_glyphToCharacterIndexes[i] = infos[i].cluster; } void HarfBuzzShaper::HarfBuzzRun::setGlyphAndPositions(unsigned index, uint16_t glyphId, float advance, float offsetX, float offsetY) @@ -204,18 +200,27 @@ void HarfBuzzShaper::setDrawRange(int from, int to) void HarfBuzzShaper::setFontFeatures() { - FontFeatureSettings* settings = m_font->fontDescription().featureSettings(); + const FontDescription& description = m_font->fontDescription(); + if (description.orientation() == Vertical) { + static hb_feature_t vert = { HarfBuzzNGFace::vertTag, 1, 0, static_cast<unsigned>(-1) }; + static hb_feature_t vrt2 = { HarfBuzzNGFace::vrt2Tag, 1, 0, static_cast<unsigned>(-1) }; + m_features.append(vert); + m_features.append(vrt2); + } + + FontFeatureSettings* settings = description.featureSettings(); if (!settings) return; unsigned numFeatures = settings->size(); - m_features.resize(numFeatures); for (unsigned i = 0; i < numFeatures; ++i) { + hb_feature_t feature; const UChar* tag = settings->at(i).tag().characters(); - m_features[i].tag = HB_TAG(tag[0], tag[1], tag[2], tag[3]); - m_features[i].value = settings->at(i).value(); - m_features[i].start = 0; - m_features[i].end = static_cast<unsigned>(-1); + feature.tag = HB_TAG(tag[0], tag[1], tag[2], tag[3]); + feature.value = settings->at(i).value(); + feature.start = 0; + feature.end = static_cast<unsigned>(-1); + m_features.append(feature); } } @@ -240,20 +245,6 @@ FloatPoint HarfBuzzShaper::adjustStartPoint(const FloatPoint& point) return point + m_startOffset; } -static const SimpleFontData* fontDataForCombiningCharacterSequence(const Font* font, const UChar* characters, size_t length) -{ - UErrorCode error = U_ZERO_ERROR; - Vector<UChar, 4> normalizedCharacters(length); - int32_t normalizedLength = unorm_normalize(characters, length, UNORM_NFC, UNORM_UNICODE_3_2, &normalizedCharacters[0], length, &error); - // Should fallback if we have an error or no composition occurred. - if (U_FAILURE(error) || (static_cast<size_t>(normalizedLength) == length)) - return 0; - UChar32 normalizedCharacter; - size_t index = 0; - U16_NEXT(&normalizedCharacters[0], index, static_cast<size_t>(normalizedLength), normalizedCharacter); - return font->glyphDataForCharacter(normalizedCharacter, false).fontData; -} - bool HarfBuzzShaper::collectHarfBuzzRuns() { const UChar* normalizedBufferEnd = m_normalizedBuffer.get() + m_normalizedBufferLength; @@ -278,6 +269,7 @@ bool HarfBuzzShaper::collectHarfBuzzRuns() for (iterator.advance(clusterLength); iterator.consume(character, clusterLength); iterator.advance(clusterLength)) { if (Font::treatAsZeroWidthSpace(character)) continue; + if (U_GET_GC_MASK(character) & U_GC_M_MASK) { int markLength = clusterLength; const UChar* markCharactersEnd = iterator.characters() + clusterLength; @@ -290,11 +282,12 @@ bool HarfBuzzShaper::collectHarfBuzzRuns() markLength += nextCharacterLength; markCharactersEnd += nextCharacterLength; } - nextFontData = fontDataForCombiningCharacterSequence(m_font, currentCharacterPosition, markCharactersEnd - currentCharacterPosition); - if (nextFontData) + + if (currentFontData->canRenderCombiningCharacterSequence(currentCharacterPosition, markCharactersEnd - currentCharacterPosition)) { clusterLength = markLength; - else - nextFontData = m_font->glyphDataForCharacter(character, false).fontData; + continue; + } + nextFontData = m_font->glyphDataForCharacter(character, false).fontData; } else nextFontData = m_font->glyphDataForCharacter(character, false).fontData; @@ -305,6 +298,7 @@ bool HarfBuzzShaper::collectHarfBuzzRuns() break; if (nextScript == USCRIPT_INHERITED) nextScript = currentScript; + currentCharacterPosition = iterator.characters(); } unsigned numCharactersOfCurrentRun = iterator.currentCharacter() - startIndexOfCurrentRun; m_harfbuzzRuns.append(HarfBuzzRun::create(currentFontData, startIndexOfCurrentRun, numCharactersOfCurrentRun, m_run.direction())); @@ -340,7 +334,12 @@ bool HarfBuzzShaper::shapeHarfBuzzRuns() HarfBuzzNGFace* face = platformData->harfbuzzFace(); if (!face) return false; + + if (m_font->fontDescription().orientation() == Vertical) + face->setScriptForVerticalGlyphSubstitution(harfbuzzBuffer.get()); + HarfBuzzScopedPtr<hb_font_t> harfbuzzFont(face->createFont(), hb_font_destroy); + hb_shape(harfbuzzFont.get(), harfbuzzBuffer.get(), m_features.isEmpty() ? 0 : m_features.data(), m_features.size()); currentRun->applyShapeResult(harfbuzzBuffer.get()); @@ -361,6 +360,7 @@ void HarfBuzzShaper::setGlyphPositionsForHarfBuzzRun(HarfBuzzRun* currentRun, hb hb_glyph_position_t* glyphPositions = hb_buffer_get_glyph_positions(harfbuzzBuffer, 0); unsigned numGlyphs = currentRun->numGlyphs(); + uint16_t* glyphToCharacterIndexes = currentRun->glyphToCharacterIndexes(); float totalAdvance = 0; // HarfBuzz returns the shaping result in visual order. We need not to flip for RTL. @@ -374,6 +374,9 @@ void HarfBuzzShaper::setGlyphPositionsForHarfBuzzRun(HarfBuzzRun* currentRun, hb unsigned currentCharacterIndex = currentRun->startIndex() + glyphInfos[i].cluster; bool isClusterEnd = runEnd || glyphInfos[i].cluster != glyphInfos[i + 1].cluster; float spacing = 0; + + glyphToCharacterIndexes[i] = glyphInfos[i].cluster; + if (isClusterEnd && !Font::treatAsZeroWidthSpace(m_normalizedBuffer[currentCharacterIndex])) spacing += m_letterSpacing; |