| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "platform/fonts/shaping/ShapeResultBuffer.h" |
| |
| #include "platform/fonts/CharacterRange.h" |
| #include "platform/fonts/GlyphBuffer.h" |
| #include "platform/fonts/SimpleFontData.h" |
| #include "platform/fonts/shaping/ShapeResultInlineHeaders.h" |
| #include "platform/geometry/FloatPoint.h" |
| #include "platform/text/Character.h" |
| #include "platform/text/TextBreakIterator.h" |
| #include "platform/text/TextDirection.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| inline void addGlyphToBuffer(GlyphBuffer* glyphBuffer, |
| float advance, |
| hb_direction_t direction, |
| const SimpleFontData* fontData, |
| const HarfBuzzRunGlyphData& glyphData) { |
| FloatPoint startOffset = HB_DIRECTION_IS_HORIZONTAL(direction) |
| ? FloatPoint(advance, 0) |
| : FloatPoint(0, advance); |
| glyphBuffer->add(glyphData.glyph, fontData, startOffset + glyphData.offset); |
| } |
| |
| inline void addEmphasisMark(GlyphBuffer* buffer, |
| const GlyphData* emphasisData, |
| FloatPoint glyphCenter, |
| float midGlyphOffset) { |
| ASSERT(buffer); |
| ASSERT(emphasisData); |
| |
| const SimpleFontData* emphasisFontData = emphasisData->fontData; |
| ASSERT(emphasisFontData); |
| |
| bool isVertical = emphasisFontData->platformData().isVerticalAnyUpright() && |
| emphasisFontData->verticalData(); |
| |
| if (!isVertical) { |
| buffer->add(emphasisData->glyph, emphasisFontData, |
| midGlyphOffset - glyphCenter.x()); |
| } else { |
| buffer->add(emphasisData->glyph, emphasisFontData, |
| FloatPoint(-glyphCenter.x(), midGlyphOffset - glyphCenter.y())); |
| } |
| } |
| |
| inline unsigned countGraphemesInCluster(const UChar* str, |
| unsigned strLength, |
| 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<unsigned>(startIndex + length) <= strLength); |
| TextBreakIterator* cursorPosIterator = |
| cursorMovementIterator(&str[startIndex], length); |
| |
| int cursorPos = cursorPosIterator->current(); |
| int numGraphemes = -1; |
| while (0 <= cursorPos) { |
| cursorPos = cursorPosIterator->next(); |
| numGraphemes++; |
| } |
| return std::max(0, numGraphemes); |
| } |
| |
| } // anonymous namespace |
| |
| template <TextDirection direction> |
| float ShapeResultBuffer::fillGlyphBufferForRun(GlyphBuffer* glyphBuffer, |
| const ShapeResult::RunInfo* run, |
| float initialAdvance, |
| unsigned from, |
| unsigned to, |
| unsigned runOffset) { |
| if (!run) |
| return 0; |
| float advanceSoFar = initialAdvance; |
| const unsigned numGlyphs = run->m_glyphData.size(); |
| for (unsigned i = 0; i < numGlyphs; ++i) { |
| const HarfBuzzRunGlyphData& glyphData = run->m_glyphData[i]; |
| uint16_t currentCharacterIndex = |
| run->m_startIndex + glyphData.characterIndex + runOffset; |
| if ((direction == TextDirection::Rtl && currentCharacterIndex >= to) || |
| (direction == TextDirection::Ltr && currentCharacterIndex < from)) { |
| advanceSoFar += glyphData.advance; |
| } else if ((direction == TextDirection::Rtl && |
| currentCharacterIndex >= from) || |
| (direction == TextDirection::Ltr && |
| currentCharacterIndex < to)) { |
| addGlyphToBuffer(glyphBuffer, advanceSoFar, run->m_direction, |
| run->m_fontData.get(), glyphData); |
| advanceSoFar += glyphData.advance; |
| } |
| } |
| return advanceSoFar - initialAdvance; |
| } |
| |
| float ShapeResultBuffer::fillGlyphBufferForTextEmphasisRun( |
| GlyphBuffer* glyphBuffer, |
| const ShapeResult::RunInfo* run, |
| const TextRun& textRun, |
| const GlyphData* emphasisData, |
| float initialAdvance, |
| unsigned from, |
| unsigned to, |
| unsigned runOffset) { |
| if (!run) |
| return 0; |
| |
| unsigned graphemesInCluster = 1; |
| float clusterAdvance = 0; |
| |
| FloatPoint glyphCenter = |
| emphasisData->fontData->boundsForGlyph(emphasisData->glyph).center(); |
| |
| TextDirection direction = textRun.direction(); |
| |
| // 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. |
| uint16_t clusterStart = static_cast<uint16_t>( |
| direction == TextDirection::Rtl |
| ? run->m_startIndex + run->m_numCharacters + runOffset |
| : run->glyphToCharacterIndex(0) + runOffset); |
| |
| float advanceSoFar = initialAdvance; |
| const unsigned numGlyphs = run->m_glyphData.size(); |
| for (unsigned i = 0; i < numGlyphs; ++i) { |
| const HarfBuzzRunGlyphData& glyphData = run->m_glyphData[i]; |
| uint16_t currentCharacterIndex = |
| run->m_startIndex + glyphData.characterIndex + runOffset; |
| bool isRunEnd = (i + 1 == numGlyphs); |
| bool isClusterEnd = |
| isRunEnd || (run->glyphToCharacterIndex(i + 1) + runOffset != |
| currentCharacterIndex); |
| |
| if ((direction == TextDirection::Rtl && currentCharacterIndex >= to) || |
| (direction != TextDirection::Rtl && currentCharacterIndex < from)) { |
| advanceSoFar += glyphData.advance; |
| direction == TextDirection::Rtl ? --clusterStart : ++clusterStart; |
| continue; |
| } |
| |
| clusterAdvance += glyphData.advance; |
| |
| if (textRun.is8Bit()) { |
| float glyphAdvanceX = glyphData.advance; |
| if (Character::canReceiveTextEmphasis(textRun[currentCharacterIndex])) { |
| addEmphasisMark(glyphBuffer, emphasisData, glyphCenter, |
| advanceSoFar + glyphAdvanceX / 2); |
| } |
| advanceSoFar += glyphAdvanceX; |
| } else if (isClusterEnd) { |
| uint16_t clusterEnd; |
| if (direction == TextDirection::Rtl) |
| clusterEnd = currentCharacterIndex; |
| else |
| clusterEnd = static_cast<uint16_t>( |
| isRunEnd ? run->m_startIndex + run->m_numCharacters + runOffset |
| : run->glyphToCharacterIndex(i + 1) + runOffset); |
| |
| graphemesInCluster = countGraphemesInCluster(textRun.characters16(), |
| textRun.charactersLength(), |
| 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. |
| if (Character::canReceiveTextEmphasis(textRun[currentCharacterIndex])) |
| addEmphasisMark(glyphBuffer, emphasisData, glyphCenter, |
| advanceSoFar + glyphAdvanceX / 2); |
| advanceSoFar += glyphAdvanceX; |
| } |
| clusterStart = clusterEnd; |
| clusterAdvance = 0; |
| } |
| } |
| return advanceSoFar - initialAdvance; |
| } |
| |
| float ShapeResultBuffer::fillFastHorizontalGlyphBuffer( |
| GlyphBuffer* glyphBuffer, |
| TextDirection dir) const { |
| ASSERT(!hasVerticalOffsets()); |
| |
| float advance = 0; |
| |
| for (unsigned i = 0; i < m_results.size(); ++i) { |
| const auto& wordResult = isLeftToRightDirection(dir) |
| ? m_results[i] |
| : m_results[m_results.size() - 1 - i]; |
| ASSERT(!wordResult->hasVerticalOffsets()); |
| |
| for (const auto& run : wordResult->m_runs) { |
| ASSERT(run); |
| ASSERT(HB_DIRECTION_IS_HORIZONTAL(run->m_direction)); |
| |
| for (const auto& glyphData : run->m_glyphData) { |
| ASSERT(!glyphData.offset.height()); |
| |
| glyphBuffer->add(glyphData.glyph, run->m_fontData.get(), |
| advance + glyphData.offset.width()); |
| advance += glyphData.advance; |
| } |
| } |
| } |
| |
| ASSERT(!glyphBuffer->hasVerticalOffsets()); |
| |
| return advance; |
| } |
| |
| float ShapeResultBuffer::fillGlyphBuffer(GlyphBuffer* glyphBuffer, |
| const TextRun& textRun, |
| unsigned from, |
| unsigned to) const { |
| // Fast path: full run with no vertical offsets |
| if (!from && to == textRun.length() && !hasVerticalOffsets()) |
| return fillFastHorizontalGlyphBuffer(glyphBuffer, textRun.direction()); |
| |
| float advance = 0; |
| |
| if (textRun.rtl()) { |
| unsigned wordOffset = textRun.length(); |
| for (unsigned j = 0; j < m_results.size(); j++) { |
| unsigned resolvedIndex = m_results.size() - 1 - j; |
| const RefPtr<const ShapeResult>& wordResult = m_results[resolvedIndex]; |
| for (unsigned i = 0; i < wordResult->m_runs.size(); i++) { |
| advance += fillGlyphBufferForRun<TextDirection::Rtl>( |
| glyphBuffer, wordResult->m_runs[i].get(), advance, from, to, |
| wordOffset - wordResult->numCharacters()); |
| } |
| wordOffset -= wordResult->numCharacters(); |
| } |
| } else { |
| unsigned wordOffset = 0; |
| for (unsigned j = 0; j < m_results.size(); j++) { |
| const RefPtr<const ShapeResult>& wordResult = m_results[j]; |
| for (unsigned i = 0; i < wordResult->m_runs.size(); i++) { |
| advance += fillGlyphBufferForRun<TextDirection::Ltr>( |
| glyphBuffer, wordResult->m_runs[i].get(), advance, from, to, |
| wordOffset); |
| } |
| wordOffset += wordResult->numCharacters(); |
| } |
| } |
| |
| return advance; |
| } |
| |
| float ShapeResultBuffer::fillGlyphBufferForTextEmphasis( |
| GlyphBuffer* glyphBuffer, |
| const TextRun& textRun, |
| const GlyphData* emphasisData, |
| unsigned from, |
| unsigned to) const { |
| float advance = 0; |
| unsigned wordOffset = textRun.rtl() ? textRun.length() : 0; |
| |
| for (unsigned j = 0; j < m_results.size(); j++) { |
| unsigned resolvedIndex = textRun.rtl() ? m_results.size() - 1 - j : j; |
| const RefPtr<const ShapeResult>& wordResult = m_results[resolvedIndex]; |
| for (unsigned i = 0; i < wordResult->m_runs.size(); i++) { |
| unsigned resolvedOffset = |
| wordOffset - (textRun.rtl() ? wordResult->numCharacters() : 0); |
| advance += fillGlyphBufferForTextEmphasisRun( |
| glyphBuffer, wordResult->m_runs[i].get(), textRun, emphasisData, |
| advance, from, to, resolvedOffset); |
| } |
| wordOffset += wordResult->numCharacters() * (textRun.rtl() ? -1 : 1); |
| } |
| |
| return advance; |
| } |
| |
| CharacterRange ShapeResultBuffer::getCharacterRange(TextDirection direction, |
| float totalWidth, |
| unsigned absoluteFrom, |
| unsigned absoluteTo) const { |
| float currentX = 0; |
| float fromX = 0; |
| float toX = 0; |
| bool foundFromX = false; |
| bool foundToX = false; |
| |
| if (direction == TextDirection::Rtl) |
| currentX = totalWidth; |
| |
| // The absoluteFrom and absoluteTo arguments represent the start/end offset |
| // for the entire run, from/to are continuously updated to be relative to |
| // the current word (ShapeResult instance). |
| int from = absoluteFrom; |
| int to = absoluteTo; |
| |
| unsigned totalNumCharacters = 0; |
| for (unsigned j = 0; j < m_results.size(); j++) { |
| const RefPtr<const ShapeResult> result = m_results[j]; |
| if (direction == TextDirection::Rtl) { |
| // Convert logical offsets to visual offsets, because results are in |
| // logical order while runs are in visual order. |
| if (!foundFromX && from >= 0 && |
| static_cast<unsigned>(from) < result->numCharacters()) |
| from = result->numCharacters() - from - 1; |
| if (!foundToX && to >= 0 && |
| static_cast<unsigned>(to) < result->numCharacters()) |
| to = result->numCharacters() - to - 1; |
| currentX -= result->width(); |
| } |
| for (unsigned i = 0; i < result->m_runs.size(); i++) { |
| if (!result->m_runs[i]) |
| continue; |
| DCHECK_EQ(direction == TextDirection::Rtl, result->m_runs[i]->rtl()); |
| int numCharacters = result->m_runs[i]->m_numCharacters; |
| if (!foundFromX && from >= 0 && from < numCharacters) { |
| fromX = |
| result->m_runs[i]->xPositionForVisualOffset(from, AdjustToStart) + |
| currentX; |
| foundFromX = true; |
| } else { |
| from -= numCharacters; |
| } |
| |
| if (!foundToX && to >= 0 && to < numCharacters) { |
| toX = result->m_runs[i]->xPositionForVisualOffset(to, AdjustToEnd) + |
| currentX; |
| foundToX = true; |
| } else { |
| to -= numCharacters; |
| } |
| |
| if (foundFromX && foundToX) |
| break; |
| currentX += result->m_runs[i]->m_width; |
| } |
| if (direction == TextDirection::Rtl) |
| currentX -= result->width(); |
| totalNumCharacters += result->numCharacters(); |
| } |
| |
| // The position in question might be just after the text. |
| if (!foundFromX && absoluteFrom == totalNumCharacters) { |
| fromX = direction == TextDirection::Rtl ? 0 : totalWidth; |
| foundFromX = true; |
| } |
| if (!foundToX && absoluteTo == totalNumCharacters) { |
| toX = direction == TextDirection::Rtl ? 0 : totalWidth; |
| foundToX = true; |
| } |
| if (!foundFromX) |
| fromX = 0; |
| if (!foundToX) |
| toX = direction == TextDirection::Rtl ? 0 : totalWidth; |
| |
| // None of our runs is part of the selection, possibly invalid arguments. |
| if (!foundToX && !foundFromX) |
| fromX = toX = 0; |
| if (fromX < toX) |
| return CharacterRange(fromX, toX); |
| return CharacterRange(toX, fromX); |
| } |
| |
| void ShapeResultBuffer::addRunInfoRanges(const ShapeResult::RunInfo& runInfo, |
| float offset, |
| Vector<CharacterRange>& ranges) { |
| Vector<float> characterWidths(runInfo.m_numCharacters); |
| for (const auto& glyph : runInfo.m_glyphData) |
| characterWidths[glyph.characterIndex] += glyph.advance; |
| |
| for (unsigned characterIndex = 0; characterIndex < runInfo.m_numCharacters; |
| characterIndex++) { |
| float start = offset; |
| offset += characterWidths[characterIndex]; |
| float end = offset; |
| |
| // To match getCharacterRange we flip ranges to ensure start <= end. |
| if (end < start) |
| ranges.append(CharacterRange(end, start)); |
| else |
| ranges.append(CharacterRange(start, end)); |
| } |
| } |
| |
| Vector<CharacterRange> ShapeResultBuffer::individualCharacterRanges( |
| TextDirection direction, |
| float totalWidth) const { |
| Vector<CharacterRange> ranges; |
| float currentX = direction == TextDirection::Rtl ? totalWidth : 0; |
| for (const RefPtr<const ShapeResult> result : m_results) { |
| if (direction == TextDirection::Rtl) |
| currentX -= result->width(); |
| unsigned runCount = result->m_runs.size(); |
| for (unsigned index = 0; index < runCount; index++) { |
| unsigned runIndex = |
| direction == TextDirection::Rtl ? runCount - 1 - index : index; |
| addRunInfoRanges(*result->m_runs[runIndex], currentX, ranges); |
| currentX += result->m_runs[runIndex]->m_width; |
| } |
| if (direction == TextDirection::Rtl) |
| currentX -= result->width(); |
| } |
| return ranges; |
| } |
| |
| int ShapeResultBuffer::offsetForPosition(const TextRun& run, |
| float targetX, |
| bool includePartialGlyphs) const { |
| unsigned totalOffset; |
| if (run.rtl()) { |
| totalOffset = run.length(); |
| for (unsigned i = m_results.size(); i; --i) { |
| const RefPtr<const ShapeResult>& wordResult = m_results[i - 1]; |
| if (!wordResult) |
| continue; |
| totalOffset -= wordResult->numCharacters(); |
| if (targetX >= 0 && targetX <= wordResult->width()) { |
| int offsetForWord = |
| wordResult->offsetForPosition(targetX, includePartialGlyphs); |
| return totalOffset + offsetForWord; |
| } |
| targetX -= wordResult->width(); |
| } |
| } else { |
| totalOffset = 0; |
| for (const auto& wordResult : m_results) { |
| if (!wordResult) |
| continue; |
| int offsetForWord = |
| wordResult->offsetForPosition(targetX, includePartialGlyphs); |
| ASSERT(offsetForWord >= 0); |
| totalOffset += offsetForWord; |
| if (targetX >= 0 && targetX <= wordResult->width()) |
| return totalOffset; |
| targetX -= wordResult->width(); |
| } |
| } |
| return totalOffset; |
| } |
| |
| } // namespace blink |