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 #include "SkTemplates.h"
     42 #include "SkTypeface_win.h"
     43 
     44 #include <wtf/ListHashSet.h>
     45 #include <wtf/Vector.h>
     46 
     47 namespace WebCore {
     48 
     49 struct 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 
     61 const bool operator==(const CachedOutlineKey& a, const CachedOutlineKey& b)
     62 {
     63     return a.font == b.font && a.glyph == b.glyph;
     64 }
     65 
     66 struct 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.
     83 static const int outlineCacheSize = 256;
     84 
     85 typedef 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.
     89 static OutlineCache outlineCache;
     90 
     91 static 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.
     99 static void deleteOutline(OutlineCache::iterator deleteMe)
    100 {
    101     delete deleteMe->path;
    102     outlineCache.remove(deleteMe);
    103 }
    104 
    105 static 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.
    141 static 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.
    145 static 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.
    182 const 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 
    207 void 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 
    225 bool 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 
    236 bool 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 
    248 bool 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.
    279 static 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 
    330 static 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 
    349 bool 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