#include "config.h"
#include "SkiaFontWin.h"
#include "PlatformContextSkia.h"
#include "Gradient.h"
#include "Pattern.h"
#include "SkCanvas.h"
#include "SkPaint.h"
#include "SkShader.h"
#include "TransformationMatrix.h"
#include <wtf/ListHashSet.h>
#include <wtf/Vector.h>
namespace WebCore {
struct CachedOutlineKey {
CachedOutlineKey() : font(0), glyph(0), path(0) {}
CachedOutlineKey(HFONT f, WORD g) : font(f), glyph(g), path(0) {}
HFONT font;
WORD glyph;
SkPath* path;
};
const bool operator==(const CachedOutlineKey& a, const CachedOutlineKey& b)
{
return a.font == b.font && a.glyph == b.glyph;
}
struct CachedOutlineKeyHash {
static unsigned hash(const CachedOutlineKey& key)
{
unsigned keyBytes;
memcpy(&keyBytes, &key.font, sizeof(unsigned));
return keyBytes + key.glyph;
}
static unsigned equal(const CachedOutlineKey& a, const CachedOutlineKey& b)
{
return a.font == b.font && a.glyph == b.glyph;
}
static const bool safeToCompareToEmptyOrDeleted = true;
};
typedef ListHashSet<CachedOutlineKey, CachedOutlineKeyHash> OutlineCache;
static OutlineCache outlineCache;
static const int outlineCacheSize = 256;
static SkScalar FIXEDToSkScalar(FIXED fixed)
{
SkFixed skFixed;
memcpy(&skFixed, &fixed, sizeof(SkFixed));
return SkFixedToScalar(skFixed);
}
static void deleteOutline(OutlineCache::iterator deleteMe)
{
delete deleteMe->path;
outlineCache.remove(deleteMe);
}
static void addPolyCurveToPath(const TTPOLYCURVE* polyCurve, SkPath* path)
{
switch (polyCurve->wType) {
case TT_PRIM_LINE:
for (WORD i = 0; i < polyCurve->cpfx; i++) {
path->lineTo(FIXEDToSkScalar(polyCurve->apfx[i].x), -FIXEDToSkScalar(polyCurve->apfx[i].y));
}
break;
case TT_PRIM_QSPLINE:
for (WORD i = 0; i < polyCurve->cpfx - 1; i++) {
SkScalar bx = FIXEDToSkScalar(polyCurve->apfx[i].x);
SkScalar by = FIXEDToSkScalar(polyCurve->apfx[i].y);
SkScalar cx = FIXEDToSkScalar(polyCurve->apfx[i + 1].x);
SkScalar cy = FIXEDToSkScalar(polyCurve->apfx[i + 1].y);
if (i < polyCurve->cpfx - 2) {
cx = SkScalarAve(bx, cx);
cy = SkScalarAve(by, cy);
}
path->quadTo(bx, -by, cx, -cy);
}
break;
case TT_PRIM_CSPLINE:
break;
}
}
static const int glyphPathBufferSize = 4096;
static bool getPathForGlyph(HDC dc, WORD glyph, SkPath* path)
{
char buffer[glyphPathBufferSize];
GLYPHMETRICS gm;
MAT2 mat = {{0, 1}, {0, 0}, {0, 0}, {0, 1}};
DWORD totalSize = GetGlyphOutlineW(dc, glyph, GGO_GLYPH_INDEX | GGO_NATIVE,
&gm, glyphPathBufferSize, buffer, &mat);
if (totalSize == GDI_ERROR)
return false;
const char* curGlyph = buffer;
const char* endGlyph = &buffer[totalSize];
while (curGlyph < endGlyph) {
const TTPOLYGONHEADER* polyHeader =
reinterpret_cast<const TTPOLYGONHEADER*>(curGlyph);
path->moveTo(FIXEDToSkScalar(polyHeader->pfxStart.x),
-FIXEDToSkScalar(polyHeader->pfxStart.y));
const char* curPoly = curGlyph + sizeof(TTPOLYGONHEADER);
const char* endPoly = curGlyph + polyHeader->cb;
while (curPoly < endPoly) {
const TTPOLYCURVE* polyCurve =
reinterpret_cast<const TTPOLYCURVE*>(curPoly);
addPolyCurveToPath(polyCurve, path);
curPoly += sizeof(WORD) * 2 + sizeof(POINTFX) * polyCurve->cpfx;
}
path->close();
curGlyph += polyHeader->cb;
}
return true;
}
const SkPath* SkiaWinOutlineCache::lookupOrCreatePathForGlyph(HDC hdc, HFONT font, WORD glyph)
{
CachedOutlineKey key(font, glyph);
OutlineCache::iterator found = outlineCache.find(key);
if (found != outlineCache.end()) {
key = *found;
outlineCache.remove(found);
outlineCache.add(key);
return key.path;
}
key.path = new SkPath;
if (!getPathForGlyph(hdc, glyph, key.path))
return 0;
if (outlineCache.size() > outlineCacheSize)
deleteOutline(outlineCache.begin());
outlineCache.add(key);
return key.path;
}
void SkiaWinOutlineCache::removePathsForFont(HFONT hfont)
{
Vector<CachedOutlineKey> outlinesToDelete;
for (OutlineCache::iterator i = outlineCache.begin();
i != outlineCache.end(); ++i)
outlinesToDelete.append(*i);
for (Vector<CachedOutlineKey>::iterator i = outlinesToDelete.begin();
i != outlinesToDelete.end(); ++i)
deleteOutline(outlineCache.find(*i));
}
bool windowsCanHandleDrawTextShadow(WebCore::GraphicsContext *context)
{
IntSize shadowSize;
int shadowBlur;
Color shadowColor;
bool hasShadow = context->getShadow(shadowSize, shadowBlur, shadowColor);
return (hasShadow && (shadowBlur == 0) && (shadowColor.alpha() == 255) && (context->fillColor().alpha() == 255));
}
bool windowsCanHandleTextDrawing(GraphicsContext* context)
{
const TransformationMatrix& matrix = context->getCTM();
if (matrix.b() != 0 || matrix.c() != 0) return false;
if (context->platformContext()->getTextDrawingMode() != cTextFill)
return false;
if (context->fillGradient() || context->strokeGradient())
return false;
if (context->fillPattern() || context->strokePattern())
return false;
if (context->platformContext()->getDrawLooper() && (!windowsCanHandleDrawTextShadow(context)))
return false;
return true;
}
static bool skiaDrawText(HFONT hfont,
HDC dc,
SkCanvas* canvas,
const SkPoint& point,
SkPaint* paint,
const TransformationMatrix& transformationMatrix,
Gradient* gradient,
Pattern* pattern,
const WORD* glyphs,
const int* advances,
const GOFFSET* offsets,
int numGlyphs)
{
SkShader* shader = NULL;
if (gradient)
shader = gradient->platformGradient();
else if (pattern)
shader = pattern->createPlatformPattern(transformationMatrix);
paint->setShader(shader);
float x = point.fX, y = point.fY;
for (int i = 0; i < numGlyphs; i++) {
const SkPath* path = SkiaWinOutlineCache::lookupOrCreatePathForGlyph(dc, hfont, glyphs[i]);
if (!path)
return false;
float offsetX = 0.0f, offsetY = 0.0f;
if (offsets && (offsets[i].du != 0 || offsets[i].dv != 0)) {
offsetX = offsets[i].du;
offsetY = offsets[i].dv;
}
SkPath newPath;
newPath.addPath(*path, x + offsetX, y + offsetY);
canvas->drawPath(newPath, *paint);
x += advances[i];
}
return true;
}
bool paintSkiaText(GraphicsContext* context,
HFONT hfont,
int numGlyphs,
const WORD* glyphs,
const int* advances,
const GOFFSET* offsets,
const SkPoint* origin)
{
HDC dc = GetDC(0);
HGDIOBJ oldFont = SelectObject(dc, hfont);
PlatformContextSkia* platformContext = context->platformContext();
int textMode = platformContext->getTextDrawingMode();
SkPaint paint;
platformContext->setupPaintForFilling(&paint);
paint.setFlags(SkPaint::kAntiAlias_Flag);
bool didFill = false;
if ((textMode & cTextFill) && SkColorGetA(paint.getColor())) {
Gradient* fillGradient = 0;
Pattern* fillPattern = 0;
if (context->fillColorSpace() == GradientColorSpace)
fillGradient = context->fillGradient();
else if (context->fillColorSpace() == PatternColorSpace)
fillPattern = context->fillPattern();
if (!skiaDrawText(hfont, dc, platformContext->canvas(), *origin, &paint,
context->getCTM(), fillGradient, fillPattern,
&glyphs[0], &advances[0], &offsets[0], numGlyphs))
return false;
didFill = true;
}
if ((textMode & WebCore::cTextStroke)
&& platformContext->getStrokeStyle() != NoStroke
&& platformContext->getStrokeThickness() > 0) {
paint.reset();
platformContext->setupPaintForStroking(&paint, 0, 0);
paint.setFlags(SkPaint::kAntiAlias_Flag);
if (didFill) {
paint.setLooper(0)->safeUnref();
}
Gradient* strokeGradient = 0;
Pattern* strokePattern = 0;
if (context->strokeColorSpace() == GradientColorSpace)
strokeGradient = context->strokeGradient();
else if (context->strokeColorSpace() == PatternColorSpace)
strokePattern = context->strokePattern();
if (!skiaDrawText(hfont, dc, platformContext->canvas(), *origin, &paint,
context->getCTM(), strokeGradient, strokePattern,
&glyphs[0], &advances[0], &offsets[0], numGlyphs))
return false;
}
SelectObject(dc, oldFont);
ReleaseDC(0, dc);
return true;
}
}