Home | History | Annotate | Download | only in skia
      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 
     42 #include <wtf/ListHashSet.h>
     43 #include <wtf/Vector.h>
     44 
     45 namespace WebCore {
     46 
     47 struct CachedOutlineKey {
     48     CachedOutlineKey() : font(0), glyph(0), path(0) {}
     49     CachedOutlineKey(HFONT f, WORD g) : font(f), glyph(g), path(0) {}
     50 
     51     HFONT font;
     52     WORD glyph;
     53 
     54     // The lifetime of this pointer is managed externally to this class. Be sure
     55     // to call DeleteOutline to remove items.
     56     SkPath* path;
     57 };
     58 
     59 const bool operator==(const CachedOutlineKey& a, const CachedOutlineKey& b)
     60 {
     61     return a.font == b.font && a.glyph == b.glyph;
     62 }
     63 
     64 struct CachedOutlineKeyHash {
     65     static unsigned hash(const CachedOutlineKey& key)
     66     {
     67         unsigned keyBytes;
     68         memcpy(&keyBytes, &key.font, sizeof(unsigned));
     69         return keyBytes + key.glyph;
     70     }
     71 
     72     static unsigned equal(const CachedOutlineKey& a, const CachedOutlineKey& b)
     73     {
     74         return a.font == b.font && a.glyph == b.glyph;
     75     }
     76 
     77     static const bool safeToCompareToEmptyOrDeleted = true;
     78 };
     79 
     80 typedef ListHashSet<CachedOutlineKey, CachedOutlineKeyHash> OutlineCache;
     81 
     82 // FIXME: Convert from static constructor to accessor function. WebCore tries to
     83 // avoid global constructors to save on start-up time.
     84 static OutlineCache outlineCache;
     85 
     86 // The global number of glyph outlines we'll cache.
     87 static const int outlineCacheSize = 256;
     88 
     89 static SkScalar FIXEDToSkScalar(FIXED fixed)
     90 {
     91     SkFixed skFixed;
     92     memcpy(&skFixed, &fixed, sizeof(SkFixed));
     93     return SkFixedToScalar(skFixed);
     94 }
     95 
     96 // Removes the given key from the cached outlines, also deleting the path.
     97 static void deleteOutline(OutlineCache::iterator deleteMe)
     98 {
     99     delete deleteMe->path;
    100     outlineCache.remove(deleteMe);
    101 }
    102 
    103 static void addPolyCurveToPath(const TTPOLYCURVE* polyCurve, SkPath* path)
    104 {
    105     switch (polyCurve->wType) {
    106     case TT_PRIM_LINE:
    107         for (WORD i = 0; i < polyCurve->cpfx; i++) {
    108           path->lineTo(FIXEDToSkScalar(polyCurve->apfx[i].x), -FIXEDToSkScalar(polyCurve->apfx[i].y));
    109         }
    110         break;
    111 
    112     case TT_PRIM_QSPLINE:
    113         // FIXME: doesn't this duplicate points if we do the loop > once?
    114         for (WORD i = 0; i < polyCurve->cpfx - 1; i++) {
    115             SkScalar bx = FIXEDToSkScalar(polyCurve->apfx[i].x);
    116             SkScalar by = FIXEDToSkScalar(polyCurve->apfx[i].y);
    117 
    118             SkScalar cx = FIXEDToSkScalar(polyCurve->apfx[i + 1].x);
    119             SkScalar cy = FIXEDToSkScalar(polyCurve->apfx[i + 1].y);
    120             if (i < polyCurve->cpfx - 2) {
    121                 // We're not the last point, compute C.
    122                 cx = SkScalarAve(bx, cx);
    123                 cy = SkScalarAve(by, cy);
    124             }
    125 
    126             // Need to flip the y coordinates since the font's coordinate system is
    127             // flipped from ours vertically.
    128             path->quadTo(bx, -by, cx, -cy);
    129         }
    130         break;
    131 
    132     case TT_PRIM_CSPLINE:
    133         // FIXME
    134         break;
    135     }
    136 }
    137 
    138 // The size of the glyph path buffer.
    139 static const int glyphPathBufferSize = 4096;
    140 
    141 // Fills the given SkPath with the outline for the given glyph index. The font
    142 // currently selected into the given DC is used. Returns true on success.
    143 static bool getPathForGlyph(HDC dc, WORD glyph, SkPath* path)
    144 {
    145     char buffer[glyphPathBufferSize];
    146     GLYPHMETRICS gm;
    147     MAT2 mat = {{0, 1}, {0, 0}, {0, 0}, {0, 1}};  // Each one is (fract,value).
    148 
    149     DWORD totalSize = GetGlyphOutlineW(dc, glyph, GGO_GLYPH_INDEX | GGO_NATIVE,
    150                                        &gm, glyphPathBufferSize, buffer, &mat);
    151     if (totalSize == GDI_ERROR)
    152         return false;
    153 
    154     const char* curGlyph = buffer;
    155     const char* endGlyph = &buffer[totalSize];
    156     while (curGlyph < endGlyph) {
    157         const TTPOLYGONHEADER* polyHeader =
    158             reinterpret_cast<const TTPOLYGONHEADER*>(curGlyph);
    159         path->moveTo(FIXEDToSkScalar(polyHeader->pfxStart.x),
    160                      -FIXEDToSkScalar(polyHeader->pfxStart.y));
    161 
    162         const char* curPoly = curGlyph + sizeof(TTPOLYGONHEADER);
    163         const char* endPoly = curGlyph + polyHeader->cb;
    164         while (curPoly < endPoly) {
    165             const TTPOLYCURVE* polyCurve =
    166                 reinterpret_cast<const TTPOLYCURVE*>(curPoly);
    167             addPolyCurveToPath(polyCurve, path);
    168             curPoly += sizeof(WORD) * 2 + sizeof(POINTFX) * polyCurve->cpfx;
    169         }
    170         path->close();
    171         curGlyph += polyHeader->cb;
    172     }
    173 
    174     return true;
    175 }
    176 
    177 // Returns a SkPath corresponding to the give glyph in the given font. The font
    178 // should be selected into the given DC. The returned path is owned by the
    179 // hashtable. Returns 0 on error.
    180 const SkPath* SkiaWinOutlineCache::lookupOrCreatePathForGlyph(HDC hdc, HFONT font, WORD glyph)
    181 {
    182     CachedOutlineKey key(font, glyph);
    183     OutlineCache::iterator found = outlineCache.find(key);
    184     if (found != outlineCache.end()) {
    185         // Keep in MRU order by removing & reinserting the value.
    186         key = *found;
    187         outlineCache.remove(found);
    188         outlineCache.add(key);
    189         return key.path;
    190     }
    191 
    192     key.path = new SkPath;
    193     if (!getPathForGlyph(hdc, glyph, key.path))
    194       return 0;
    195 
    196     if (outlineCache.size() > outlineCacheSize)
    197         // The cache is too big, find the oldest value (first in the list).
    198         deleteOutline(outlineCache.begin());
    199 
    200     outlineCache.add(key);
    201     return key.path;
    202 }
    203 
    204 
    205 void SkiaWinOutlineCache::removePathsForFont(HFONT hfont)
    206 {
    207     // ListHashSet isn't the greatest structure for deleting stuff out of, but
    208     // removing entries will be relatively rare (we don't remove fonts much, nor
    209     // do we draw out own glyphs using these routines much either).
    210     //
    211     // We keep a list of all glyphs we're removing which we do in a separate
    212     // pass.
    213     Vector<CachedOutlineKey> outlinesToDelete;
    214     for (OutlineCache::iterator i = outlineCache.begin();
    215          i != outlineCache.end(); ++i)
    216         outlinesToDelete.append(*i);
    217 
    218     for (Vector<CachedOutlineKey>::iterator i = outlinesToDelete.begin();
    219          i != outlinesToDelete.end(); ++i)
    220         deleteOutline(outlineCache.find(*i));
    221 }
    222 
    223 bool windowsCanHandleDrawTextShadow(WebCore::GraphicsContext *context)
    224 {
    225     IntSize shadowSize;
    226     int shadowBlur;
    227     Color shadowColor;
    228 
    229     bool hasShadow = context->getShadow(shadowSize, shadowBlur, shadowColor);
    230     return (hasShadow && (shadowBlur == 0) && (shadowColor.alpha() == 255) && (context->fillColor().alpha() == 255));
    231 }
    232 
    233 bool windowsCanHandleTextDrawing(GraphicsContext* context)
    234 {
    235     // Check for non-translation transforms. Sometimes zooms will look better in
    236     // Skia, and sometimes better in Windows. The main problem is that zooming
    237     // in using Skia will show you the hinted outlines for the smaller size,
    238     // which look weird. All else being equal, it's better to use Windows' text
    239     // drawing, so we don't check for zooms.
    240     const AffineTransform& matrix = context->getCTM();
    241     if (matrix.b() != 0 || matrix.c() != 0)  // Check for skew.
    242         return false;
    243 
    244     // Check for stroke effects.
    245     if (context->platformContext()->getTextDrawingMode() != cTextFill)
    246         return false;
    247 
    248     // Check for gradients.
    249     if (context->fillGradient() || context->strokeGradient())
    250         return false;
    251 
    252     // Check for patterns.
    253     if (context->fillPattern() || context->strokePattern())
    254         return false;
    255 
    256     // Check for shadow effects.
    257     if (context->platformContext()->getDrawLooper() && (!windowsCanHandleDrawTextShadow(context)))
    258         return false;
    259 
    260     return true;
    261 }
    262 
    263 // Draws the given text string using skia.  Note that gradient or
    264 // pattern may be NULL, in which case a solid colour is used.
    265 static bool skiaDrawText(HFONT hfont,
    266                          HDC dc,
    267                          SkCanvas* canvas,
    268                          const SkPoint& point,
    269                          SkPaint* paint,
    270                          const WORD* glyphs,
    271                          const int* advances,
    272                          const GOFFSET* offsets,
    273                          int numGlyphs)
    274 {
    275     float x = point.fX, y = point.fY;
    276 
    277     for (int i = 0; i < numGlyphs; i++) {
    278         const SkPath* path = SkiaWinOutlineCache::lookupOrCreatePathForGlyph(dc, hfont, glyphs[i]);
    279         if (!path)
    280             return false;
    281 
    282         float offsetX = 0.0f, offsetY = 0.0f;
    283         if (offsets && (offsets[i].du != 0 || offsets[i].dv != 0)) {
    284             offsetX = offsets[i].du;
    285             offsetY = offsets[i].dv;
    286         }
    287 
    288         SkPath newPath;
    289         newPath.addPath(*path, x + offsetX, y + offsetY);
    290         canvas->drawPath(newPath, *paint);
    291 
    292         x += advances[i];
    293     }
    294 
    295     return true;
    296 }
    297 
    298 bool paintSkiaText(GraphicsContext* context,
    299                    HFONT hfont,
    300                    int numGlyphs,
    301                    const WORD* glyphs,
    302                    const int* advances,
    303                    const GOFFSET* offsets,
    304                    const SkPoint* origin)
    305 {
    306     HDC dc = GetDC(0);
    307     HGDIOBJ oldFont = SelectObject(dc, hfont);
    308 
    309     PlatformContextSkia* platformContext = context->platformContext();
    310     int textMode = platformContext->getTextDrawingMode();
    311 
    312     // Filling (if necessary). This is the common case.
    313     SkPaint paint;
    314     platformContext->setupPaintForFilling(&paint);
    315     paint.setFlags(SkPaint::kAntiAlias_Flag);
    316     bool didFill = false;
    317 
    318     if ((textMode & cTextFill) && SkColorGetA(paint.getColor())) {
    319         if (!skiaDrawText(hfont, dc, platformContext->canvas(), *origin, &paint,
    320                           &glyphs[0], &advances[0], &offsets[0], numGlyphs))
    321             return false;
    322         didFill = true;
    323     }
    324 
    325     // Stroking on top (if necessary).
    326     if ((textMode & WebCore::cTextStroke)
    327         && platformContext->getStrokeStyle() != NoStroke
    328         && platformContext->getStrokeThickness() > 0) {
    329 
    330         paint.reset();
    331         platformContext->setupPaintForStroking(&paint, 0, 0);
    332         paint.setFlags(SkPaint::kAntiAlias_Flag);
    333         if (didFill) {
    334             // If there is a shadow and we filled above, there will already be
    335             // a shadow. We don't want to draw it again or it will be too dark
    336             // and it will go on top of the fill.
    337             //
    338             // Note that this isn't strictly correct, since the stroke could be
    339             // very thick and the shadow wouldn't account for this. The "right"
    340             // thing would be to draw to a new layer and then draw that layer
    341             // with a shadow. But this is a lot of extra work for something
    342             // that isn't normally an issue.
    343             paint.setLooper(0)->safeUnref();
    344         }
    345 
    346         if (!skiaDrawText(hfont, dc, platformContext->canvas(), *origin, &paint,
    347                           &glyphs[0], &advances[0], &offsets[0], numGlyphs))
    348             return false;
    349     }
    350 
    351     SelectObject(dc, oldFont);
    352     ReleaseDC(0, dc);
    353 
    354     return true;
    355 }
    356 
    357 }  // namespace WebCore
    358