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