blob: c75d18caeddd36d9e9f498ab54c945c3764b35e3 [file] [log] [blame]
// 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