1/*
2 * Copyright (c) 2008, Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#include "SkiaFontWin.h"
33
34#include "AffineTransform.h"
35#include "PlatformContextSkia.h"
36#include "Gradient.h"
37#include "Pattern.h"
38#include "SkCanvas.h"
39#include "SkPaint.h"
40#include "SkShader.h"
41#include "SkTemplates.h"
42#include "SkTypeface_win.h"
43
44#include <wtf/ListHashSet.h>
45#include <wtf/Vector.h>
46
47namespace WebCore {
48
49struct CachedOutlineKey {
50    CachedOutlineKey() : font(0), glyph(0), path(0) {}
51    CachedOutlineKey(HFONT f, WORD g) : font(f), glyph(g), path(0) {}
52
53    HFONT font;
54    WORD glyph;
55
56    // The lifetime of this pointer is managed externally to this class. Be sure
57    // to call DeleteOutline to remove items.
58    SkPath* path;
59};
60
61const bool operator==(const CachedOutlineKey& a, const CachedOutlineKey& b)
62{
63    return a.font == b.font && a.glyph == b.glyph;
64}
65
66struct CachedOutlineKeyHash {
67    static unsigned hash(const CachedOutlineKey& key)
68    {
69        unsigned keyBytes;
70        memcpy(&keyBytes, &key.font, sizeof(unsigned));
71        return keyBytes + key.glyph;
72    }
73
74    static unsigned equal(const CachedOutlineKey& a, const CachedOutlineKey& b)
75    {
76        return a.font == b.font && a.glyph == b.glyph;
77    }
78
79    static const bool safeToCompareToEmptyOrDeleted = true;
80};
81
82// The global number of glyph outlines we'll cache.
83static const int outlineCacheSize = 256;
84
85typedef ListHashSet<CachedOutlineKey, outlineCacheSize+1, CachedOutlineKeyHash> OutlineCache;
86
87// FIXME: Convert from static constructor to accessor function. WebCore tries to
88// avoid global constructors to save on start-up time.
89static OutlineCache outlineCache;
90
91static SkScalar FIXEDToSkScalar(FIXED fixed)
92{
93    SkFixed skFixed;
94    memcpy(&skFixed, &fixed, sizeof(SkFixed));
95    return SkFixedToScalar(skFixed);
96}
97
98// Removes the given key from the cached outlines, also deleting the path.
99static void deleteOutline(OutlineCache::iterator deleteMe)
100{
101    delete deleteMe->path;
102    outlineCache.remove(deleteMe);
103}
104
105static void addPolyCurveToPath(const TTPOLYCURVE* polyCurve, SkPath* path)
106{
107    switch (polyCurve->wType) {
108    case TT_PRIM_LINE:
109        for (WORD i = 0; i < polyCurve->cpfx; i++) {
110          path->lineTo(FIXEDToSkScalar(polyCurve->apfx[i].x), -FIXEDToSkScalar(polyCurve->apfx[i].y));
111        }
112        break;
113
114    case TT_PRIM_QSPLINE:
115        // FIXME: doesn't this duplicate points if we do the loop > once?
116        for (WORD i = 0; i < polyCurve->cpfx - 1; i++) {
117            SkScalar bx = FIXEDToSkScalar(polyCurve->apfx[i].x);
118            SkScalar by = FIXEDToSkScalar(polyCurve->apfx[i].y);
119
120            SkScalar cx = FIXEDToSkScalar(polyCurve->apfx[i + 1].x);
121            SkScalar cy = FIXEDToSkScalar(polyCurve->apfx[i + 1].y);
122            if (i < polyCurve->cpfx - 2) {
123                // We're not the last point, compute C.
124                cx = SkScalarAve(bx, cx);
125                cy = SkScalarAve(by, cy);
126            }
127
128            // Need to flip the y coordinates since the font's coordinate system is
129            // flipped from ours vertically.
130            path->quadTo(bx, -by, cx, -cy);
131        }
132        break;
133
134    case TT_PRIM_CSPLINE:
135        // FIXME
136        break;
137    }
138}
139
140// The size of the glyph path buffer.
141static const int glyphPathBufferSize = 4096;
142
143// Fills the given SkPath with the outline for the given glyph index. The font
144// currently selected into the given DC is used. Returns true on success.
145static bool getPathForGlyph(HDC dc, WORD glyph, SkPath* path)
146{
147    char buffer[glyphPathBufferSize];
148    GLYPHMETRICS gm;
149    MAT2 mat = {{0, 1}, {0, 0}, {0, 0}, {0, 1}};  // Each one is (fract,value).
150
151    DWORD totalSize = GetGlyphOutlineW(dc, glyph, GGO_GLYPH_INDEX | GGO_NATIVE,
152                                       &gm, glyphPathBufferSize, buffer, &mat);
153    if (totalSize == GDI_ERROR)
154        return false;
155
156    const char* curGlyph = buffer;
157    const char* endGlyph = &buffer[totalSize];
158    while (curGlyph < endGlyph) {
159        const TTPOLYGONHEADER* polyHeader =
160            reinterpret_cast<const TTPOLYGONHEADER*>(curGlyph);
161        path->moveTo(FIXEDToSkScalar(polyHeader->pfxStart.x),
162                     -FIXEDToSkScalar(polyHeader->pfxStart.y));
163
164        const char* curPoly = curGlyph + sizeof(TTPOLYGONHEADER);
165        const char* endPoly = curGlyph + polyHeader->cb;
166        while (curPoly < endPoly) {
167            const TTPOLYCURVE* polyCurve =
168                reinterpret_cast<const TTPOLYCURVE*>(curPoly);
169            addPolyCurveToPath(polyCurve, path);
170            curPoly += sizeof(WORD) * 2 + sizeof(POINTFX) * polyCurve->cpfx;
171        }
172        path->close();
173        curGlyph += polyHeader->cb;
174    }
175
176    return true;
177}
178
179// Returns a SkPath corresponding to the give glyph in the given font. The font
180// should be selected into the given DC. The returned path is owned by the
181// hashtable. Returns 0 on error.
182const SkPath* SkiaWinOutlineCache::lookupOrCreatePathForGlyph(HDC hdc, HFONT font, WORD glyph)
183{
184    CachedOutlineKey key(font, glyph);
185    OutlineCache::iterator found = outlineCache.find(key);
186    if (found != outlineCache.end()) {
187        // Keep in MRU order by removing & reinserting the value.
188        key = *found;
189        outlineCache.remove(found);
190        outlineCache.add(key);
191        return key.path;
192    }
193
194    key.path = new SkPath;
195    if (!getPathForGlyph(hdc, glyph, key.path))
196      return 0;
197
198    if (outlineCache.size() > outlineCacheSize)
199        // The cache is too big, find the oldest value (first in the list).
200        deleteOutline(outlineCache.begin());
201
202    outlineCache.add(key);
203    return key.path;
204}
205
206
207void SkiaWinOutlineCache::removePathsForFont(HFONT hfont)
208{
209    // ListHashSet isn't the greatest structure for deleting stuff out of, but
210    // removing entries will be relatively rare (we don't remove fonts much, nor
211    // do we draw out own glyphs using these routines much either).
212    //
213    // We keep a list of all glyphs we're removing which we do in a separate
214    // pass.
215    Vector<CachedOutlineKey> outlinesToDelete;
216    for (OutlineCache::iterator i = outlineCache.begin();
217         i != outlineCache.end(); ++i)
218        outlinesToDelete.append(*i);
219
220    for (Vector<CachedOutlineKey>::iterator i = outlinesToDelete.begin();
221         i != outlinesToDelete.end(); ++i)
222        deleteOutline(outlineCache.find(*i));
223}
224
225bool windowsCanHandleDrawTextShadow(GraphicsContext *context)
226{
227    FloatSize shadowOffset;
228    float shadowBlur;
229    Color shadowColor;
230    ColorSpace shadowColorSpace;
231
232    bool hasShadow = context->getShadow(shadowOffset, shadowBlur, shadowColor, shadowColorSpace);
233    return !hasShadow || (!shadowBlur && (shadowColor.alpha() == 255) && (context->fillColor().alpha() == 255));
234}
235
236bool windowsCanHandleTextDrawing(GraphicsContext* context)
237{
238    if (!windowsCanHandleTextDrawingWithoutShadow(context))
239        return false;
240
241    // Check for shadow effects.
242    if (!windowsCanHandleDrawTextShadow(context))
243        return false;
244
245    return true;
246}
247
248bool windowsCanHandleTextDrawingWithoutShadow(GraphicsContext* context)
249{
250    // Check for non-translation transforms. Sometimes zooms will look better in
251    // Skia, and sometimes better in Windows. The main problem is that zooming
252    // in using Skia will show you the hinted outlines for the smaller size,
253    // which look weird. All else being equal, it's better to use Windows' text
254    // drawing, so we don't check for zooms.
255    const AffineTransform& matrix = context->getCTM();
256    if (matrix.b() != 0 || matrix.c() != 0)  // Check for skew.
257        return false;
258
259    // Check for stroke effects.
260    if (context->platformContext()->getTextDrawingMode() != TextModeFill)
261        return false;
262
263    // Check for gradients.
264    if (context->fillGradient() || context->strokeGradient())
265        return false;
266
267    // Check for patterns.
268    if (context->fillPattern() || context->strokePattern())
269        return false;
270
271    if (!context->platformContext()->isNativeFontRenderingAllowed())
272        return false;
273
274    return true;
275}
276
277// Draws the given text string using skia.  Note that gradient or
278// pattern may be NULL, in which case a solid colour is used.
279static bool skiaDrawText(HFONT hfont,
280                         HDC dc,
281                         PlatformContextSkia* platformContext,
282                         const SkPoint& point,
283                         SkPaint* paint,
284                         const WORD* glyphs,
285                         const int* advances,
286                         const GOFFSET* offsets,
287                         int numGlyphs)
288{
289    SkCanvas* canvas = platformContext->canvas();
290    if (!platformContext->isNativeFontRenderingAllowed()) {
291        SkASSERT(sizeof(WORD) == sizeof(uint16_t));
292
293        // Reserve space for 64 glyphs on the stack. If numGlyphs is larger, the array
294        // will dynamically allocate it space for numGlyph glyphs.
295        static const size_t kLocalGlyphMax = 64;
296        SkAutoSTArray<kLocalGlyphMax, SkPoint> posStorage(numGlyphs);
297        SkPoint* pos = posStorage.get();
298        SkScalar x = point.fX;
299        SkScalar y = point.fY;
300        for (int i = 0; i < numGlyphs; i++) {
301            pos[i].set(x + (offsets ? offsets[i].du : 0),
302                       y + (offsets ? offsets[i].dv : 0));
303            x += SkIntToScalar(advances[i]);
304        }
305        canvas->drawPosText(glyphs, numGlyphs * sizeof(uint16_t), pos, *paint);
306    } else {
307        float x = point.fX, y = point.fY;
308
309        for (int i = 0; i < numGlyphs; i++) {
310            const SkPath* path = SkiaWinOutlineCache::lookupOrCreatePathForGlyph(dc, hfont, glyphs[i]);
311            if (!path)
312                return false;
313
314            float offsetX = 0.0f, offsetY = 0.0f;
315            if (offsets && (offsets[i].du || offsets[i].dv)) {
316                offsetX = offsets[i].du;
317                offsetY = offsets[i].dv;
318            }
319
320            SkPath newPath;
321            newPath.addPath(*path, x + offsetX, y + offsetY);
322            canvas->drawPath(newPath, *paint);
323
324            x += advances[i];
325        }
326    }
327    return true;
328}
329
330static void setupPaintForFont(HFONT hfont, SkPaint* paint)
331{
332    //  FIXME:
333    //  Much of this logic could also happen in
334    //  FontCustomPlatformData::fontPlatformData and be cached,
335    //  allowing us to avoid talking to GDI at this point.
336    //
337    LOGFONT info;
338    GetObject(hfont, sizeof(info), &info);
339    int size = info.lfHeight;
340    if (size < 0)
341        size = -size; // We don't let GDI dpi-scale us (see SkFontHost_win.cpp).
342    paint->setTextSize(SkIntToScalar(size));
343
344    SkTypeface* face = SkCreateTypefaceFromLOGFONT(info);
345    paint->setTypeface(face);
346    SkSafeUnref(face);
347}
348
349bool paintSkiaText(GraphicsContext* context,
350                   HFONT hfont,
351                   int numGlyphs,
352                   const WORD* glyphs,
353                   const int* advances,
354                   const GOFFSET* offsets,
355                   const SkPoint* origin)
356{
357    HDC dc = GetDC(0);
358    HGDIOBJ oldFont = SelectObject(dc, hfont);
359
360    PlatformContextSkia* platformContext = context->platformContext();
361    TextDrawingModeFlags textMode = platformContext->getTextDrawingMode();
362
363    // Filling (if necessary). This is the common case.
364    SkPaint paint;
365    platformContext->setupPaintForFilling(&paint);
366    paint.setFlags(SkPaint::kAntiAlias_Flag);
367    if (!platformContext->isNativeFontRenderingAllowed()) {
368        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
369        setupPaintForFont(hfont, &paint);
370    }
371    bool didFill = false;
372
373    if ((textMode & TextModeFill) && (SkColorGetA(paint.getColor()) || paint.getLooper())) {
374        if (!skiaDrawText(hfont, dc, platformContext, *origin, &paint,
375                          &glyphs[0], &advances[0], &offsets[0], numGlyphs))
376            return false;
377        didFill = true;
378    }
379
380    // Stroking on top (if necessary).
381    if ((textMode & TextModeStroke)
382        && platformContext->getStrokeStyle() != NoStroke
383        && platformContext->getStrokeThickness() > 0) {
384
385        paint.reset();
386        platformContext->setupPaintForStroking(&paint, 0, 0);
387        paint.setFlags(SkPaint::kAntiAlias_Flag);
388        if (!platformContext->isNativeFontRenderingAllowed()) {
389            paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
390            setupPaintForFont(hfont, &paint);
391        }
392
393        if (didFill) {
394            // If there is a shadow and we filled above, there will already be
395            // a shadow. We don't want to draw it again or it will be too dark
396            // and it will go on top of the fill.
397            //
398            // Note that this isn't strictly correct, since the stroke could be
399            // very thick and the shadow wouldn't account for this. The "right"
400            // thing would be to draw to a new layer and then draw that layer
401            // with a shadow. But this is a lot of extra work for something
402            // that isn't normally an issue.
403            paint.setLooper(0);
404        }
405
406        if (!skiaDrawText(hfont, dc, platformContext, *origin, &paint,
407                          &glyphs[0], &advances[0], &offsets[0], numGlyphs))
408            return false;
409    }
410
411    SelectObject(dc, oldFont);
412    ReleaseDC(0, dc);
413
414    return true;
415}
416
417}  // namespace WebCore
418