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 {
CachedOutlineKeyWebCore::CachedOutlineKey48 CachedOutlineKey() : font(0), glyph(0), path(0) {}
CachedOutlineKeyWebCore::CachedOutlineKey49 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
operator ==(const CachedOutlineKey & a,const CachedOutlineKey & b)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 {
hashWebCore::CachedOutlineKeyHash65 static unsigned hash(const CachedOutlineKey& key)
66 {
67 unsigned keyBytes;
68 memcpy(&keyBytes, &key.font, sizeof(unsigned));
69 return keyBytes + key.glyph;
70 }
71
equalWebCore::CachedOutlineKeyHash72 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
FIXEDToSkScalar(FIXED fixed)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.
deleteOutline(OutlineCache::iterator deleteMe)97 static void deleteOutline(OutlineCache::iterator deleteMe)
98 {
99 delete deleteMe->path;
100 outlineCache.remove(deleteMe);
101 }
102
addPolyCurveToPath(const TTPOLYCURVE * polyCurve,SkPath * path)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.
getPathForGlyph(HDC dc,WORD glyph,SkPath * path)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.
lookupOrCreatePathForGlyph(HDC hdc,HFONT font,WORD glyph)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
removePathsForFont(HFONT hfont)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
windowsCanHandleDrawTextShadow(WebCore::GraphicsContext * context)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
windowsCanHandleTextDrawing(GraphicsContext * context)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.
skiaDrawText(HFONT hfont,HDC dc,SkCanvas * canvas,const SkPoint & point,SkPaint * paint,const WORD * glyphs,const int * advances,const GOFFSET * offsets,int numGlyphs)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
paintSkiaText(GraphicsContext * context,HFONT hfont,int numGlyphs,const WORD * glyphs,const int * advances,const GOFFSET * offsets,const SkPoint * origin)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