• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
CachedOutlineKeyWebCore::CachedOutlineKey50     CachedOutlineKey() : font(0), glyph(0), path(0) {}
CachedOutlineKeyWebCore::CachedOutlineKey51     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 
operator ==(const CachedOutlineKey & a,const CachedOutlineKey & b)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 {
hashWebCore::CachedOutlineKeyHash67     static unsigned hash(const CachedOutlineKey& key)
68     {
69         unsigned keyBytes;
70         memcpy(&keyBytes, &key.font, sizeof(unsigned));
71         return keyBytes + key.glyph;
72     }
73 
equalWebCore::CachedOutlineKeyHash74     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 
FIXEDToSkScalar(FIXED fixed)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.
deleteOutline(OutlineCache::iterator deleteMe)99 static void deleteOutline(OutlineCache::iterator deleteMe)
100 {
101     delete deleteMe->path;
102     outlineCache.remove(deleteMe);
103 }
104 
addPolyCurveToPath(const TTPOLYCURVE * polyCurve,SkPath * path)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.
getPathForGlyph(HDC dc,WORD glyph,SkPath * path)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.
lookupOrCreatePathForGlyph(HDC hdc,HFONT font,WORD glyph)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 
removePathsForFont(HFONT hfont)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 
windowsCanHandleDrawTextShadow(GraphicsContext * context)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 
windowsCanHandleTextDrawing(GraphicsContext * context)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 
windowsCanHandleTextDrawingWithoutShadow(GraphicsContext * context)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.
skiaDrawText(HFONT hfont,HDC dc,PlatformContextSkia * platformContext,const SkPoint & point,SkPaint * paint,const WORD * glyphs,const int * advances,const GOFFSET * offsets,int numGlyphs)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 
setupPaintForFont(HFONT hfont,SkPaint * paint)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 
paintSkiaText(GraphicsContext * context,HFONT hfont,int numGlyphs,const WORD * glyphs,const int * advances,const GOFFSET * offsets,const SkPoint * origin)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