#include "config.h"
#include "Font.h"
#include "FloatRect.h"
#include "GlyphBuffer.h"
#include "GraphicsContext.h"
#include "HarfbuzzSkia.h"
#include "NotImplemented.h"
#include "PlatformContextSkia.h"
#include "SimpleFontData.h"
#include "SkCanvas.h"
#include "SkPaint.h"
#include "SkTemplates.h"
#include "SkTypeface.h"
#include "SkUtils.h"
#include <unicode/normlzr.h>
#include <unicode/uchar.h>
#include <wtf/OwnArrayPtr.h>
#include <wtf/OwnPtr.h>
namespace WebCore {
bool Font::canReturnFallbackFontsForComplexText()
{
return false;
}
static bool isCanvasMultiLayered(SkCanvas* canvas)
{
SkCanvas::LayerIter layerIterator(canvas, false);
layerIterator.next();
return !layerIterator.done();
}
static void adjustTextRenderMode(SkPaint* paint, bool isCanvasMultiLayered)
{
if (isCanvasMultiLayered)
paint->setLCDRenderText(false);
}
void Font::drawGlyphs(GraphicsContext* gc, const SimpleFontData* font,
const GlyphBuffer& glyphBuffer, int from, int numGlyphs,
const FloatPoint& point) const {
SkASSERT(sizeof(GlyphBufferGlyph) == sizeof(uint16_t));
const GlyphBufferGlyph* glyphs = glyphBuffer.glyphs(from);
SkScalar x = SkFloatToScalar(point.x());
SkScalar y = SkFloatToScalar(point.y());
const GlyphBufferAdvance* adv = glyphBuffer.advances(from);
SkAutoSTMalloc<32, SkPoint> storage(numGlyphs);
SkPoint* pos = storage.get();
for (int i = 0; i < numGlyphs; i++) {
pos[i].set(x, y);
x += SkFloatToScalar(adv[i].width());
y += SkFloatToScalar(adv[i].height());
}
SkCanvas* canvas = gc->platformContext()->canvas();
int textMode = gc->platformContext()->getTextDrawingMode();
bool haveMultipleLayers = isCanvasMultiLayered(canvas);
if (textMode & cTextFill) {
SkPaint paint;
gc->platformContext()->setupPaintForFilling(&paint);
font->platformData().setupPaint(&paint);
adjustTextRenderMode(&paint, haveMultipleLayers);
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setColor(gc->fillColor().rgb());
canvas->drawPosText(glyphs, numGlyphs << 1, pos, paint);
}
if ((textMode & cTextStroke)
&& gc->platformContext()->getStrokeStyle() != NoStroke
&& gc->platformContext()->getStrokeThickness() > 0) {
SkPaint paint;
gc->platformContext()->setupPaintForStroking(&paint, 0, 0);
font->platformData().setupPaint(&paint);
adjustTextRenderMode(&paint, haveMultipleLayers);
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setColor(gc->strokeColor().rgb());
if (textMode & cTextFill) {
paint.setLooper(0)->safeUnref();
}
canvas->drawPosText(glyphs, numGlyphs << 1, pos, paint);
}
}
static int truncateFixedPointToInteger(HB_Fixed value)
{
return value >> 6;
}
class TextRunWalker {
public:
TextRunWalker(const TextRun& run, unsigned startingX, const Font* font)
: m_font(font)
, m_startingX(startingX)
, m_offsetX(m_startingX)
, m_run(getTextRun(run))
, m_iterateBackwards(m_run.rtl())
{
memset(&m_item, 0, sizeof(m_item));
m_maxGlyphs = m_run.length() * 2;
createGlyphArrays();
m_item.log_clusters = new unsigned short[m_run.length()];
m_item.face = 0;
m_item.font = allocHarfbuzzFont();
m_item.string = m_run.characters();
m_item.stringLength = m_run.length();
m_item.item.bidiLevel = m_run.rtl();
reset();
}
~TextRunWalker()
{
fastFree(m_item.font);
deleteGlyphArrays();
delete[] m_item.log_clusters;
}
void reset()
{
if (m_iterateBackwards)
m_indexOfNextScriptRun = m_run.length() - 1;
else
m_indexOfNextScriptRun = 0;
m_offsetX = m_startingX;
}
void setXOffsetToZero()
{
m_offsetX = 0;
}
bool rtl() const
{
return m_run.rtl();
}
void setBackwardsIteration(bool isBackwards)
{
m_iterateBackwards = isBackwards;
reset();
}
bool nextScriptRun()
{
if (m_iterateBackwards) {
if (!hb_utf16_script_run_prev(&m_numCodePoints, &m_item.item, m_run.characters(), m_run.length(), &m_indexOfNextScriptRun))
return false;
} else {
if (!hb_utf16_script_run_next(&m_numCodePoints, &m_item.item, m_run.characters(), m_run.length(), &m_indexOfNextScriptRun))
return false;
const FontData* glyphData = m_font->glyphDataForCharacter(m_item.string[m_item.item.pos], false, false).fontData;
int endOfRun;
for (endOfRun = 1; endOfRun < m_item.item.length; ++endOfRun) {
const FontData* nextGlyphData = m_font->glyphDataForCharacter(m_item.string[m_item.item.pos + endOfRun], false, false).fontData;
if (nextGlyphData != glyphData)
break;
}
m_item.item.length = endOfRun;
m_indexOfNextScriptRun = m_item.item.pos + endOfRun;
}
setupFontForScriptRun();
if (!shapeGlyphs())
return false;
setGlyphXPositions(rtl());
return true;
}
const uint16_t* glyphs() const
{
return m_glyphs16;
}
const unsigned length() const
{
return m_item.num_glyphs;
}
const SkScalar* xPositions() const
{
return m_xPositions;
}
const HB_Fixed* advances() const
{
return m_item.advances;
}
const unsigned width() const
{
return m_pixelWidth;
}
const unsigned short* logClusters() const
{
return m_item.log_clusters;
}
const unsigned numCodePoints() const
{
return m_numCodePoints;
}
const FontPlatformData* fontPlatformDataForScriptRun()
{
return reinterpret_cast<FontPlatformData*>(m_item.font->userData);
}
float widthOfFullRun()
{
float widthSum = 0;
while (nextScriptRun())
widthSum += width();
return widthSum;
}
private:
const TextRun& getTextRun(const TextRun& originalRun)
{
for (unsigned i = 0; i < originalRun.length(); ++i) {
UChar ch = originalRun[i];
UBlockCode block = ::ublock_getCode(ch);
if (block == UBLOCK_COMBINING_DIACRITICAL_MARKS || (Font::treatAsSpace(ch) && ch != ' ')) {
return getNormalizedTextRun(originalRun);
}
}
return originalRun;
}
const TextRun& getNormalizedTextRun(const TextRun& originalRun)
{
icu::UnicodeString normalizedString;
UErrorCode error = U_ZERO_ERROR;
icu::Normalizer::normalize(icu::UnicodeString(originalRun.characters(), originalRun.length()), UNORM_NFC, 0 , normalizedString, error);
if (U_FAILURE(error))
return originalRun;
m_normalizedBuffer.set(new UChar[normalizedString.length() + 1]);
normalizedString.extract(m_normalizedBuffer.get(), normalizedString.length() + 1, error);
ASSERT(U_SUCCESS(error));
for (unsigned i = 0; i < normalizedString.length(); ++i) {
if (Font::treatAsSpace(m_normalizedBuffer[i]))
m_normalizedBuffer[i] = ' ';
}
m_normalizedRun.set(new TextRun(originalRun));
m_normalizedRun->setText(m_normalizedBuffer.get(), normalizedString.length());
return *m_normalizedRun;
}
void setupFontForScriptRun()
{
const FontData* fontData = m_font->glyphDataForCharacter(m_item.string[m_item.item.pos], false, false).fontData;
const FontPlatformData& platformData = fontData->fontDataForCharacter(' ')->platformData();
m_item.face = platformData.harfbuzzFace();
void* opaquePlatformData = const_cast<FontPlatformData*>(&platformData);
m_item.font->userData = opaquePlatformData;
}
HB_FontRec* allocHarfbuzzFont()
{
HB_FontRec* font = reinterpret_cast<HB_FontRec*>(fastMalloc(sizeof(HB_FontRec)));
memset(font, 0, sizeof(HB_FontRec));
font->klass = &harfbuzzSkiaClass;
font->userData = 0;
font->x_ppem = 1;
font->y_ppem = 1;
font->x_scale = 1;
font->y_scale = 1;
return font;
}
void deleteGlyphArrays()
{
delete[] m_item.glyphs;
delete[] m_item.attributes;
delete[] m_item.advances;
delete[] m_item.offsets;
delete[] m_glyphs16;
delete[] m_xPositions;
}
bool createGlyphArrays()
{
m_item.glyphs = new HB_Glyph[m_maxGlyphs];
m_item.attributes = new HB_GlyphAttributes[m_maxGlyphs];
m_item.advances = new HB_Fixed[m_maxGlyphs];
m_item.offsets = new HB_FixedPoint[m_maxGlyphs];
memset(m_item.offsets, 0, m_maxGlyphs * sizeof(HB_FixedPoint));
m_glyphs16 = new uint16_t[m_maxGlyphs];
m_xPositions = new SkScalar[m_maxGlyphs];
return m_item.glyphs
&& m_item.attributes
&& m_item.advances
&& m_item.offsets
&& m_glyphs16
&& m_xPositions;
}
bool expandGlyphArrays()
{
deleteGlyphArrays();
m_maxGlyphs <<= 1;
return createGlyphArrays();
}
bool shapeGlyphs()
{
for (;;) {
m_item.num_glyphs = m_maxGlyphs;
HB_ShapeItem(&m_item);
if (m_item.num_glyphs < m_maxGlyphs)
break;
if (!expandGlyphArrays())
return false;
}
return true;
}
void setGlyphXPositions(bool isRTL)
{
double position = 0;
for (int iter = 0; iter < m_item.num_glyphs; ++iter) {
int i = isRTL ? m_item.num_glyphs - iter - 1 : iter;
m_glyphs16[i] = m_item.glyphs[i];
double offsetX = truncateFixedPointToInteger(m_item.offsets[i].x);
m_xPositions[i] = m_offsetX + position + offsetX;
double advance = truncateFixedPointToInteger(m_item.advances[i]);
position += advance;
}
m_pixelWidth = position;
m_offsetX += m_pixelWidth;
}
const Font* const m_font;
HB_ShaperItem m_item;
uint16_t* m_glyphs16; SkScalar* m_xPositions; ssize_t m_indexOfNextScriptRun; const unsigned m_startingX; unsigned m_offsetX; unsigned m_pixelWidth; unsigned m_numCodePoints; unsigned m_maxGlyphs;
OwnPtr<TextRun> m_normalizedRun;
OwnArrayPtr<UChar> m_normalizedBuffer; const TextRun& m_run;
bool m_iterateBackwards;
};
static void setupForTextPainting(SkPaint* paint, SkColor color)
{
paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint->setColor(color);
}
void Font::drawComplexText(GraphicsContext* gc, const TextRun& run,
const FloatPoint& point, int from, int to) const
{
if (!run.length())
return;
SkCanvas* canvas = gc->platformContext()->canvas();
int textMode = gc->platformContext()->getTextDrawingMode();
bool fill = textMode & cTextFill;
bool stroke = (textMode & cTextStroke)
&& gc->platformContext()->getStrokeStyle() != NoStroke
&& gc->platformContext()->getStrokeThickness() > 0;
if (!fill && !stroke)
return;
SkPaint strokePaint, fillPaint;
if (fill) {
gc->platformContext()->setupPaintForFilling(&fillPaint);
setupForTextPainting(&fillPaint, gc->fillColor().rgb());
}
if (stroke) {
gc->platformContext()->setupPaintForStroking(&strokePaint, 0, 0);
setupForTextPainting(&strokePaint, gc->strokeColor().rgb());
}
TextRunWalker walker(run, point.x(), this);
bool haveMultipleLayers = isCanvasMultiLayered(canvas);
while (walker.nextScriptRun()) {
if (fill) {
walker.fontPlatformDataForScriptRun()->setupPaint(&fillPaint);
adjustTextRenderMode(&fillPaint, haveMultipleLayers);
canvas->drawPosTextH(walker.glyphs(), walker.length() << 1, walker.xPositions(), point.y(), fillPaint);
}
if (stroke) {
walker.fontPlatformDataForScriptRun()->setupPaint(&strokePaint);
adjustTextRenderMode(&strokePaint, haveMultipleLayers);
canvas->drawPosTextH(walker.glyphs(), walker.length() << 1, walker.xPositions(), point.y(), strokePaint);
}
}
}
float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>* , GlyphOverflow* ) const
{
TextRunWalker walker(run, 0, this);
return walker.widthOfFullRun();
}
static int glyphIndexForXPositionInScriptRun(const TextRunWalker& walker, int x)
{
const HB_Fixed* advances = walker.advances();
int glyphIndex;
if (walker.rtl()) {
for (glyphIndex = walker.length() - 1; glyphIndex >= 0; --glyphIndex) {
if (x < truncateFixedPointToInteger(advances[glyphIndex]))
break;
x -= truncateFixedPointToInteger(advances[glyphIndex]);
}
} else {
for (glyphIndex = 0; glyphIndex < walker.length(); ++glyphIndex) {
if (x < truncateFixedPointToInteger(advances[glyphIndex]))
break;
x -= truncateFixedPointToInteger(advances[glyphIndex]);
}
}
return glyphIndex;
}
int Font::offsetForPositionForComplexText(const TextRun& run, int x,
bool includePartialGlyphs) const
{
TextRunWalker walker(run, 0, this);
unsigned totalCodePoints = 0;
if (walker.rtl()) {
ssize_t offset = 0;
while (offset < run.length()) {
utf16_to_code_point(run.characters(), run.length(), &offset);
totalCodePoints++;
}
}
unsigned basePosition = totalCodePoints;
while (walker.nextScriptRun()) {
if (walker.rtl())
basePosition -= walker.numCodePoints();
if (x >= 0 && x < walker.width()) {
const int glyphIndex = glyphIndexForXPositionInScriptRun(walker, x);
const unsigned short* log = walker.logClusters();
for (unsigned j = 0; j < walker.numCodePoints(); ++j) {
if (log[j] >= glyphIndex)
return basePosition + j;
}
return basePosition + walker.numCodePoints() - 1;
}
x -= walker.width();
if (!walker.rtl())
basePosition += walker.numCodePoints();
}
return basePosition;
}
FloatRect Font::selectionRectForComplexText(const TextRun& run,
const IntPoint& point, int height,
int from, int to) const
{
int fromX = -1, toX = -1, fromAdvance = -1, toAdvance = -1;
TextRunWalker walker(run, 0, this);
int base = walker.rtl() ? walker.widthOfFullRun() : 0;
const int leftEdge = base;
walker.setBackwardsIteration(false);
while (walker.nextScriptRun() && (fromX == -1 || toX == -1)) {
walker.setXOffsetToZero();
if (walker.rtl())
base -= walker.width();
if (fromX == -1 && from < walker.numCodePoints()) {
int glyph = walker.logClusters()[from];
fromX = base + walker.xPositions()[glyph];
fromAdvance = walker.advances()[glyph];
} else
from -= walker.numCodePoints();
if (toX == -1 && to < walker.numCodePoints()) {
int glyph = walker.logClusters()[to];
toX = base + walker.xPositions()[glyph];
toAdvance = walker.advances()[glyph];
} else
to -= walker.numCodePoints();
if (!walker.rtl())
base += walker.width();
}
const int rightEdge = base;
if (fromX == -1 && !from)
fromX = leftEdge;
else if (walker.rtl())
fromX += truncateFixedPointToInteger(fromAdvance);
if (toX == -1 && !to)
toX = rightEdge;
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);
}
}