/* * Copyright (c) 2012 Google Inc. All rights reserved. * Copyright (C) 2013 BlackBerry Limited. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "sky/engine/platform/fonts/harfbuzz/HarfBuzzShaper.h" #include #include #include #include "gen/sky/platform/RuntimeEnabledFeatures.h" #include "hb.h" #include "sky/engine/platform/LayoutUnit.h" #include "sky/engine/platform/fonts/Character.h" #include "sky/engine/platform/fonts/Font.h" #include "sky/engine/platform/fonts/GlyphBuffer.h" #include "sky/engine/platform/fonts/harfbuzz/HarfBuzzFace.h" #include "sky/engine/platform/text/SurrogatePairAwareTextIterator.h" #include "sky/engine/platform/text/TextBreakIterator.h" #include "sky/engine/wtf/Compiler.h" #include "sky/engine/wtf/MathExtras.h" #include "sky/engine/wtf/unicode/Unicode.h" #include #include #include namespace blink { template class HarfBuzzScopedPtr { public: typedef void (*DestroyFunction)(T*); HarfBuzzScopedPtr(T* ptr, DestroyFunction destroy) : m_ptr(ptr) , m_destroy(destroy) { ASSERT(m_destroy); } ~HarfBuzzScopedPtr() { if (m_ptr) (*m_destroy)(m_ptr); } T* get() { return m_ptr; } void set(T* ptr) { m_ptr = ptr; } private: T* m_ptr; DestroyFunction m_destroy; }; static const unsigned cHarfBuzzCacheMaxSize = 256; struct CachedShapingResultsLRUNode; struct CachedShapingResults; typedef std::map CachedShapingResultsMap; typedef std::list CachedShapingResultsLRU; struct CachedShapingResults { CachedShapingResults(hb_buffer_t* harfBuzzBuffer, const Font* runFont, hb_direction_t runDir, const String& newLocale); ~CachedShapingResults(); hb_buffer_t* buffer; Font font; hb_direction_t dir; String locale; CachedShapingResultsLRU::iterator lru; }; struct CachedShapingResultsLRUNode { CachedShapingResultsLRUNode(const CachedShapingResultsMap::iterator& cacheEntry); ~CachedShapingResultsLRUNode(); CachedShapingResultsMap::iterator entry; }; CachedShapingResults::CachedShapingResults(hb_buffer_t* harfBuzzBuffer, const Font* fontData, hb_direction_t dirData, const String& newLocale) : buffer(harfBuzzBuffer) , font(*fontData) , dir(dirData) , locale(newLocale) { } CachedShapingResults::~CachedShapingResults() { hb_buffer_destroy(buffer); } CachedShapingResultsLRUNode::CachedShapingResultsLRUNode(const CachedShapingResultsMap::iterator& cacheEntry) : entry(cacheEntry) { } CachedShapingResultsLRUNode::~CachedShapingResultsLRUNode() { } class HarfBuzzRunCache { public: HarfBuzzRunCache(); ~HarfBuzzRunCache(); CachedShapingResults* find(const std::wstring& key) const; void remove(CachedShapingResults* node); void moveToBack(CachedShapingResults* node); bool insert(const std::wstring& key, CachedShapingResults* run); private: CachedShapingResultsMap m_harfBuzzRunMap; CachedShapingResultsLRU m_harfBuzzRunLRU; }; HarfBuzzRunCache::HarfBuzzRunCache() { } HarfBuzzRunCache::~HarfBuzzRunCache() { for (CachedShapingResultsMap::iterator it = m_harfBuzzRunMap.begin(); it != m_harfBuzzRunMap.end(); ++it) delete it->second; for (CachedShapingResultsLRU::iterator it = m_harfBuzzRunLRU.begin(); it != m_harfBuzzRunLRU.end(); ++it) delete *it; } bool HarfBuzzRunCache::insert(const std::wstring& key, CachedShapingResults* data) { std::pair results = m_harfBuzzRunMap.insert(CachedShapingResultsMap::value_type(key, data)); if (!results.second) return false; CachedShapingResultsLRUNode* node = new CachedShapingResultsLRUNode(results.first); m_harfBuzzRunLRU.push_back(node); data->lru = --m_harfBuzzRunLRU.end(); if (m_harfBuzzRunMap.size() > cHarfBuzzCacheMaxSize) { CachedShapingResultsLRUNode* lru = m_harfBuzzRunLRU.front(); CachedShapingResults* foo = lru->entry->second; m_harfBuzzRunMap.erase(lru->entry); m_harfBuzzRunLRU.pop_front(); delete foo; delete lru; } return true; } inline CachedShapingResults* HarfBuzzRunCache::find(const std::wstring& key) const { CachedShapingResultsMap::const_iterator it = m_harfBuzzRunMap.find(key); return it != m_harfBuzzRunMap.end() ? it->second : 0; } inline void HarfBuzzRunCache::remove(CachedShapingResults* node) { CachedShapingResultsLRUNode* lruNode = *node->lru; m_harfBuzzRunLRU.erase(node->lru); m_harfBuzzRunMap.erase(lruNode->entry); delete lruNode; delete node; } inline void HarfBuzzRunCache::moveToBack(CachedShapingResults* node) { CachedShapingResultsLRUNode* lruNode = *node->lru; m_harfBuzzRunLRU.erase(node->lru); m_harfBuzzRunLRU.push_back(lruNode); node->lru = --m_harfBuzzRunLRU.end(); } HarfBuzzRunCache& harfBuzzRunCache() { DEFINE_STATIC_LOCAL(HarfBuzzRunCache, globalHarfBuzzRunCache, ()); return globalHarfBuzzRunCache; } static inline float harfBuzzPositionToFloat(hb_position_t value) { return static_cast(value) / (1 << 16); } static inline unsigned countGraphemesInCluster(const UChar* normalizedBuffer, unsigned normalizedBufferLength, uint16_t startIndex, uint16_t endIndex) { if (startIndex > endIndex) { uint16_t tempIndex = startIndex; startIndex = endIndex; endIndex = tempIndex; } uint16_t length = endIndex - startIndex; ASSERT(static_cast(startIndex + length) <= normalizedBufferLength); TextBreakIterator* cursorPosIterator = cursorMovementIterator(&normalizedBuffer[startIndex], length); int cursorPos = cursorPosIterator->current(); int numGraphemes = -1; while (0 <= cursorPos) { cursorPos = cursorPosIterator->next(); numGraphemes++; } return numGraphemes < 0 ? 0 : numGraphemes; } inline HarfBuzzShaper::HarfBuzzRun::HarfBuzzRun(const SimpleFontData* fontData, unsigned startIndex, unsigned numCharacters, hb_direction_t direction, hb_script_t script) : m_fontData(fontData) , m_startIndex(startIndex) , m_numCharacters(numCharacters) , m_numGlyphs(0) , m_direction(direction) , m_script(script) , m_width(0) { } inline HarfBuzzShaper::HarfBuzzRun::HarfBuzzRun(const HarfBuzzRun& rhs) : m_fontData(rhs.m_fontData) , m_startIndex(rhs.m_startIndex) , m_numCharacters(rhs.m_numCharacters) , m_numGlyphs(rhs.m_numGlyphs) , m_direction(rhs.m_direction) , m_script(rhs.m_script) , m_glyphs(rhs.m_glyphs) , m_advances(rhs.m_advances) , m_glyphToCharacterIndexes(rhs.m_glyphToCharacterIndexes) , m_offsets(rhs.m_offsets) , m_width(rhs.m_width) { } HarfBuzzShaper::HarfBuzzRun::~HarfBuzzRun() { } inline void HarfBuzzShaper::HarfBuzzRun::applyShapeResult(hb_buffer_t* harfBuzzBuffer) { m_numGlyphs = hb_buffer_get_length(harfBuzzBuffer); m_glyphs.resize(m_numGlyphs); m_advances.resize(m_numGlyphs); m_glyphToCharacterIndexes.resize(m_numGlyphs); m_offsets.resize(m_numGlyphs); } inline void HarfBuzzShaper::HarfBuzzRun::setGlyphAndPositions(unsigned index, uint16_t glyphId, float advance, float offsetX, float offsetY) { m_glyphs[index] = glyphId; m_advances[index] = advance; m_offsets[index] = FloatSize(offsetX, offsetY); } int HarfBuzzShaper::HarfBuzzRun::characterIndexForXPosition(float targetX) { ASSERT(targetX <= m_width); float currentX = 0; float currentAdvance = m_advances[0]; unsigned glyphIndex = 0; // Sum up advances that belong to a character. while (glyphIndex < m_numGlyphs - 1 && m_glyphToCharacterIndexes[glyphIndex] == m_glyphToCharacterIndexes[glyphIndex + 1]) currentAdvance += m_advances[++glyphIndex]; currentAdvance = currentAdvance / 2.0; if (targetX <= currentAdvance) return rtl() ? m_numCharacters : 0; currentX = currentAdvance; ++glyphIndex; while (glyphIndex < m_numGlyphs) { unsigned prevCharacterIndex = m_glyphToCharacterIndexes[glyphIndex - 1]; float prevAdvance = currentAdvance; currentAdvance = m_advances[glyphIndex]; while (glyphIndex < m_numGlyphs - 1 && m_glyphToCharacterIndexes[glyphIndex] == m_glyphToCharacterIndexes[glyphIndex + 1]) currentAdvance += m_advances[++glyphIndex]; currentAdvance = currentAdvance / 2.0; float nextX = currentX + prevAdvance + currentAdvance; if (currentX <= targetX && targetX <= nextX) return rtl() ? prevCharacterIndex : m_glyphToCharacterIndexes[glyphIndex]; currentX = nextX; ++glyphIndex; } return rtl() ? 0 : m_numCharacters; } float HarfBuzzShaper::HarfBuzzRun::xPositionForOffset(unsigned offset) { ASSERT(offset < m_numCharacters); unsigned glyphIndex = 0; float position = 0; if (rtl()) { while (glyphIndex < m_numGlyphs && m_glyphToCharacterIndexes[glyphIndex] > offset) { position += m_advances[glyphIndex]; ++glyphIndex; } // For RTL, we need to return the right side boundary of the character. // Add advance of glyphs which are part of the character. while (glyphIndex < m_numGlyphs - 1 && m_glyphToCharacterIndexes[glyphIndex] == m_glyphToCharacterIndexes[glyphIndex + 1]) { position += m_advances[glyphIndex]; ++glyphIndex; } position += m_advances[glyphIndex]; } else { while (glyphIndex < m_numGlyphs && m_glyphToCharacterIndexes[glyphIndex] < offset) { position += m_advances[glyphIndex]; ++glyphIndex; } } return position; } static void normalizeCharacters(const TextRun& run, unsigned length, UChar* destination, unsigned* destinationLength) { unsigned position = 0; bool error = false; const UChar* source; String stringFor8BitRun; if (run.is8Bit()) { stringFor8BitRun = String::make16BitFrom8BitSource(run.characters8(), run.length()); source = stringFor8BitRun.characters16(); } else source = run.characters16(); *destinationLength = 0; while (position < length) { UChar32 character; U16_NEXT(source, position, length, character); // Don't normalize tabs as they are not treated as spaces for word-end. if (Character::treatAsSpace(character) && character != characterTabulation) character = space; else if (Character::treatAsZeroWidthSpaceInComplexScript(character)) character = zeroWidthSpace; U16_APPEND(destination, *destinationLength, length, character, error); ASSERT_UNUSED(error, !error); } } HarfBuzzShaper::HarfBuzzShaper(const Font* font, const TextRun& run, ForTextEmphasisOrNot forTextEmphasis, HashSet* fallbackFonts) : m_font(font) , m_normalizedBufferLength(0) , m_run(run) , m_wordSpacingAdjustment(font->fontDescription().wordSpacing()) , m_padding(0) , m_padPerWordBreak(0) , m_padError(0) , m_letterSpacing(font->fontDescription().letterSpacing()) , m_fromIndex(0) , m_toIndex(m_run.length()) , m_forTextEmphasis(forTextEmphasis) , m_glyphBoundingBox(std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::min(), std::numeric_limits::max()) , m_fallbackFonts(fallbackFonts) { m_normalizedBuffer = adoptArrayPtr(new UChar[m_run.length() + 1]); normalizeCharacters(m_run, m_run.length(), m_normalizedBuffer.get(), &m_normalizedBufferLength); setPadding(m_run.expansion()); setFontFeatures(); } // In complex text word-spacing affects each line-break, space (U+0020) and non-breaking space (U+00A0). static inline bool isCodepointSpace(UChar c) { return c == space || c == noBreakSpace || c == newlineCharacter; } static inline bool isWordEnd(const UChar* normalizedBuffer, unsigned index) { // This could refer a high-surrogate, but should work. return index && isCodepointSpace(normalizedBuffer[index]); } int HarfBuzzShaper::determineWordBreakSpacing() { int wordBreakSpacing = m_wordSpacingAdjustment; if (m_padding > 0) { int toPad = roundf(m_padPerWordBreak + m_padError); m_padError += m_padPerWordBreak - toPad; if (m_padding < toPad) toPad = m_padding; m_padding -= toPad; wordBreakSpacing += toPad; } return wordBreakSpacing; } // setPadding sets a number of pixels to be distributed across the TextRun. // WebKit uses this to justify text. void HarfBuzzShaper::setPadding(int padding) { m_padding = padding; m_padError = 0; if (!m_padding) return; // If we have padding to distribute, then we try to give an equal // amount to each space. The last space gets the smaller amount, if // any. unsigned numWordEnds = 0; for (unsigned i = 0; i < m_normalizedBufferLength; i++) { if (isWordEnd(m_normalizedBuffer.get(), i)) numWordEnds++; } if (numWordEnds) m_padPerWordBreak = m_padding / numWordEnds; else m_padPerWordBreak = 0; } void HarfBuzzShaper::setDrawRange(int from, int to) { ASSERT_WITH_SECURITY_IMPLICATION(from >= 0); ASSERT_WITH_SECURITY_IMPLICATION(to <= m_run.length()); m_fromIndex = from; m_toIndex = to; } void HarfBuzzShaper::setFontFeatures() { const FontDescription& description = m_font->fontDescription(); if (description.orientation() == Vertical) { static hb_feature_t vert = { HarfBuzzFace::vertTag, 1, 0, static_cast(-1) }; static hb_feature_t vrt2 = { HarfBuzzFace::vrt2Tag, 1, 0, static_cast(-1) }; m_features.append(vert); m_features.append(vrt2); } static hb_feature_t noKern = { HB_TAG('k', 'e', 'r', 'n'), 0, 0, static_cast(-1) }; static hb_feature_t noVkrn = { HB_TAG('v', 'k', 'r', 'n'), 0, 0, static_cast(-1) }; switch (description.kerning()) { case FontDescription::NormalKerning: // kern/vkrn are enabled by default break; case FontDescription::NoneKerning: m_features.append(description.orientation() == Vertical ? noVkrn : noKern); break; case FontDescription::AutoKerning: break; } static hb_feature_t noClig = { HB_TAG('c', 'l', 'i', 'g'), 0, 0, static_cast(-1) }; static hb_feature_t noLiga = { HB_TAG('l', 'i', 'g', 'a'), 0, 0, static_cast(-1) }; switch (description.commonLigaturesState()) { case FontDescription::DisabledLigaturesState: m_features.append(noLiga); m_features.append(noClig); break; case FontDescription::EnabledLigaturesState: // liga and clig are on by default break; case FontDescription::NormalLigaturesState: break; } static hb_feature_t dlig = { HB_TAG('d', 'l', 'i', 'g'), 1, 0, static_cast(-1) }; switch (description.discretionaryLigaturesState()) { case FontDescription::DisabledLigaturesState: // dlig is off by default break; case FontDescription::EnabledLigaturesState: m_features.append(dlig); break; case FontDescription::NormalLigaturesState: break; } static hb_feature_t hlig = { HB_TAG('h', 'l', 'i', 'g'), 1, 0, static_cast(-1) }; switch (description.historicalLigaturesState()) { case FontDescription::DisabledLigaturesState: // hlig is off by default break; case FontDescription::EnabledLigaturesState: m_features.append(hlig); break; case FontDescription::NormalLigaturesState: break; } static hb_feature_t noCalt = { HB_TAG('c', 'a', 'l', 't'), 0, 0, static_cast(-1) }; switch (description.contextualLigaturesState()) { case FontDescription::DisabledLigaturesState: m_features.append(noCalt); break; case FontDescription::EnabledLigaturesState: // calt is on by default break; case FontDescription::NormalLigaturesState: break; } static hb_feature_t hwid = { HB_TAG('h', 'w', 'i', 'd'), 1, 0, static_cast(-1) }; static hb_feature_t twid = { HB_TAG('t', 'w', 'i', 'd'), 1, 0, static_cast(-1) }; static hb_feature_t qwid = { HB_TAG('d', 'w', 'i', 'd'), 1, 0, static_cast(-1) }; switch (description.widthVariant()) { case HalfWidth: m_features.append(hwid); break; case ThirdWidth: m_features.append(twid); break; case QuarterWidth: m_features.append(qwid); break; case RegularWidth: break; } FontFeatureSettings* settings = description.featureSettings(); if (!settings) return; unsigned numFeatures = settings->size(); for (unsigned i = 0; i < numFeatures; ++i) { hb_feature_t feature; const AtomicString& tag = settings->at(i).tag(); 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(-1); m_features.append(feature); } } bool HarfBuzzShaper::shape(GlyphBuffer* glyphBuffer) { if (!createHarfBuzzRuns()) return false; m_totalWidth = 0; if (!shapeHarfBuzzRuns()) return false; if (!RuntimeEnabledFeatures::subpixelFontScalingEnabled()) m_totalWidth = roundf(m_totalWidth); if (m_harfBuzzRuns.last()->hasGlyphToCharacterIndexes() && glyphBuffer && !fillGlyphBuffer(glyphBuffer)) return false; return true; } static inline int handleMultipleUChar( UChar32 character, unsigned clusterLength, const SimpleFontData* currentFontData, const UChar* currentCharacterPosition, const UChar* markCharactersEnd, const UChar* normalizedBufferEnd) { if (U_GET_GC_MASK(character) & U_GC_M_MASK) { int markLength = clusterLength; while (markCharactersEnd < normalizedBufferEnd) { UChar32 nextCharacter; int nextCharacterLength = 0; U16_NEXT(markCharactersEnd, nextCharacterLength, normalizedBufferEnd - markCharactersEnd, nextCharacter); if (!(U_GET_GC_MASK(nextCharacter) & U_GC_M_MASK)) break; markLength += nextCharacterLength; markCharactersEnd += nextCharacterLength; } if (currentFontData->canRenderCombiningCharacterSequence(currentCharacterPosition, markCharactersEnd - currentCharacterPosition)) { return markLength; } } return 0; } struct CandidateRun { UChar32 character; unsigned start; unsigned end; const SimpleFontData* fontData; UScriptCode script; }; static inline bool collectCandidateRuns(const UChar* normalizedBuffer, size_t bufferLength, const Font* font, Vector* runs) { const UChar* normalizedBufferEnd = normalizedBuffer + bufferLength; SurrogatePairAwareTextIterator iterator(normalizedBuffer, 0, bufferLength, bufferLength); UChar32 character; unsigned clusterLength = 0; unsigned startIndexOfCurrentRun = 0; if (!iterator.consume(character, clusterLength)) return false; const SimpleFontData* nextFontData = font->glyphDataForCharacter(character, false).fontData; UErrorCode errorCode = U_ZERO_ERROR; UScriptCode nextScript = uscript_getScript(character, &errorCode); if (U_FAILURE(errorCode)) return false; do { const UChar* currentCharacterPosition = iterator.characters(); const SimpleFontData* currentFontData = nextFontData; UScriptCode currentScript = nextScript; UChar32 lastCharacter = character; for (iterator.advance(clusterLength); iterator.consume(character, clusterLength); iterator.advance(clusterLength)) { if (Character::treatAsZeroWidthSpace(character)) continue; int length = handleMultipleUChar(character, clusterLength, currentFontData, currentCharacterPosition, iterator.characters() + clusterLength, normalizedBufferEnd); if (length) { clusterLength = length; continue; } nextFontData = font->glyphDataForCharacter(character, false).fontData; nextScript = uscript_getScript(character, &errorCode); if (U_FAILURE(errorCode)) return false; if (lastCharacter == zeroWidthJoiner) currentFontData = nextFontData; if ((nextFontData != currentFontData) || ((currentScript != nextScript) && (nextScript != USCRIPT_INHERITED) && (!uscript_hasScript(character, currentScript)))) break; currentCharacterPosition = iterator.characters(); lastCharacter = character; } CandidateRun run = { character, startIndexOfCurrentRun, iterator.currentCharacter(), currentFontData, currentScript }; runs->append(run); startIndexOfCurrentRun = iterator.currentCharacter(); } while (iterator.consume(character, clusterLength)); return true; } static inline bool matchesAdjacentRun(UScriptCode* scriptExtensions, int length, CandidateRun& adjacentRun) { for (int i = 0; i < length; i++) { if (scriptExtensions[i] == adjacentRun.script) return true; } return false; } static inline void resolveRunBasedOnScriptExtensions(Vector& 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; if (i > 0 && matchesAdjacentRun(scriptExtensions, extensionsLength, runs[i - 1])) { run.script = runs[i - 1].script; return; } 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; } } } static inline void resolveRunBasedOnScriptValue(Vector& runs, CandidateRun& run, size_t i, size_t length, size_t& nextResolvedRun) { if (run.script != USCRIPT_COMMON) return; if (i > 0 && runs[i - 1].script != USCRIPT_COMMON) { run.script = runs[i - 1].script; return; } for (size_t j = i + 1; j < length; j++) { if (runs[j].script != USCRIPT_COMMON && runs[j].script != USCRIPT_INHERITED) { nextResolvedRun = j; break; } } } static inline bool resolveCandidateRuns(Vector& runs) { UScriptCode scriptExtensions[8]; UErrorCode errorCode = U_ZERO_ERROR; size_t length = runs.size(); size_t nextResolvedRun = 0; for (size_t i = 0; i < length; i++) { CandidateRun& run = runs[i]; 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), &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); } return true; } bool HarfBuzzShaper::createHarfBuzzRuns() { Vector candidateRuns; if (!collectCandidateRuns(m_normalizedBuffer.get(), m_normalizedBufferLength, m_font, &candidateRuns)) return false; if (!resolveCandidateRuns(candidateRuns)) return false; 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) break; lastMatchingRun = candidateRuns[i]; } 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) { return dir == RTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR; } void HarfBuzzShaper::addHarfBuzzRun(unsigned startCharacter, unsigned endCharacter, const SimpleFontData* fontData, UScriptCode script) { ASSERT(endCharacter > startCharacter); ASSERT(script != USCRIPT_INVALID_CODE); if (m_fallbackFonts) m_fallbackFonts->add(fontData); return m_harfBuzzRuns.append(HarfBuzzRun::create(fontData, startCharacter, endCharacter - startCharacter, TextDirectionToHBDirection(m_run.direction()), ICUScriptToHBScript(script))); } 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. COMPILE_ASSERT(sizeof(UChar) == sizeof(uint16_t), UChar_is_the_same_size_as_uint16_t); return reinterpret_cast(src); } bool HarfBuzzShaper::shapeHarfBuzzRuns() { HarfBuzzScopedPtr harfBuzzBuffer(hb_buffer_create(), hb_buffer_destroy); HarfBuzzRunCache& runCache = harfBuzzRunCache(); const FontDescription& fontDescription = m_font->fontDescription(); const String& localeString = fontDescription.locale(); CString locale = localeString.latin1(); for (unsigned i = 0; i < m_harfBuzzRuns.size(); ++i) { unsigned runIndex = m_run.rtl() ? m_harfBuzzRuns.size() - i - 1 : i; HarfBuzzRun* currentRun = m_harfBuzzRuns[runIndex].get(); const SimpleFontData* currentFontData = currentRun->fontData(); if (currentFontData->isSVGFont()) return false; FontPlatformData* platformData = const_cast(¤tFontData->platformData()); HarfBuzzFace* face = platformData->harfBuzzFace(); if (!face) return false; hb_buffer_set_language(harfBuzzBuffer.get(), hb_language_from_string(locale.data(), locale.length())); hb_buffer_set_script(harfBuzzBuffer.get(), currentRun->script()); hb_buffer_set_direction(harfBuzzBuffer.get(), currentRun->direction()); const UChar* src = m_normalizedBuffer.get() + currentRun->startIndex(); std::wstring key(src, src + currentRun->numCharacters()); CachedShapingResults* cachedResults = runCache.find(key); if (cachedResults) { if (cachedResults->dir == currentRun->direction() && cachedResults->font == *m_font && cachedResults->locale == localeString) { currentRun->applyShapeResult(cachedResults->buffer); setGlyphPositionsForHarfBuzzRun(currentRun, cachedResults->buffer); hb_buffer_clear_contents(harfBuzzBuffer.get()); runCache.moveToBack(cachedResults); continue; } runCache.remove(cachedResults); } // 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 = ' '; hb_buffer_add_utf16(harfBuzzBuffer.get(), &preContext, 1, 1, 0); if (fontDescription.variant() == FontVariantSmallCaps && u_islower(m_normalizedBuffer[currentRun->startIndex()])) { String upperText = String(m_normalizedBuffer.get() + currentRun->startIndex(), currentRun->numCharacters()).upper(); ASSERT(!upperText.is8Bit()); // m_normalizedBuffer is 16 bit, therefore upperText is 16 bit, even after we call makeUpper(). hb_buffer_add_utf16(harfBuzzBuffer.get(), toUint16(upperText.characters16()), currentRun->numCharacters(), 0, currentRun->numCharacters()); } else { hb_buffer_add_utf16(harfBuzzBuffer.get(), toUint16(m_normalizedBuffer.get() + currentRun->startIndex()), currentRun->numCharacters(), 0, currentRun->numCharacters()); } if (fontDescription.orientation() == Vertical) face->setScriptForVerticalGlyphSubstitution(harfBuzzBuffer.get()); HarfBuzzScopedPtr 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()); setGlyphPositionsForHarfBuzzRun(currentRun, harfBuzzBuffer.get()); runCache.insert(key, new CachedShapingResults(harfBuzzBuffer.get(), m_font, currentRun->direction(), localeString)); harfBuzzBuffer.set(hb_buffer_create()); } return true; } void HarfBuzzShaper::setGlyphPositionsForHarfBuzzRun(HarfBuzzRun* currentRun, hb_buffer_t* harfBuzzBuffer) { const SimpleFontData* currentFontData = currentRun->fontData(); hb_glyph_info_t* glyphInfos = hb_buffer_get_glyph_infos(harfBuzzBuffer, 0); hb_glyph_position_t* glyphPositions = hb_buffer_get_glyph_positions(harfBuzzBuffer, 0); if (!currentRun->hasGlyphToCharacterIndexes()) { // FIXME: https://crbug.com/337886 ASSERT_NOT_REACHED(); return; } unsigned numGlyphs = currentRun->numGlyphs(); uint16_t* glyphToCharacterIndexes = currentRun->glyphToCharacterIndexes(); float totalAdvance = 0; FloatPoint glyphOrigin; // HarfBuzz returns the shaping result in visual order. We need not to flip for RTL. for (size_t i = 0; i < numGlyphs; ++i) { bool runEnd = i + 1 == numGlyphs; uint16_t glyph = glyphInfos[i].codepoint; float offsetX = harfBuzzPositionToFloat(glyphPositions[i].x_offset); float offsetY = -harfBuzzPositionToFloat(glyphPositions[i].y_offset); float advance = harfBuzzPositionToFloat(glyphPositions[i].x_advance); 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 && !Character::treatAsZeroWidthSpace(m_normalizedBuffer[currentCharacterIndex])) spacing += m_letterSpacing; if (isClusterEnd && isWordEnd(m_normalizedBuffer.get(), currentCharacterIndex)) spacing += determineWordBreakSpacing(); if (currentFontData->isZeroWidthSpaceGlyph(glyph)) { currentRun->setGlyphAndPositions(i, glyph, 0, 0, 0); continue; } advance += spacing; if (m_run.rtl()) { // In RTL, spacing should be added to left side of glyphs. offsetX += spacing; if (!isClusterEnd) offsetX += m_letterSpacing; } currentRun->setGlyphAndPositions(i, glyph, advance, offsetX, offsetY); FloatRect glyphBounds = currentFontData->boundsForGlyph(glyph); glyphBounds.move(glyphOrigin.x(), glyphOrigin.y()); m_glyphBoundingBox.unite(glyphBounds); glyphOrigin += FloatSize(advance + offsetX, offsetY); totalAdvance += advance; } currentRun->setWidth(totalAdvance > 0.0 ? totalAdvance : 0.0); m_totalWidth += currentRun->width(); } void HarfBuzzShaper::fillGlyphBufferFromHarfBuzzRun(GlyphBuffer* glyphBuffer, HarfBuzzRun* currentRun, float& carryAdvance) { FloatSize* offsets = currentRun->offsets(); uint16_t* glyphs = currentRun->glyphs(); float* advances = currentRun->advances(); unsigned numGlyphs = currentRun->numGlyphs(); uint16_t* glyphToCharacterIndexes = currentRun->glyphToCharacterIndexes(); FloatSize runStartOffset = FloatSize(); if (m_run.rtl()) { for (unsigned i = 0; i < numGlyphs; ++i) { uint16_t currentCharacterIndex = currentRun->startIndex() + glyphToCharacterIndexes[i]; if (currentCharacterIndex >= m_toIndex) { carryAdvance += advances[i]; } else if (currentCharacterIndex >= m_fromIndex) { runStartOffset = HB_DIRECTION_IS_HORIZONTAL(currentRun->direction()) ? FloatSize(carryAdvance, 0) : FloatSize(0, carryAdvance); glyphBuffer->add(glyphs[i], currentRun->fontData(), runStartOffset + offsets[i], carryAdvance + advances[i]); carryAdvance = 0; } } } else { for (unsigned i = 0; i < numGlyphs; ++i) { uint16_t currentCharacterIndex = currentRun->startIndex() + glyphToCharacterIndexes[i]; if (currentCharacterIndex < m_fromIndex) { carryAdvance += advances[i]; } else if (currentCharacterIndex < m_toIndex) { runStartOffset = HB_DIRECTION_IS_HORIZONTAL(currentRun->direction()) ? FloatSize(carryAdvance, 0) : FloatSize(0, carryAdvance); glyphBuffer->add(glyphs[i], currentRun->fontData(), runStartOffset + offsets[i], carryAdvance + advances[i]); carryAdvance = 0; } } } } void HarfBuzzShaper::fillGlyphBufferForTextEmphasis(GlyphBuffer* glyphBuffer, HarfBuzzRun* currentRun) { // FIXME: Instead of generating a synthetic GlyphBuffer here which is then used by the // drawEmphasisMarks method of FontFastPath, we should roll our own emphasis mark drawing function. float* advances = currentRun->advances(); unsigned numGlyphs = currentRun->numGlyphs(); uint16_t* glyphToCharacterIndexes = currentRun->glyphToCharacterIndexes(); unsigned graphemesInCluster = 1; float clusterAdvance = 0; uint16_t clusterStart; // A "cluster" in this context means a cluster as it is used by HarfBuzz: // The minimal group of characters and corresponding glyphs, that cannot be broken // down further from a text shaping point of view. // A cluster can contain multiple glyphs and grapheme clusters, with mutually // overlapping boundaries. Below we count grapheme clusters per HarfBuzz clusters, // then linearly split the sum of corresponding glyph advances by the number of // grapheme clusters in order to find positions for emphasis mark drawing. if (m_run.rtl()) clusterStart = currentRun->startIndex() + currentRun->numCharacters(); else clusterStart = currentRun->startIndex() + glyphToCharacterIndexes[0]; for (unsigned i = 0; i < numGlyphs; ++i) { uint16_t currentCharacterIndex = currentRun->startIndex() + glyphToCharacterIndexes[i]; bool isRunEnd = (i + 1 == numGlyphs); bool isClusterEnd = isRunEnd || (currentRun->startIndex() + glyphToCharacterIndexes[i + 1] != currentCharacterIndex); clusterAdvance += advances[i]; if (isClusterEnd) { uint16_t clusterEnd; if (m_run.rtl()) clusterEnd = currentCharacterIndex; else clusterEnd = isRunEnd ? currentRun->startIndex() + currentRun->numCharacters() : currentRun->startIndex() + glyphToCharacterIndexes[i + 1]; graphemesInCluster = countGraphemesInCluster(m_normalizedBuffer.get(), m_normalizedBufferLength, clusterStart, clusterEnd); if (!graphemesInCluster || !clusterAdvance) continue; float glyphAdvanceX = clusterAdvance / graphemesInCluster; for (unsigned j = 0; j < graphemesInCluster; ++j) { // Do not put emphasis marks on space, separator, and control characters. Glyph glyphToAdd = Character::canReceiveTextEmphasis(m_run[currentCharacterIndex]) ? 1 : 0; glyphBuffer->add(glyphToAdd, currentRun->fontData(), glyphAdvanceX); } clusterStart = clusterEnd; clusterAdvance = 0; } } } bool HarfBuzzShaper::fillGlyphBuffer(GlyphBuffer* glyphBuffer) { unsigned numRuns = m_harfBuzzRuns.size(); float carryAdvance = 0; if (m_run.rtl()) { for (int runIndex = numRuns - 1; runIndex >= 0; --runIndex) { HarfBuzzRun* currentRun = m_harfBuzzRuns[runIndex].get(); if (!currentRun->hasGlyphToCharacterIndexes()) { // FIXME: bug 337886, 359664 continue; } if (m_forTextEmphasis == ForTextEmphasis) fillGlyphBufferForTextEmphasis(glyphBuffer, currentRun); else fillGlyphBufferFromHarfBuzzRun(glyphBuffer, currentRun, carryAdvance); } } else { for (unsigned runIndex = 0; runIndex < numRuns; ++runIndex) { HarfBuzzRun* currentRun = m_harfBuzzRuns[runIndex].get(); if (!currentRun->hasGlyphToCharacterIndexes()) { // FIXME: bug 337886, 359664 continue; } if (m_forTextEmphasis == ForTextEmphasis) fillGlyphBufferForTextEmphasis(glyphBuffer, currentRun); else fillGlyphBufferFromHarfBuzzRun(glyphBuffer, currentRun, carryAdvance); } } return glyphBuffer->size(); } int HarfBuzzShaper::offsetForPosition(float targetX) { int charactersSoFar = 0; float currentX = 0; if (m_run.rtl()) { charactersSoFar = m_normalizedBufferLength; for (int i = m_harfBuzzRuns.size() - 1; i >= 0; --i) { charactersSoFar -= m_harfBuzzRuns[i]->numCharacters(); float nextX = currentX + m_harfBuzzRuns[i]->width(); float offsetForRun = targetX - currentX; if (offsetForRun >= 0 && offsetForRun <= m_harfBuzzRuns[i]->width()) { // The x value in question is within this script run. const unsigned index = m_harfBuzzRuns[i]->characterIndexForXPosition(offsetForRun); return charactersSoFar + index; } currentX = nextX; } } else { for (unsigned i = 0; i < m_harfBuzzRuns.size(); ++i) { float nextX = currentX + m_harfBuzzRuns[i]->width(); float offsetForRun = targetX - currentX; if (offsetForRun >= 0 && offsetForRun <= m_harfBuzzRuns[i]->width()) { const unsigned index = m_harfBuzzRuns[i]->characterIndexForXPosition(offsetForRun); return charactersSoFar + index; } charactersSoFar += m_harfBuzzRuns[i]->numCharacters(); currentX = nextX; } } return charactersSoFar; } FloatRect HarfBuzzShaper::selectionRect(const FloatPoint& point, int height, int from, int to) { float currentX = 0; float fromX = 0; float toX = 0; bool foundFromX = false; bool foundToX = false; if (m_run.rtl()) currentX = m_totalWidth; for (unsigned i = 0; i < m_harfBuzzRuns.size(); ++i) { if (m_run.rtl()) currentX -= m_harfBuzzRuns[i]->width(); int numCharacters = m_harfBuzzRuns[i]->numCharacters(); if (!foundFromX && from >= 0 && from < numCharacters) { fromX = m_harfBuzzRuns[i]->xPositionForOffset(from) + currentX; foundFromX = true; } else from -= numCharacters; if (!foundToX && to >= 0 && to < numCharacters) { toX = m_harfBuzzRuns[i]->xPositionForOffset(to) + currentX; foundToX = true; } else to -= numCharacters; if (foundFromX && foundToX) break; if (!m_run.rtl()) currentX += m_harfBuzzRuns[i]->width(); } // The position in question might be just after the text. if (!foundFromX) fromX = 0; if (!foundToX) toX = m_run.rtl() ? 0 : m_totalWidth; if (fromX < toX) { return Font::pixelSnappedSelectionRect( point.x() + fromX, point.x() + toX, point.y(), height); } return Font::pixelSnappedSelectionRect( point.x() + toX, point.x() + fromX, point.y(), height); } } // namespace blink