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