blob: 9158d80eb783abd6c187b069f66368f5424877a9 [file] [log] [blame]
/*
* 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 "platform/fonts/shaping/ShapeResult.h"
#include "platform/fonts/Font.h"
#include "platform/fonts/shaping/ShapeResultInlineHeaders.h"
#include "platform/fonts/shaping/ShapeResultSpacing.h"
#include "wtf/PtrUtil.h"
#include <hb.h>
#include <memory>
namespace blink {
float ShapeResult::RunInfo::xPositionForVisualOffset(
unsigned offset,
AdjustMidCluster adjustMidCluster) const {
ASSERT(offset < m_numCharacters);
if (rtl())
offset = m_numCharacters - offset - 1;
return xPositionForOffset(offset, adjustMidCluster);
}
float ShapeResult::RunInfo::xPositionForOffset(
unsigned offset,
AdjustMidCluster adjustMidCluster) const {
ASSERT(offset <= m_numCharacters);
const unsigned numGlyphs = m_glyphData.size();
unsigned glyphIndex = 0;
float position = 0;
if (rtl()) {
while (glyphIndex < numGlyphs &&
m_glyphData[glyphIndex].characterIndex > offset) {
position += m_glyphData[glyphIndex].advance;
++glyphIndex;
}
// Adjust offset if it's not on the cluster boundary. In RTL, this means
// that the adjusted position is the left side of the character.
if (adjustMidCluster == AdjustToEnd &&
(glyphIndex < numGlyphs ? m_glyphData[glyphIndex].characterIndex
: m_numCharacters) < offset) {
return position;
}
// 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 < numGlyphs - 1 &&
m_glyphData[glyphIndex].characterIndex ==
m_glyphData[glyphIndex + 1].characterIndex) {
position += m_glyphData[glyphIndex].advance;
++glyphIndex;
}
position += m_glyphData[glyphIndex].advance;
} else {
while (glyphIndex < numGlyphs &&
m_glyphData[glyphIndex].characterIndex < offset) {
position += m_glyphData[glyphIndex].advance;
++glyphIndex;
}
// Adjust offset if it's not on the cluster boundary.
if (adjustMidCluster == AdjustToStart && glyphIndex &&
(glyphIndex < numGlyphs ? m_glyphData[glyphIndex].characterIndex
: m_numCharacters) > offset) {
offset = m_glyphData[--glyphIndex].characterIndex;
for (; m_glyphData[glyphIndex].characterIndex == offset; --glyphIndex) {
position -= m_glyphData[glyphIndex].advance;
if (!glyphIndex)
break;
}
}
}
return position;
}
int ShapeResult::RunInfo::characterIndexForXPosition(
float targetX,
bool includePartialGlyphs) const {
DCHECK(targetX >= 0 && targetX <= m_width);
const unsigned numGlyphs = m_glyphData.size();
float currentX = 0;
float currentAdvance = 0;
unsigned glyphIndex = 0;
unsigned prevCharacterIndex = m_numCharacters; // used only when rtl()
while (glyphIndex < numGlyphs) {
float prevAdvance = currentAdvance;
unsigned currentCharacterIndex = m_glyphData[glyphIndex].characterIndex;
currentAdvance = m_glyphData[glyphIndex].advance;
while (glyphIndex < numGlyphs - 1 &&
currentCharacterIndex == m_glyphData[glyphIndex + 1].characterIndex)
currentAdvance += m_glyphData[++glyphIndex].advance;
float nextX;
if (includePartialGlyphs) {
// For hit testing, find the closest caret point by incuding
// end-half of the previous character and start-half of the current
// character.
currentAdvance = currentAdvance / 2.0;
nextX = currentX + prevAdvance + currentAdvance;
} else {
nextX = currentX + currentAdvance;
}
if (currentX <= targetX && targetX <= nextX)
return includePartialGlyphs && rtl() ? prevCharacterIndex
: currentCharacterIndex;
currentX = nextX;
prevCharacterIndex = currentCharacterIndex;
++glyphIndex;
}
return rtl() ? 0 : m_numCharacters;
}
void ShapeResult::RunInfo::setGlyphAndPositions(unsigned index,
uint16_t glyphId,
float advance,
float offsetX,
float offsetY) {
HarfBuzzRunGlyphData& data = m_glyphData[index];
data.glyph = glyphId;
data.advance = advance;
data.offset = FloatSize(offsetX, offsetY);
}
ShapeResult::ShapeResult(const Font* font,
unsigned numCharacters,
TextDirection direction)
: m_width(0),
m_primaryFont(const_cast<SimpleFontData*>(font->primaryFont())),
m_numCharacters(numCharacters),
m_numGlyphs(0),
m_direction(static_cast<unsigned>(direction)),
m_hasVerticalOffsets(0) {}
ShapeResult::ShapeResult(const ShapeResult& other)
: m_width(other.m_width),
m_glyphBoundingBox(other.m_glyphBoundingBox),
m_primaryFont(other.m_primaryFont),
m_numCharacters(other.m_numCharacters),
m_numGlyphs(other.m_numGlyphs),
m_direction(other.m_direction),
m_hasVerticalOffsets(other.m_hasVerticalOffsets) {
m_runs.reserveCapacity(other.m_runs.size());
for (const auto& run : other.m_runs)
m_runs.append(WTF::wrapUnique(new ShapeResult::RunInfo(*run)));
}
ShapeResult::~ShapeResult() {}
size_t ShapeResult::byteSize() const {
size_t selfByteSize = sizeof(this);
for (unsigned i = 0; i < m_runs.size(); ++i) {
selfByteSize += m_runs[i]->byteSize();
}
return selfByteSize;
}
int ShapeResult::offsetForPosition(float targetX,
bool includePartialGlyphs) const {
int charactersSoFar = 0;
float currentX = 0;
if (rtl()) {
charactersSoFar = m_numCharacters;
for (unsigned i = 0; i < m_runs.size(); ++i) {
if (!m_runs[i])
continue;
charactersSoFar -= m_runs[i]->m_numCharacters;
float nextX = currentX + m_runs[i]->m_width;
float offsetForRun = targetX - currentX;
if (offsetForRun >= 0 && offsetForRun <= m_runs[i]->m_width) {
// The x value in question is within this script run.
const unsigned index = m_runs[i]->characterIndexForXPosition(
offsetForRun, includePartialGlyphs);
return charactersSoFar + index;
}
currentX = nextX;
}
} else {
for (unsigned i = 0; i < m_runs.size(); ++i) {
if (!m_runs[i])
continue;
float nextX = currentX + m_runs[i]->m_width;
float offsetForRun = targetX - currentX;
if (offsetForRun >= 0 && offsetForRun <= m_runs[i]->m_width) {
const unsigned index = m_runs[i]->characterIndexForXPosition(
offsetForRun, includePartialGlyphs);
return charactersSoFar + index;
}
charactersSoFar += m_runs[i]->m_numCharacters;
currentX = nextX;
}
}
return charactersSoFar;
}
void ShapeResult::fallbackFonts(
HashSet<const SimpleFontData*>* fallback) const {
ASSERT(fallback);
ASSERT(m_primaryFont);
for (unsigned i = 0; i < m_runs.size(); ++i) {
if (m_runs[i] && m_runs[i]->m_fontData &&
m_runs[i]->m_fontData != m_primaryFont &&
!m_runs[i]->m_fontData->isTextOrientationFallbackOf(
m_primaryFont.get())) {
fallback->add(m_runs[i]->m_fontData.get());
}
}
}
void ShapeResult::applySpacing(ShapeResultSpacing& spacing,
const TextRun& textRun) {
float offsetX, offsetY;
float& offset = spacing.isVerticalOffset() ? offsetY : offsetX;
float totalSpace = 0;
for (auto& run : m_runs) {
if (!run)
continue;
float totalSpaceForRun = 0;
for (size_t i = 0; i < run->m_glyphData.size(); i++) {
HarfBuzzRunGlyphData& glyphData = run->m_glyphData[i];
// Skip if it's not a grapheme cluster boundary.
if (i + 1 < run->m_glyphData.size() &&
glyphData.characterIndex == run->m_glyphData[i + 1].characterIndex) {
// In RTL, marks need the same letter-spacing offset as the base.
if (textRun.rtl() && spacing.letterSpacing()) {
offsetX = offsetY = 0;
offset = spacing.letterSpacing();
glyphData.offset.expand(offsetX, offsetY);
}
} else {
offsetX = offsetY = 0;
float space = spacing.computeSpacing(
textRun, run->m_startIndex + glyphData.characterIndex, offset);
glyphData.advance += space;
totalSpaceForRun += space;
if (textRun.rtl()) {
// In RTL, spacing should be added to left side of glyphs.
offset += space;
}
glyphData.offset.expand(offsetX, offsetY);
}
m_hasVerticalOffsets |= (glyphData.offset.height() != 0);
}
run->m_width += totalSpaceForRun;
totalSpace += totalSpaceForRun;
}
m_width += totalSpace;
if (spacing.isVerticalOffset())
m_glyphBoundingBox.setHeight(m_glyphBoundingBox.height() + totalSpace);
else
m_glyphBoundingBox.setWidth(m_glyphBoundingBox.width() + totalSpace);
}
PassRefPtr<ShapeResult> ShapeResult::applySpacingToCopy(
ShapeResultSpacing& spacing,
const TextRun& run) const {
RefPtr<ShapeResult> result = ShapeResult::create(*this);
result->applySpacing(spacing, run);
return result.release();
}
static inline float harfBuzzPositionToFloat(hb_position_t value) {
return static_cast<float>(value) / (1 << 16);
}
void ShapeResult::insertRun(std::unique_ptr<ShapeResult::RunInfo> runToInsert,
unsigned startGlyph,
unsigned numGlyphs,
hb_buffer_t* harfBuzzBuffer) {
ASSERT(numGlyphs > 0);
std::unique_ptr<ShapeResult::RunInfo> run(std::move(runToInsert));
ASSERT(numGlyphs == run->m_glyphData.size());
const SimpleFontData* currentFontData = run->m_fontData.get();
const hb_glyph_info_t* glyphInfos =
hb_buffer_get_glyph_infos(harfBuzzBuffer, 0);
const hb_glyph_position_t* glyphPositions =
hb_buffer_get_glyph_positions(harfBuzzBuffer, 0);
const unsigned startCluster =
HB_DIRECTION_IS_FORWARD(hb_buffer_get_direction(harfBuzzBuffer))
? glyphInfos[startGlyph].cluster
: glyphInfos[startGlyph + numGlyphs - 1].cluster;
float totalAdvance = 0.0f;
FloatPoint glyphOrigin;
bool hasVerticalOffsets = !HB_DIRECTION_IS_HORIZONTAL(run->m_direction);
// HarfBuzz returns result in visual order, no need to flip for RTL.
for (unsigned i = 0; i < numGlyphs; ++i) {
uint16_t glyph = glyphInfos[startGlyph + i].codepoint;
hb_glyph_position_t pos = glyphPositions[startGlyph + i];
float offsetX = harfBuzzPositionToFloat(pos.x_offset);
float offsetY = -harfBuzzPositionToFloat(pos.y_offset);
// One out of x_advance and y_advance is zero, depending on
// whether the buffer direction is horizontal or vertical.
// Convert to float and negate to avoid integer-overflow for ULONG_MAX.
float advance;
if (LIKELY(pos.x_advance))
advance = harfBuzzPositionToFloat(pos.x_advance);
else
advance = -harfBuzzPositionToFloat(pos.y_advance);
run->m_glyphData[i].characterIndex =
glyphInfos[startGlyph + i].cluster - startCluster;
run->setGlyphAndPositions(i, glyph, advance, offsetX, offsetY);
totalAdvance += advance;
hasVerticalOffsets |= (offsetY != 0);
FloatRect glyphBounds = currentFontData->boundsForGlyph(glyph);
glyphBounds.move(glyphOrigin.x(), glyphOrigin.y());
m_glyphBoundingBox.unite(glyphBounds);
glyphOrigin += FloatSize(advance + offsetX, offsetY);
}
run->m_width = std::max(0.0f, totalAdvance);
m_width += run->m_width;
m_numGlyphs += numGlyphs;
ASSERT(m_numGlyphs >= numGlyphs);
m_hasVerticalOffsets |= hasVerticalOffsets;
// 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 < m_runs.size(); ++pos) {
if (m_runs.at(pos)->m_startIndex > run->m_startIndex) {
m_runs.insert(pos, std::move(run));
break;
}
}
} else {
for (size_t pos = 0; pos < m_runs.size(); ++pos) {
if (m_runs.at(pos)->m_startIndex < run->m_startIndex) {
m_runs.insert(pos, std::move(run));
break;
}
}
}
// If we didn't find an existing slot to place it, append.
if (run)
m_runs.append(std::move(run));
}
PassRefPtr<ShapeResult> ShapeResult::createForTabulationCharacters(
const Font* font,
const TextRun& textRun,
float positionOffset,
unsigned count) {
const SimpleFontData* fontData = font->primaryFont();
// Tab characters are always LTR or RTL, not TTB, even when
// isVerticalAnyUpright().
std::unique_ptr<ShapeResult::RunInfo> run =
WTF::wrapUnique(new ShapeResult::RunInfo(
fontData, textRun.rtl() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR,
HB_SCRIPT_COMMON, 0, count, count));
float position = textRun.xPos() + positionOffset;
float startPosition = position;
for (unsigned i = 0; i < count; i++) {
float advance = font->tabWidth(fontData, textRun.getTabSize(), position);
run->m_glyphData[i].characterIndex = i;
run->setGlyphAndPositions(i, fontData->spaceGlyph(), advance, 0, 0);
position += advance;
}
run->m_width = position - startPosition;
RefPtr<ShapeResult> result =
ShapeResult::create(font, count, textRun.direction());
result->m_width = run->m_width;
result->m_numGlyphs = count;
DCHECK_EQ(result->m_numGlyphs, count); // no overflow
result->m_hasVerticalOffsets =
fontData->platformData().isVerticalAnyUpright();
result->m_runs.append(std::move(run));
return result.release();
}
} // namespace blink