Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(529)

Unified Diff: third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaper.cpp

Issue 1397423004: Improve shaping segmentation for grapheme cluster based font fallback (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: merge TestExpectations with the HarfBuzz rebaselines Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaper.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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*>(&currentFontData->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
« no previous file with comments | « third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaper.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698