Index: third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaper.cpp |
diff --git a/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaper.cpp b/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaper.cpp |
index 6e47ccf7442173fb82c1ed7d5f1366239720b404..64de83df863e65d442e72f4a3e11af7e232ba927 100644 |
--- a/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaper.cpp |
+++ b/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaper.cpp |
@@ -34,12 +34,15 @@ |
#include "hb.h" |
#include "platform/LayoutUnit.h" |
+#include "platform/Logging.h" |
#include "platform/RuntimeEnabledFeatures.h" |
#include "platform/fonts/Character.h" |
#include "platform/fonts/Font.h" |
+#include "platform/fonts/FontFallbackIterator.h" |
#include "platform/fonts/GlyphBuffer.h" |
#include "platform/fonts/UTF16TextIterator.h" |
#include "platform/fonts/shaping/HarfBuzzFace.h" |
+#include "platform/fonts/shaping/RunSegmenter.h" |
#include "platform/text/TextBreakIterator.h" |
#include "wtf/Compiler.h" |
#include "wtf/MathExtras.h" |
@@ -779,264 +782,263 @@ void HarfBuzzShaper::setFontFeatures() |
} |
} |
-PassRefPtr<ShapeResult> HarfBuzzShaper::shapeResult() |
+// A port of hb_icu_script_to_script because harfbuzz on CrOS is built |
+// without hb-icu. See http://crbug.com/356929 |
+static inline hb_script_t ICUScriptToHBScript(UScriptCode script) |
{ |
- if (!createHarfBuzzRuns()) |
- return nullptr; |
- return shapeHarfBuzzRuns(); |
+ if (UNLIKELY(script == USCRIPT_INVALID_CODE)) |
+ return HB_SCRIPT_INVALID; |
+ |
+ return hb_script_from_string(uscript_getShortName(script), -1); |
} |
-struct CandidateRun { |
- UChar32 character; |
- unsigned start; |
- unsigned end; |
- const SimpleFontData* fontData; |
- UScriptCode script; |
-}; |
+static inline hb_direction_t TextDirectionToHBDirection(TextDirection dir, FontOrientation orientation, const SimpleFontData* fontData) |
+{ |
+ hb_direction_t harfBuzzDirection = isVerticalAnyUpright(orientation) && !fontData->isTextOrientationFallback() ? HB_DIRECTION_TTB : HB_DIRECTION_LTR; |
+ return dir == RTL ? HB_DIRECTION_REVERSE(harfBuzzDirection) : harfBuzzDirection; |
+} |
-static inline bool collectCandidateRuns(const UChar* normalizedBuffer, |
- size_t bufferLength, const Font* font, Vector<CandidateRun>* runs, bool isSpaceNormalize) |
+static const uint16_t* toUint16(const UChar* src) |
{ |
- UTF16TextIterator iterator(normalizedBuffer, bufferLength); |
- UChar32 character; |
- unsigned startIndexOfCurrentRun = 0; |
+ // FIXME: This relies on undefined behavior however it works on the |
+ // current versions of all compilers we care about and avoids making |
+ // a copy of the string. |
+ static_assert(sizeof(UChar) == sizeof(uint16_t), "UChar should be the same size as uint16_t"); |
+ return reinterpret_cast<const uint16_t*>(src); |
+} |
- if (!iterator.consume(character)) |
- return false; |
+static inline void addToHarfBuzzBufferInternal(hb_buffer_t* buffer, |
+ const FontDescription& fontDescription, const UChar* normalizedBuffer, |
+ unsigned startIndex, unsigned numCharacters) |
+{ |
+ // TODO: Revisit whether we can always fill the hb_buffer_t with the |
+ // full run text, but only specify startIndex and numCharacters for the part |
+ // to be shaped. Then simplify/change the complicated index computations in |
+ // extractShapeResults(). |
+ if (fontDescription.variant() == FontVariantSmallCaps) { |
+ String upperText = String(normalizedBuffer + startIndex, numCharacters) |
+ .upper(); |
+ // TextRun is 16 bit, therefore upperText is 16 bit, even after we call |
+ // makeUpper(). |
+ ASSERT(!upperText.is8Bit()); |
+ hb_buffer_add_utf16(buffer, toUint16(upperText.characters16()), |
+ numCharacters, 0, numCharacters); |
+ } else { |
+ hb_buffer_add_utf16(buffer, toUint16(normalizedBuffer + startIndex), |
+ numCharacters, 0, numCharacters); |
+ } |
+} |
- const SimpleFontData* nextFontData = font->glyphDataForCharacter(character, false, isSpaceNormalize).fontData; |
- UErrorCode errorCode = U_ZERO_ERROR; |
- UScriptCode nextScript = uscript_getScript(character, &errorCode); |
- if (U_FAILURE(errorCode)) |
+inline bool HarfBuzzShaper::shapeRange(hb_buffer_t* harfBuzzBuffer, |
+ unsigned startIndex, |
+ unsigned numCharacters, |
+ const SimpleFontData* currentFont, |
+ unsigned currentFontRangeFrom, |
+ unsigned currentFontRangeTo, |
+ UScriptCode currentRunScript, |
+ hb_language_t language) |
+{ |
+ const FontPlatformData* platformData = &(currentFont->platformData()); |
+ HarfBuzzFace* face = platformData->harfBuzzFace(); |
+ if (!face) { |
+ WTF_LOG_ERROR("Could not create HarfBuzzFace from FontPlatformData."); |
return false; |
+ } |
- do { |
- const UChar* currentCharacterPosition = iterator.characters(); |
- const SimpleFontData* currentFontData = nextFontData; |
- UScriptCode currentScript = nextScript; |
- |
- UChar32 lastCharacter = character; |
- for (iterator.advance(); iterator.consume(character); iterator.advance()) { |
- if (Character::treatAsZeroWidthSpace(character)) |
- continue; |
- if ((U_GET_GC_MASK(character) & U_GC_M_MASK) |
- && (Character::isUnicodeVariationSelector(character) |
- || currentFontData->canRenderCombiningCharacterSequence( |
- currentCharacterPosition, |
- iterator.glyphEnd() - currentCharacterPosition))) |
- continue; |
+ hb_buffer_set_language(harfBuzzBuffer, language); |
+ hb_buffer_set_script(harfBuzzBuffer, ICUScriptToHBScript(currentRunScript)); |
+ hb_buffer_set_direction(harfBuzzBuffer, TextDirectionToHBDirection(m_textRun.direction(), |
+ m_font->fontDescription().orientation(), currentFont)); |
- nextFontData = font->glyphDataForCharacter(character, false, isSpaceNormalize).fontData; |
- nextScript = uscript_getScript(character, &errorCode); |
- if (U_FAILURE(errorCode)) |
- return false; |
- if (lastCharacter == zeroWidthJoinerCharacter) |
- currentFontData = nextFontData; |
- if ((nextFontData != currentFontData) || ((currentScript != nextScript) && (nextScript != USCRIPT_INHERITED) && (!uscript_hasScript(character, currentScript)))) |
- break; |
- currentCharacterPosition = iterator.characters(); |
- lastCharacter = character; |
- } |
+ // Add a space as pre-context to the buffer. This prevents showing dotted-circle |
+ // for combining marks at the beginning of runs. |
+ static const uint16_t preContext = spaceCharacter; |
+ hb_buffer_add_utf16(harfBuzzBuffer, &preContext, 1, 1, 0); |
- CandidateRun run = { lastCharacter, startIndexOfCurrentRun, static_cast<unsigned>(iterator.offset()), currentFontData, currentScript }; |
- runs->append(run); |
+ addToHarfBuzzBufferInternal(harfBuzzBuffer, |
+ m_font->fontDescription(), m_normalizedBuffer.get(), startIndex, |
+ numCharacters); |
- startIndexOfCurrentRun = iterator.offset(); |
- } while (iterator.consume(character)); |
+ HarfBuzzScopedPtr<hb_font_t> harfBuzzFont(face->createFont(currentFontRangeFrom, currentFontRangeTo), hb_font_destroy); |
+ hb_shape(harfBuzzFont.get(), harfBuzzBuffer, m_features.isEmpty() ? 0 : m_features.data(), m_features.size()); |
return true; |
} |
-static inline bool matchesAdjacentRun(UScriptCode* scriptExtensions, int length, |
- CandidateRun& adjacentRun) |
+bool HarfBuzzShaper::extractShapeResults(hb_buffer_t* harfBuzzBuffer, |
+ ShapeResult* shapeResult, |
+ bool& fontCycleQueued, const HolesQueueItem& currentQueueItem, |
+ const SimpleFontData* currentFont, |
+ UScriptCode currentRunScript, |
+ bool isLastResort) |
{ |
- for (int i = 0; i < length; i++) { |
- if (scriptExtensions[i] == adjacentRun.script) |
- return true; |
- } |
- return false; |
-} |
+ enum ClusterResult { |
+ Shaped, |
+ NotDef, |
+ Unknown |
+ }; |
+ ClusterResult currentClusterResult = Unknown; |
+ ClusterResult previousClusterResult = Unknown; |
+ unsigned previousCluster = 0; |
+ unsigned currentCluster = 0; |
-static inline void resolveRunBasedOnScriptExtensions(Vector<CandidateRun>& runs, |
- CandidateRun& run, size_t i, size_t length, UScriptCode* scriptExtensions, |
- int extensionsLength, size_t& nextResolvedRun) |
-{ |
- // If uscript_getScriptExtensions returns 1 it only contains the script value, |
- // we only care about ScriptExtensions which is indicated by a value >= 2. |
- if (extensionsLength <= 1) |
- return; |
+ // Find first notdef glyph in harfBuzzBuffer. |
+ unsigned numGlyphs = hb_buffer_get_length(harfBuzzBuffer); |
+ hb_glyph_info_t* glyphInfo = hb_buffer_get_glyph_infos(harfBuzzBuffer, 0); |
- if (i > 0 && matchesAdjacentRun(scriptExtensions, extensionsLength, runs[i - 1])) { |
- run.script = runs[i - 1].script; |
- return; |
+ unsigned lastChangePosition = 0; |
+ |
+ if (!numGlyphs) { |
+ WTF_LOG_ERROR("HarfBuzz returned empty glyph buffer after shaping."); |
+ return false; |
} |
- for (size_t j = i + 1; j < length; j++) { |
- if (runs[j].script != USCRIPT_COMMON |
- && runs[j].script != USCRIPT_INHERITED |
- && matchesAdjacentRun(scriptExtensions, extensionsLength, runs[j])) { |
- nextResolvedRun = j; |
- break; |
+ for (unsigned glyphIndex = 0; glyphIndex <= numGlyphs; ++glyphIndex) { |
+ // Iterating by clusters, check for when the state switches from shaped |
+ // to non-shaped and vice versa. Taking into account the edge cases of |
+ // beginning of the run and end of the run. |
+ previousCluster = currentCluster; |
+ currentCluster = glyphInfo[glyphIndex].cluster; |
+ |
+ if (glyphIndex < numGlyphs) { |
+ // Still the same cluster, merge shaping status. |
+ if (previousCluster == currentCluster && glyphIndex != 0) { |
+ if (glyphInfo[glyphIndex].codepoint == 0) { |
+ currentClusterResult = NotDef; |
+ } else { |
+ // We can only call the current cluster fully shapped, if |
+ // all characters that are part of it are shaped, so update |
+ // currentClusterResult to Shaped only if the previous |
+ // characters have been shaped, too. |
+ currentClusterResult = currentClusterResult == Shaped ? Shaped : NotDef; |
+ } |
+ continue; |
+ } |
+ // We've moved to a new cluster. |
+ previousClusterResult = currentClusterResult; |
+ currentClusterResult = glyphInfo[glyphIndex].codepoint == 0 ? NotDef : Shaped; |
+ } else { |
+ // The code below operates on the "flanks"/changes between NotDef |
+ // and Shaped. In order to keep the code below from explictly |
+ // dealing with character indices and run end, we explicitly |
+ // terminate the cluster/run here by setting the result value to the |
+ // opposite of what it was, leading to atChange turning true. |
+ previousClusterResult = currentClusterResult; |
+ currentClusterResult = currentClusterResult == NotDef ? Shaped : NotDef; |
} |
- } |
-} |
-static inline void resolveRunBasedOnScriptValue(Vector<CandidateRun>& runs, |
- CandidateRun& run, size_t i, size_t length, size_t& nextResolvedRun) |
-{ |
- if (run.script != USCRIPT_COMMON) |
- return; |
+ bool atChange = (previousClusterResult != currentClusterResult) && previousClusterResult != Unknown; |
+ if (!atChange) |
+ continue; |
- if (i > 0 && runs[i - 1].script != USCRIPT_COMMON) { |
- run.script = runs[i - 1].script; |
- return; |
- } |
+ // Compute the range indices of consecutive shaped or .notdef glyphs. |
+ // Cluster information for RTL runs becomes reversed, e.g. character 0 |
+ // has cluster index 5 in a run of 6 characters. |
+ unsigned numCharacters = 0; |
+ unsigned numGlyphsToInsert = 0; |
+ unsigned startIndex = 0; |
+ if (HB_DIRECTION_IS_FORWARD(hb_buffer_get_direction(harfBuzzBuffer))) { |
+ startIndex = currentQueueItem.m_startIndex + glyphInfo[lastChangePosition].cluster; |
+ if (glyphIndex == numGlyphs) { |
+ numCharacters = currentQueueItem.m_numCharacters - glyphInfo[lastChangePosition].cluster; |
+ numGlyphsToInsert = numGlyphs - lastChangePosition; |
+ } else { |
+ numCharacters = glyphInfo[glyphIndex].cluster - glyphInfo[lastChangePosition].cluster; |
+ numGlyphsToInsert = glyphIndex - lastChangePosition; |
+ } |
+ } else { |
+ // Direction Backwards |
+ startIndex = currentQueueItem.m_startIndex + glyphInfo[glyphIndex - 1].cluster; |
+ if (lastChangePosition == 0) { |
+ numCharacters = currentQueueItem.m_numCharacters - glyphInfo[glyphIndex - 1].cluster; |
+ } else { |
+ numCharacters = glyphInfo[lastChangePosition - 1].cluster - glyphInfo[glyphIndex - 1].cluster; |
+ } |
+ numGlyphsToInsert = glyphIndex - lastChangePosition; |
+ } |
- for (size_t j = i + 1; j < length; j++) { |
- if (runs[j].script != USCRIPT_COMMON |
- && runs[j].script != USCRIPT_INHERITED) { |
- nextResolvedRun = j; |
- break; |
+ if (currentClusterResult == Shaped && !isLastResort) { |
+ // Now it's clear that we need to continue processing. |
+ if (!fontCycleQueued) { |
+ appendToHolesQueue(HolesQueueNextFont, 0, 0); |
+ fontCycleQueued = true; |
+ } |
+ |
+ // Here we need to put character positions. |
+ ASSERT(numCharacters); |
+ appendToHolesQueue(HolesQueueRange, startIndex, numCharacters); |
} |
- } |
-} |
-static inline bool resolveCandidateRuns(Vector<CandidateRun>& runs) |
-{ |
- UScriptCode scriptExtensions[USCRIPT_CODE_LIMIT]; |
- UErrorCode errorCode = U_ZERO_ERROR; |
- size_t length = runs.size(); |
- for (size_t i = 0; i < length; i++) { |
- CandidateRun& run = runs[i]; |
- size_t nextResolvedRun = 0; |
- |
- if (run.script == USCRIPT_INHERITED) |
- run.script = i > 0 ? runs[i - 1].script : USCRIPT_COMMON; |
- |
- int extensionsLength = uscript_getScriptExtensions(run.character, |
- scriptExtensions, sizeof(scriptExtensions) / sizeof(scriptExtensions[0]), |
- &errorCode); |
- if (U_FAILURE(errorCode)) |
- return false; |
- |
- resolveRunBasedOnScriptExtensions(runs, run, i, length, |
- scriptExtensions, extensionsLength, nextResolvedRun); |
- resolveRunBasedOnScriptValue(runs, run, i, length, |
- nextResolvedRun); |
- for (size_t j = i; j < nextResolvedRun; j++) |
- runs[j].script = runs[nextResolvedRun].script; |
- |
- i = std::max(i, nextResolvedRun); |
+ // If numCharacters is 0, that means we hit a NotDef before shaping the |
+ // whole grapheme. We do not append it here. For the next glyph we |
+ // encounter, atChange will be true, and the characters corresponding to |
+ // the grapheme will be added to the TODO queue again, attempting to |
+ // shape the whole grapheme with the next font. |
+ // When we're getting here with the last resort font, we have no other |
+ // choice than adding boxes to the ShapeResult. |
+ if ((currentClusterResult == NotDef && numCharacters) || isLastResort) { |
+ // Here we need to specify glyph positions. |
+ OwnPtr<ShapeResult::RunInfo> run = adoptPtr(new ShapeResult::RunInfo(currentFont, |
+ TextDirectionToHBDirection(m_textRun.direction(), |
+ m_font->fontDescription().orientation(), currentFont), |
+ ICUScriptToHBScript(currentRunScript), |
+ startIndex, |
+ numGlyphsToInsert, numCharacters)); |
+ insertRunIntoShapeResult(shapeResult, run.release(), lastChangePosition, numGlyphsToInsert, currentQueueItem.m_startIndex, harfBuzzBuffer); |
+ } |
+ lastChangePosition = glyphIndex; |
} |
return true; |
} |
-// For ideographic (CJK) documents, 90-95% of calls from width() are one character length |
-// because most characters have break opportunities both before and after. |
-bool HarfBuzzShaper::createHarfBuzzRunsForSingleCharacter() |
+static inline const SimpleFontData* fontDataAdjustedForOrientation(const SimpleFontData* originalFont, |
+ FontOrientation runOrientation, |
+ OrientationIterator::RenderOrientation renderOrientation) |
{ |
- ASSERT(m_normalizedBufferLength == 1); |
- UChar32 character = m_normalizedBuffer[0]; |
- if (!U16_IS_SINGLE(character)) |
- return false; |
- const SimpleFontData* fontData = m_font->glyphDataForCharacter(character, false, m_textRun.normalizeSpace()).fontData; |
- UErrorCode errorCode = U_ZERO_ERROR; |
- UScriptCode script = uscript_getScript(character, &errorCode); |
- if (U_FAILURE(errorCode)) |
- return false; |
- addHarfBuzzRun(0, 1, fontData, script); |
- return true; |
+ if (!isVerticalBaseline(runOrientation)) |
+ return originalFont; |
+ |
+ if (runOrientation == FontOrientation::VerticalRotated |
+ || (runOrientation == FontOrientation::VerticalMixed && renderOrientation == OrientationIterator::OrientationRotateSideways)) |
+ return originalFont->verticalRightOrientationFontData().get(); |
+ |
+ return originalFont; |
} |
-bool HarfBuzzShaper::createHarfBuzzRuns() |
+bool HarfBuzzShaper::collectFallbackHintChars(Vector<UChar32>& hint, bool needsList) |
{ |
- if (m_normalizedBufferLength == 1) |
- return createHarfBuzzRunsForSingleCharacter(); |
- |
- Vector<CandidateRun> candidateRuns; |
- if (!collectCandidateRuns(m_normalizedBuffer.get(), |
- m_normalizedBufferLength, m_font, &candidateRuns, m_textRun.normalizeSpace())) |
+ if (!m_holesQueue.size()) |
return false; |
- if (!resolveCandidateRuns(candidateRuns)) |
- return false; |
+ hint.clear(); |
- size_t length = candidateRuns.size(); |
- for (size_t i = 0; i < length; ) { |
- CandidateRun& run = candidateRuns[i]; |
- CandidateRun lastMatchingRun = run; |
- for (i++; i < length; i++) { |
- if (candidateRuns[i].script != run.script |
- || candidateRuns[i].fontData != run.fontData) |
+ size_t numCharsAdded = 0; |
+ for (auto it = m_holesQueue.begin(); it != m_holesQueue.end(); ++it) { |
+ if (it->m_action == HolesQueueNextFont) |
+ break; |
+ |
+ UChar32 hintChar; |
+ RELEASE_ASSERT(it->m_startIndex + it->m_numCharacters <= m_normalizedBufferLength); |
+ UTF16TextIterator iterator(m_normalizedBuffer.get() + it->m_startIndex, it->m_numCharacters); |
+ while (iterator.consume(hintChar)) { |
+ hint.append(hintChar); |
+ numCharsAdded++; |
+ if (!needsList) |
break; |
- lastMatchingRun = candidateRuns[i]; |
+ iterator.advance(); |
} |
- addHarfBuzzRun(run.start, lastMatchingRun.end, run.fontData, run.script); |
} |
- return !m_harfBuzzRuns.isEmpty(); |
-} |
- |
-// A port of hb_icu_script_to_script because harfbuzz on CrOS is built |
-// without hb-icu. See http://crbug.com/356929 |
-static inline hb_script_t ICUScriptToHBScript(UScriptCode script) |
-{ |
- if (UNLIKELY(script == USCRIPT_INVALID_CODE)) |
- return HB_SCRIPT_INVALID; |
- |
- return hb_script_from_string(uscript_getShortName(script), -1); |
-} |
- |
-static inline hb_direction_t TextDirectionToHBDirection(TextDirection dir, FontOrientation orientation, const SimpleFontData* fontData) |
-{ |
- hb_direction_t harfBuzzDirection = isVerticalAnyUpright(orientation) && !fontData->isTextOrientationFallback() ? HB_DIRECTION_TTB : HB_DIRECTION_LTR; |
- return dir == RTL ? HB_DIRECTION_REVERSE(harfBuzzDirection) : harfBuzzDirection; |
-} |
- |
-void HarfBuzzShaper::addHarfBuzzRun(unsigned startCharacter, |
- unsigned endCharacter, const SimpleFontData* fontData, |
- UScriptCode script) |
-{ |
- ASSERT(endCharacter > startCharacter); |
- ASSERT(script != USCRIPT_INVALID_CODE); |
- |
- hb_direction_t direction = TextDirectionToHBDirection(m_textRun.direction(), |
- m_font->fontDescription().orientation(), fontData); |
- HarfBuzzRun harfBuzzRun = { |
- fontData, startCharacter, endCharacter - startCharacter, |
- direction, ICUScriptToHBScript(script) |
- }; |
- m_harfBuzzRuns.append(harfBuzzRun); |
-} |
- |
-static const uint16_t* toUint16(const UChar* src) |
-{ |
- // FIXME: This relies on undefined behavior however it works on the |
- // current versions of all compilers we care about and avoids making |
- // a copy of the string. |
- static_assert(sizeof(UChar) == sizeof(uint16_t), "UChar should be the same size as uint16_t"); |
- return reinterpret_cast<const uint16_t*>(src); |
+ return numCharsAdded > 0; |
} |
-static inline void addToHarfBuzzBufferInternal(hb_buffer_t* buffer, |
- const FontDescription& fontDescription, const UChar* normalizedBuffer, |
- unsigned startIndex, unsigned numCharacters) |
+void HarfBuzzShaper::appendToHolesQueue(HolesQueueItemAction action, |
+ unsigned startIndex, |
+ unsigned numCharacters) |
{ |
- if (fontDescription.variant() == FontVariantSmallCaps |
- && u_islower(normalizedBuffer[startIndex])) { |
- String upperText = String(normalizedBuffer + startIndex, numCharacters) |
- .upper(); |
- // TextRun is 16 bit, therefore upperText is 16 bit, even after we call |
- // makeUpper(). |
- ASSERT(!upperText.is8Bit()); |
- hb_buffer_add_utf16(buffer, toUint16(upperText.characters16()), |
- numCharacters, 0, numCharacters); |
- } else { |
- hb_buffer_add_utf16(buffer, toUint16(normalizedBuffer + startIndex), |
- numCharacters, 0, numCharacters); |
- } |
+ m_holesQueue.append(HolesQueueItem(action, startIndex, numCharacters)); |
} |
-PassRefPtr<ShapeResult> HarfBuzzShaper::shapeHarfBuzzRuns() |
+PassRefPtr<ShapeResult> HarfBuzzShaper::shapeResult() |
{ |
RefPtr<ShapeResult> result = ShapeResult::create(m_font, |
m_normalizedBufferLength, m_textRun.direction()); |
@@ -1047,61 +1049,111 @@ PassRefPtr<ShapeResult> HarfBuzzShaper::shapeHarfBuzzRuns() |
CString locale = localeString.latin1(); |
const hb_language_t language = hb_language_from_string(locale.data(), locale.length()); |
- result->m_runs.resize(m_harfBuzzRuns.size()); |
- for (unsigned i = 0; i < m_harfBuzzRuns.size(); ++i) { |
- unsigned runIndex = m_textRun.rtl() ? m_harfBuzzRuns.size() - i - 1 : i; |
- const HarfBuzzRun* currentRun = &m_harfBuzzRuns[runIndex]; |
- |
- const SimpleFontData* currentFontData = currentRun->m_fontData; |
- FontPlatformData* platformData = const_cast<FontPlatformData*>(¤tFontData->platformData()); |
- HarfBuzzFace* face = platformData->harfBuzzFace(); |
- if (!face) |
- return nullptr; |
- |
- hb_buffer_set_language(harfBuzzBuffer.get(), language); |
- hb_buffer_set_script(harfBuzzBuffer.get(), currentRun->m_script); |
- hb_buffer_set_direction(harfBuzzBuffer.get(), currentRun->m_direction); |
- |
- // Add a space as pre-context to the buffer. This prevents showing dotted-circle |
- // for combining marks at the beginning of runs. |
- static const uint16_t preContext = spaceCharacter; |
- hb_buffer_add_utf16(harfBuzzBuffer.get(), &preContext, 1, 1, 0); |
- |
- addToHarfBuzzBufferInternal(harfBuzzBuffer.get(), |
- fontDescription, m_normalizedBuffer.get(), currentRun->m_startIndex, |
- currentRun->m_numCharacters); |
- |
- 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()); |
- shapeResult(result.get(), i, currentRun, harfBuzzBuffer.get()); |
+ RunSegmenter::RunSegmenterRange segmentRange = { |
+ 0, |
+ 0, |
+ USCRIPT_INVALID_CODE, |
+ OrientationIterator::OrientationInvalid, |
+ SmallCapsIterator::SmallCapsSameCase }; |
+ RunSegmenter runSegmenter( |
+ m_normalizedBuffer.get(), |
+ m_normalizedBufferLength, |
+ m_font->fontDescription().orientation(), |
+ fontDescription.variant()); |
+ |
+ Vector<UChar32> fallbackCharsHint; |
+ |
+ // TODO: Check whether this treatAsZerowidthspace from the previous script |
+ // segmentation plays a role here, does the new scriptRuniterator handle that correctly? |
+ while (runSegmenter.consume(&segmentRange)) { |
+ RefPtr<FontFallbackIterator> fallbackIterator = m_font->createFontFallbackIterator(); |
+ |
+ appendToHolesQueue(HolesQueueNextFont, 0, 0); |
+ appendToHolesQueue(HolesQueueRange, segmentRange.start, segmentRange.end - segmentRange.start); |
+ |
+ const SimpleFontData* currentFont = nullptr; |
+ unsigned currentFontRangeFrom = 0; |
+ unsigned currentFontRangeTo = 0; |
+ |
+ bool fontCycleQueued = false; |
+ while (m_holesQueue.size()) { |
+ HolesQueueItem currentQueueItem = m_holesQueue.takeFirst(); |
+ |
+ if (currentQueueItem.m_action == HolesQueueNextFont) { |
+ // For now, we're building a character list with which we probe |
+ // for needed fonts depending on the declared unicode-range of a |
+ // segmented CSS font. Alternatively, we can build a fake font |
+ // for the shaper and check whether any glyphs were found, or |
+ // define a new API on the shaper which will give us coverage |
+ // information? |
+ if (!collectFallbackHintChars(fallbackCharsHint, fallbackIterator->needsHintList())) { |
+ // Give up shaping since we cannot retrieve a font fallback |
+ // font without a hintlist. |
+ m_holesQueue.clear(); |
+ break; |
+ } |
+ |
+ FontDataRange nextFontDataRange = fallbackIterator->next(fallbackCharsHint); |
+ currentFont = nextFontDataRange.fontData().get(); |
+ currentFontRangeFrom = nextFontDataRange.from(); |
+ currentFontRangeTo = nextFontDataRange.to(); |
+ if (!currentFont) { |
+ ASSERT(!m_holesQueue.size()); |
+ break; |
+ } |
+ fontCycleQueued = false; |
+ continue; |
+ } |
- hb_buffer_reset(harfBuzzBuffer.get()); |
+ // TODO crbug.com/522964: Only use smallCapsFontData when the font does not support true smcp. The spec |
+ // says: "To match the surrounding text, a font may provide alternate glyphs for caseless characters when |
+ // these features are enabled but when a user agent simulates small capitals, it must not attempt to |
+ // simulate alternates for codepoints which are considered caseless." |
+ const SimpleFontData* smallcapsAdjustedFont = segmentRange.smallCapsBehavior == SmallCapsIterator::SmallCapsUppercaseNeeded |
+ ? currentFont->smallCapsFontData(fontDescription).get() |
+ : currentFont; |
+ |
+ // Compatibility with SimpleFontData approach of keeping a flag for overriding drawing direction. |
+ // TODO: crbug.com/506224 This should go away in favor of storing that information elsewhere, for example in |
+ // ShapeResult. |
+ const SimpleFontData* directionAndSmallCapsAdjustedFont = fontDataAdjustedForOrientation(smallcapsAdjustedFont, |
+ m_font->fontDescription().orientation(), |
+ segmentRange.renderOrientation); |
+ |
+ if (!shapeRange(harfBuzzBuffer.get(), |
+ currentQueueItem.m_startIndex, |
+ currentQueueItem.m_numCharacters, |
+ directionAndSmallCapsAdjustedFont, |
+ currentFontRangeFrom, |
+ currentFontRangeTo, |
+ segmentRange.script, |
+ language)) |
+ WTF_LOG_ERROR("Shaping range failed."); |
+ |
+ if (!extractShapeResults(harfBuzzBuffer.get(), |
+ result.get(), |
+ fontCycleQueued, |
+ currentQueueItem, |
+ directionAndSmallCapsAdjustedFont, |
+ segmentRange.script, |
+ !fallbackIterator->hasNext())) |
+ WTF_LOG_ERROR("Shape result extraction failed."); |
+ |
+ hb_buffer_reset(harfBuzzBuffer.get()); |
+ } |
} |
- |
- // We should have consumed all expansion opportunities. |
- // Failures here means that our logic does not match to the one in expansionOpportunityCount(). |
- // FIXME: Ideally, we should ASSERT(!m_expansionOpportunityCount) here to ensure that, |
- // or unify the two logics (and the one in SimplePath too,) but there are some cases where our impl |
- // does not support justification very well yet such as U+3099, and it'll cause the ASSERT to fail. |
- // It's to be fixed because they're very rarely used, and a broken justification is still somewhat readable. |
- |
return result.release(); |
} |
-void HarfBuzzShaper::shapeResult(ShapeResult* result, unsigned index, |
- const HarfBuzzRun* currentRun, hb_buffer_t* harfBuzzBuffer) |
+// TODO crbug.com/542701: This should be a method on ShapeResult. |
+void HarfBuzzShaper::insertRunIntoShapeResult(ShapeResult* result, |
+ PassOwnPtr<ShapeResult::RunInfo> runToInsert, unsigned startGlyph, unsigned numGlyphs, |
+ int bufferStartCharIndex, hb_buffer_t* harfBuzzBuffer) |
{ |
- unsigned numGlyphs = hb_buffer_get_length(harfBuzzBuffer); |
- if (!numGlyphs) { |
- result->m_runs[index] = nullptr; |
- return; |
- } |
+ ASSERT(numGlyphs > 0); |
+ OwnPtr<ShapeResult::RunInfo> run(runToInsert); |
- OwnPtr<ShapeResult::RunInfo> run = adoptPtr(new ShapeResult::RunInfo(currentRun->m_fontData, |
- currentRun->m_direction, currentRun->m_script, currentRun->m_startIndex, |
- numGlyphs, currentRun->m_numCharacters)); |
- |
- const SimpleFontData* currentFontData = currentRun->m_fontData; |
+ const SimpleFontData* currentFontData = run->m_fontData.get(); |
hb_glyph_info_t* glyphInfos = hb_buffer_get_glyph_infos(harfBuzzBuffer, 0); |
hb_glyph_position_t* glyphPositions = hb_buffer_get_glyph_positions(harfBuzzBuffer, 0); |
@@ -1111,21 +1163,29 @@ void HarfBuzzShaper::shapeResult(ShapeResult* result, unsigned index, |
float* directionOffset = m_font->fontDescription().isVerticalAnyUpright() ? &offsetY : &offsetX; |
// HarfBuzz returns result in visual order, no need to flip for RTL. |
- for (size_t i = 0; i < numGlyphs; ++i) { |
+ for (unsigned i = 0; i < numGlyphs; ++i) { |
bool runEnd = i + 1 == numGlyphs; |
- uint16_t glyph = glyphInfos[i].codepoint; |
- offsetX = harfBuzzPositionToFloat(glyphPositions[i].x_offset); |
- offsetY = -harfBuzzPositionToFloat(glyphPositions[i].y_offset); |
+ uint16_t glyph = glyphInfos[startGlyph + i].codepoint; |
+ offsetX = harfBuzzPositionToFloat(glyphPositions[startGlyph + i].x_offset); |
+ offsetY = -harfBuzzPositionToFloat(glyphPositions[startGlyph + i].y_offset); |
// One out of x_advance and y_advance is zero, depending on |
// whether the buffer direction is horizontal or vertical. |
- float advance = harfBuzzPositionToFloat(glyphPositions[i].x_advance - glyphPositions[i].y_advance); |
- unsigned currentCharacterIndex = currentRun->m_startIndex + glyphInfos[i].cluster; |
+ float advance = harfBuzzPositionToFloat(glyphPositions[startGlyph + i].x_advance - glyphPositions[startGlyph + i].y_advance); |
+ unsigned currentCharacterIndex = bufferStartCharIndex + glyphInfos[startGlyph + i].cluster; |
RELEASE_ASSERT(m_normalizedBufferLength > currentCharacterIndex); |
- bool isClusterEnd = runEnd || glyphInfos[i].cluster != glyphInfos[i + 1].cluster; |
+ bool isClusterEnd = runEnd || glyphInfos[startGlyph + i].cluster != glyphInfos[startGlyph + i + 1].cluster; |
float spacing = 0; |
- run->m_glyphData[i].characterIndex = glyphInfos[i].cluster; |
+ // The characterIndex of one ShapeResult run is normalized to the run's |
+ // startIndex and length. TODO crbug.com/542703: Consider changing that |
+ // and instead pass the whole run to hb_buffer_t each time. |
+ run->m_glyphData.resize(numGlyphs); |
+ if (HB_DIRECTION_IS_FORWARD(hb_buffer_get_direction(harfBuzzBuffer))) { |
+ run->m_glyphData[i].characterIndex = glyphInfos[startGlyph + i].cluster - glyphInfos[startGlyph].cluster; |
+ } else { |
+ run->m_glyphData[i].characterIndex = glyphInfos[startGlyph + i].cluster - glyphInfos[startGlyph + numGlyphs - 1].cluster; |
+ } |
if (isClusterEnd) |
spacing += adjustSpacing(run.get(), i, currentCharacterIndex, *directionOffset, totalAdvance); |
@@ -1151,11 +1211,33 @@ void HarfBuzzShaper::shapeResult(ShapeResult* result, unsigned index, |
result->m_glyphBoundingBox.unite(glyphBounds); |
glyphOrigin += FloatSize(advance + offsetX, offsetY); |
} |
- |
run->m_width = std::max(0.0f, totalAdvance); |
result->m_width += run->m_width; |
result->m_numGlyphs += numGlyphs; |
- result->m_runs[index] = run.release(); |
+ |
+ // The runs are stored in result->m_runs in visual order. For LTR, we place |
+ // the run to be inserted before the next run with a bigger character |
+ // start index. For RTL, we place the run before the next run with a lower |
+ // character index. Otherwise, for both directions, at the end. |
+ if (HB_DIRECTION_IS_FORWARD(run->m_direction)) { |
+ for (size_t pos = 0; pos < result->m_runs.size(); ++pos) { |
+ if (result->m_runs.at(pos)->m_startIndex > run->m_startIndex) { |
+ result->m_runs.insert(pos, run.release()); |
+ break; |
+ } |
+ } |
+ } else { |
+ for (size_t pos = 0; pos < result->m_runs.size(); ++pos) { |
+ if (result->m_runs.at(pos)->m_startIndex < run->m_startIndex) { |
+ result->m_runs.insert(pos, run.release()); |
+ break; |
+ } |
+ } |
+ } |
+ // If we didn't find an existing slot to place it, append. |
+ if (run) { |
+ result->m_runs.append(run.release()); |
+ } |
} |
PassRefPtr<ShapeResult> ShapeResult::createForTabulationCharacters(const Font* font, |
@@ -1239,4 +1321,5 @@ float HarfBuzzShaper::adjustSpacing(ShapeResult::RunInfo* run, size_t glyphIndex |
return spacing; |
} |
+ |
} // namespace blink |