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