ComplexTextControllerHarfBuzz.cpp [plain text]
#include "config.h"
#include "ComplexTextControllerHarfBuzz.h"
#include "FloatRect.h"
#include "Font.h"
#include "SurrogatePairAwareTextIterator.h"
#include "TextRun.h"
#if OS(ANDROID)
#include "FontCache.h"
#include "SkTypeface_android.h"
#endif
extern "C" {
#include "harfbuzz-unicode.h"
}
#include <unicode/normlzr.h>
namespace WebCore {
static int truncateFixedPointToInteger(HB_Fixed value)
{
return value >> 6;
}
ComplexTextController::ComplexTextController(const Font* font, const TextRun& run, int startingX, int startingY)
: HarfBuzzShaperBase(font, run)
{
NormalizeMode mode = m_run.rtl() ? NormalizeMirrorChars : DoNotNormalizeMirrorChars;
setNormalizedBuffer(mode);
memset(&m_item, 0, sizeof(m_item));
createGlyphArrays((m_normalizedBufferLength + 2) * 2);
m_item.log_clusters = new unsigned short[m_normalizedBufferLength];
m_item.face = 0;
m_item.font = allocHarfbuzzFont();
m_item.item.bidiLevel = m_run.rtl();
m_item.string = m_normalizedBuffer.get();
m_item.stringLength = m_normalizedBufferLength;
reset(startingX);
m_startingY = startingY;
setPadding(m_run.expansion());
}
ComplexTextController::~ComplexTextController()
{
fastFree(m_item.font);
deleteGlyphArrays();
delete[] m_item.log_clusters;
}
void ComplexTextController::reset(int offset)
{
m_indexOfNextScriptRun = 0;
m_offsetX = offset;
}
void ComplexTextController::setupForRTL()
{
int padding = m_padding;
reset(m_offsetX + widthOfFullRun());
setPadding(padding);
}
bool ComplexTextController::nextScriptRun()
{
m_item.string = m_normalizedBuffer.get();
if (!hb_utf16_script_run_next(0, &m_item.item, m_normalizedBuffer.get(), m_normalizedBufferLength, &m_indexOfNextScriptRun))
return false;
SurrogatePairAwareTextIterator iterator(static_cast<const UChar*>(&m_item.string[m_item.item.pos]), 0, static_cast<int>(m_item.item.length), static_cast<int>(m_item.item.length));
UChar32 character;
unsigned clusterLength = 0;
if (!iterator.consume(character, clusterLength))
return false;
m_currentFontData = m_font->glyphDataForCharacter(character, false).fontData;
iterator.advance(clusterLength);
while (iterator.consume(character, clusterLength)) {
const SimpleFontData* nextFontData = m_font->glyphDataForCharacter(character, false).fontData;
if (nextFontData != m_currentFontData)
break;
iterator.advance(clusterLength);
}
m_item.item.length = iterator.currentCharacter();
m_indexOfNextScriptRun = m_item.item.pos + iterator.currentCharacter();
setupFontForScriptRun();
shapeGlyphs();
setGlyphPositions(rtl());
return true;
}
float ComplexTextController::widthOfFullRun()
{
float widthSum = 0;
while (nextScriptRun())
widthSum += width();
return widthSum;
}
static void setupFontFeatures(const FontFeatureSettings* settings, HB_FaceRec_* hbFace)
{
if (!settings)
return;
if (hbFace->gsub)
HB_GSUB_Clear_Features(hbFace->gsub);
if (hbFace->gpos)
HB_GPOS_Clear_Features(hbFace->gpos);
HB_UShort scriptIndex = 0;
HB_GSUB_Select_Script(hbFace->gsub, HB_MAKE_TAG('D', 'F', 'L', 'T'), &scriptIndex);
size_t numFeatures = settings->size();
for (size_t i = 0; i < numFeatures; ++i) {
if (!settings->at(i).value())
continue;
HB_UShort featureIndex = 0;
const UChar* tag = settings->at(i).tag().characters();
HB_UInt feature = HB_MAKE_TAG(tag[0], tag[1], tag[2], tag[3]);
if (hbFace->gsub && HB_GSUB_Select_Feature(hbFace->gsub, feature, scriptIndex, 0xffff, &featureIndex) == HB_Err_Ok)
HB_GSUB_Add_Feature(hbFace->gsub, featureIndex, settings->at(i).value());
else if (hbFace->gpos && HB_GPOS_Select_Feature(hbFace->gpos, feature, scriptIndex, 0xffff, &featureIndex) == HB_Err_Ok)
HB_GPOS_Add_Feature(hbFace->gpos, featureIndex, settings->at(i).value());
}
}
static UChar32 surrogatePairAwareFirstCharacter(const UChar* characters, unsigned length)
{
if (U16_IS_SURROGATE(characters[0])) {
if (!U16_IS_SURROGATE_LEAD(characters[0]) || length < 2 || !U16_IS_TRAIL(characters[1]))
return ' ';
return U16_GET_SUPPLEMENTARY(characters[0], characters[1]);
}
return characters[0];
}
const FontPlatformData* ComplexTextController::getComplexFontPlatformData()
{
#if OS(ANDROID)
FallbackScripts fallbackScript = kFallbackScriptNumber; switch (m_item.item.script) {
case HB_Script_Arabic:
fallbackScript = kArabic_FallbackScript;
break;
case HB_Script_Hebrew:
if (m_font->fontDescription().weight() >= FontWeightBold)
fallbackScript = kHebrewBold_FallbackScript;
else
fallbackScript = kHebrewRegular_FallbackScript;
break;
case HB_Script_Thai:
fallbackScript = kThai_FallbackScript;
break;
case HB_Script_Armenian:
fallbackScript = kArmenian_FallbackScript;
break;
case HB_Script_Georgian:
fallbackScript = kGeorgian_FallbackScript;
break;
case HB_Script_Devanagari:
fallbackScript = kDevanagari_FallbackScript;
break;
case HB_Script_Bengali:
fallbackScript = kBengali_FallbackScript;
break;
case HB_Script_Tamil:
fallbackScript = kTamil_FallbackScript;
break;
default:
return 0;
}
return fontCache()->getCachedFontPlatformData(m_font->fontDescription(), SkGetFallbackScriptID(fallbackScript), true);
#else
return 0;
#endif
}
void ComplexTextController::setupFontForScriptRun()
{
FontDataVariant fontDataVariant = AutoVariant;
if (m_font->isSmallCaps() && u_islower(m_item.string[m_item.item.pos])) {
m_smallCapsString = String(m_normalizedBuffer.get() + m_item.item.pos, m_item.item.length);
m_smallCapsString.makeUpper();
m_item.string = m_smallCapsString.characters();
m_item.item.pos = 0;
fontDataVariant = SmallCapsVariant;
}
const FontPlatformData* platformData = getComplexFontPlatformData();
if (!platformData) {
UChar32 current = surrogatePairAwareFirstCharacter(static_cast<const UChar*>(&m_item.string[m_item.item.pos]), m_item.item.length - m_item.item.pos);
const FontData* fontData = m_font->glyphDataForCharacter(current, false, fontDataVariant).fontData;
platformData = &fontData->fontDataForCharacter(' ')->platformData();
}
m_item.face = platformData->harfbuzzFace()->face();
if (!m_item.item.pos)
setupFontFeatures(m_font->fontDescription().featureSettings(), m_item.face);
void* opaquePlatformData = const_cast<FontPlatformData*>(platformData);
m_item.font->userData = opaquePlatformData;
int size = platformData->size();
m_item.font->x_ppem = size;
m_item.font->y_ppem = size;
const int devicePixelFraction = 64;
const int multiplyFor16Dot16 = 1 << 16;
int scale = devicePixelFraction * size * multiplyFor16Dot16 / platformData->emSizeInFontUnits();
m_item.font->x_scale = scale;
m_item.font->y_scale = scale;
}
void ComplexTextController::deleteGlyphArrays()
{
delete[] m_item.glyphs;
delete[] m_item.attributes;
delete[] m_item.advances;
delete[] m_item.offsets;
delete[] m_glyphs16;
delete[] m_positions;
}
void ComplexTextController::createGlyphArrays(int size)
{
m_item.glyphs = new HB_Glyph[size];
m_item.attributes = new HB_GlyphAttributes[size];
m_item.advances = new HB_Fixed[size];
m_item.offsets = new HB_FixedPoint[size];
m_glyphs16 = new uint16_t[size];
m_positions = new SkPoint[size];
m_item.num_glyphs = size;
m_glyphsArrayCapacity = size; resetGlyphArrays();
}
void ComplexTextController::resetGlyphArrays()
{
int size = m_item.num_glyphs;
memset(m_item.glyphs, 0, size * sizeof(HB_Glyph));
memset(m_item.attributes, 0, size * sizeof(HB_GlyphAttributes));
memset(m_item.advances, 0, size * sizeof(HB_Fixed));
memset(m_item.offsets, 0, size * sizeof(HB_FixedPoint));
memset(m_glyphs16, 0, size * sizeof(uint16_t));
memset(m_positions, 0, size * sizeof(SkPoint));
}
void ComplexTextController::shapeGlyphs()
{
m_item.num_glyphs = m_glyphsArrayCapacity;
resetGlyphArrays();
while (!HB_ShapeItem(&m_item)) {
deleteGlyphArrays();
createGlyphArrays(m_item.num_glyphs + 1);
}
}
void ComplexTextController::setGlyphPositions(bool isRTL)
{
const double rtlFlip = isRTL ? -1 : 1;
double position = 0;
size_t logClustersIndex = 0;
for (size_t i = 0; i < m_item.num_glyphs; ++i) {
while (logClustersIndex < m_item.item.length && m_item.log_clusters[logClustersIndex] < i)
logClustersIndex++;
if (logClustersIndex < m_item.item.length && isWordEnd(m_item.item.pos + logClustersIndex))
position += determineWordBreakSpacing();
m_glyphs16[i] = m_item.glyphs[i];
double offsetX = truncateFixedPointToInteger(m_item.offsets[i].x);
double offsetY = truncateFixedPointToInteger(m_item.offsets[i].y);
double advance = truncateFixedPointToInteger(m_item.advances[i]);
if (isRTL)
offsetX -= advance;
m_positions[i].set(m_offsetX + (position * rtlFlip) + offsetX,
m_startingY + offsetY);
if (m_currentFontData->isZeroWidthSpaceGlyph(m_glyphs16[i]))
continue;
if (i + 1 == m_item.num_glyphs || m_item.attributes[i + 1].clusterStart)
position += m_letterSpacing;
position += advance;
}
m_pixelWidth = std::max(position, 0.0);
m_offsetX += m_pixelWidth * rtlFlip;
}
int ComplexTextController::glyphIndexForXPositionInScriptRun(int targetX) const
{
int lastX = offsetX() - (rtl() ? -m_pixelWidth : m_pixelWidth);
for (int glyphIndex = 0; static_cast<unsigned>(glyphIndex) < length(); ++glyphIndex) {
int advance = truncateFixedPointToInteger(m_item.advances[glyphIndex]);
int nextX = static_cast<int>(positions()[glyphIndex].x()) + advance / 2;
if (std::min(nextX, lastX) <= targetX && targetX <= std::max(nextX, lastX))
return glyphIndex;
lastX = nextX;
}
return length() - 1;
}
int ComplexTextController::offsetForPosition(int targetX)
{
unsigned basePosition = 0;
int x = offsetX();
while (nextScriptRun()) {
int nextX = offsetX();
if (std::min(x, nextX) <= targetX && targetX <= std::max(x, nextX)) {
const int glyphIndex = glyphIndexForXPositionInScriptRun(targetX);
for (unsigned j = 0; j < numCodePoints(); ++j) {
if (m_item.log_clusters[j] >= glyphIndex)
return basePosition + j;
}
return basePosition + numCodePoints() - 1;
}
basePosition += numCodePoints();
}
return basePosition;
}
FloatRect ComplexTextController::selectionRect(const FloatPoint& point, int height, int from, int to)
{
int fromX = -1, toX = -1;
while (nextScriptRun() && (fromX == -1 || toX == -1)) {
if (fromX == -1 && from >= 0 && static_cast<unsigned>(from) < numCodePoints()) {
int glyph = m_item.log_clusters[from];
fromX = positions()[glyph].x();
if (rtl())
fromX += truncateFixedPointToInteger(m_item.advances[glyph]);
} else
from -= numCodePoints();
if (toX == -1 && to >= 0 && static_cast<unsigned>(to) < numCodePoints()) {
int glyph = m_item.log_clusters[to];
toX = positions()[glyph].x();
if (rtl())
toX += truncateFixedPointToInteger(m_item.advances[glyph]);
} else
to -= numCodePoints();
}
if (fromX == -1)
fromX = offsetX();
if (toX == -1)
toX = offsetX();
ASSERT(fromX != -1 && toX != -1);
if (fromX < toX)
return FloatRect(point.x() + fromX, point.y(), toX - fromX, height);
return FloatRect(point.x() + toX, point.y(), fromX - toX, height);
}
void ComplexTextController::glyphsForRange(int from, int to, int& fromGlyph, int& glyphLength)
{
int fromChar = from - m_item.item.pos;
int toChar = to - m_item.item.pos;
if (!numCodePoints() || fromChar >= static_cast<int>(numCodePoints()) || toChar <= 0) {
fromGlyph = -1;
glyphLength = 0;
return;
}
fromGlyph = m_item.log_clusters[fromChar < 0 ? 0 : fromChar];
if (toChar >= static_cast<int>(numCodePoints()))
glyphLength = length() - fromGlyph;
else
glyphLength = m_item.log_clusters[toChar] - fromGlyph;
}
}