OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2015 Google Inc. | 2 * Copyright 2015 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "GrTextUtils.h" | 8 #include "GrTextUtils.h" |
9 | 9 |
10 #include "GrAtlasTextBlob.h" | 10 #include "GrAtlasTextBlob.h" |
11 #include "GrBatchFontCache.h" | 11 #include "GrBatchFontCache.h" |
12 #include "GrBlurUtils.h" | 12 #include "GrBlurUtils.h" |
| 13 #include "GrCaps.h" |
13 #include "GrContext.h" | 14 #include "GrContext.h" |
14 #include "GrDrawContext.h" | 15 #include "GrDrawContext.h" |
15 #include "GrTextContext.h" | 16 #include "GrTextContext.h" |
| 17 |
| 18 #include "SkDistanceFieldGen.h" |
16 #include "SkDrawProcs.h" | 19 #include "SkDrawProcs.h" |
17 #include "SkFindAndPlaceGlyph.h" | 20 #include "SkFindAndPlaceGlyph.h" |
18 #include "SkGlyphCache.h" | 21 #include "SkGlyphCache.h" |
19 #include "SkPaint.h" | 22 #include "SkPaint.h" |
20 #include "SkRect.h" | 23 #include "SkRect.h" |
21 #include "SkTextMapStateProc.h" | 24 #include "SkTextMapStateProc.h" |
22 #include "SkTextToPathIter.h" | 25 #include "SkTextToPathIter.h" |
23 | 26 |
| 27 namespace { |
| 28 static const int kMinDFFontSize = 18; |
| 29 static const int kSmallDFFontSize = 32; |
| 30 static const int kSmallDFFontLimit = 32; |
| 31 static const int kMediumDFFontSize = 72; |
| 32 static const int kMediumDFFontLimit = 72; |
| 33 static const int kLargeDFFontSize = 162; |
| 34 #ifdef SK_BUILD_FOR_ANDROID |
| 35 static const int kLargeDFFontLimit = 384; |
| 36 #else |
| 37 static const int kLargeDFFontLimit = 2 * kLargeDFFontSize; |
| 38 #endif |
| 39 }; |
| 40 |
24 void GrTextUtils::DrawBmpText(GrAtlasTextBlob* blob, int runIndex, | 41 void GrTextUtils::DrawBmpText(GrAtlasTextBlob* blob, int runIndex, |
25 GrBatchFontCache* fontCache, | 42 GrBatchFontCache* fontCache, |
26 const SkSurfaceProps& props, const SkPaint& skPain
t, | 43 const SkSurfaceProps& props, const SkPaint& skPain
t, |
27 GrColor color, | 44 GrColor color, |
28 const SkMatrix& viewMatrix, | 45 const SkMatrix& viewMatrix, |
29 const char text[], size_t byteLength, | 46 const char text[], size_t byteLength, |
30 SkScalar x, SkScalar y) { | 47 SkScalar x, SkScalar y) { |
31 SkASSERT(byteLength == 0 || text != nullptr); | 48 SkASSERT(byteLength == 0 || text != nullptr); |
32 | 49 |
33 // nothing to draw | 50 // nothing to draw |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
128 SkRect r; | 145 SkRect r; |
129 r.fLeft = SkIntToScalar(x); | 146 r.fLeft = SkIntToScalar(x); |
130 r.fTop = SkIntToScalar(y); | 147 r.fTop = SkIntToScalar(y); |
131 r.fRight = r.fLeft + SkIntToScalar(width); | 148 r.fRight = r.fLeft + SkIntToScalar(width); |
132 r.fBottom = r.fTop + SkIntToScalar(height); | 149 r.fBottom = r.fTop + SkIntToScalar(height); |
133 | 150 |
134 blob->appendGlyph(runIndex, r, color, *strike, glyph, scaler, skGlyph, | 151 blob->appendGlyph(runIndex, r, color, *strike, glyph, scaler, skGlyph, |
135 SkIntToScalar(vx), SkIntToScalar(vy), 1.0f, false); | 152 SkIntToScalar(vx), SkIntToScalar(vy), 1.0f, false); |
136 } | 153 } |
137 | 154 |
| 155 bool GrTextUtils::CanDrawAsDistanceFields(const SkPaint& skPaint, const SkMatrix
& viewMatrix, |
| 156 const SkSurfaceProps& props, const GrS
haderCaps& caps) { |
| 157 // TODO: support perspective (need getMaxScale replacement) |
| 158 if (viewMatrix.hasPerspective()) { |
| 159 return false; |
| 160 } |
| 161 |
| 162 SkScalar maxScale = viewMatrix.getMaxScale(); |
| 163 SkScalar scaledTextSize = maxScale*skPaint.getTextSize(); |
| 164 // Hinted text looks far better at small resolutions |
| 165 // Scaling up beyond 2x yields undesireable artifacts |
| 166 if (scaledTextSize < kMinDFFontSize || |
| 167 scaledTextSize > kLargeDFFontLimit) { |
| 168 return false; |
| 169 } |
| 170 |
| 171 bool useDFT = props.isUseDeviceIndependentFonts(); |
| 172 #if SK_FORCE_DISTANCE_FIELD_TEXT |
| 173 useDFT = true; |
| 174 #endif |
| 175 |
| 176 if (!useDFT && scaledTextSize < kLargeDFFontSize) { |
| 177 return false; |
| 178 } |
| 179 |
| 180 // rasterizers and mask filters modify alpha, which doesn't |
| 181 // translate well to distance |
| 182 if (skPaint.getRasterizer() || skPaint.getMaskFilter() || !caps.shaderDeriva
tiveSupport()) { |
| 183 return false; |
| 184 } |
| 185 |
| 186 // TODO: add some stroking support |
| 187 if (skPaint.getStyle() != SkPaint::kFill_Style) { |
| 188 return false; |
| 189 } |
| 190 |
| 191 return true; |
| 192 } |
| 193 |
| 194 void GrTextUtils::InitDistanceFieldPaint(GrAtlasTextBlob* blob, |
| 195 SkPaint* skPaint, |
| 196 SkScalar* textRatio, |
| 197 const SkMatrix& viewMatrix) { |
| 198 // getMaxScale doesn't support perspective, so neither do we at the moment |
| 199 SkASSERT(!viewMatrix.hasPerspective()); |
| 200 SkScalar maxScale = viewMatrix.getMaxScale(); |
| 201 SkScalar textSize = skPaint->getTextSize(); |
| 202 SkScalar scaledTextSize = textSize; |
| 203 // if we have non-unity scale, we need to choose our base text size |
| 204 // based on the SkPaint's text size multiplied by the max scale factor |
| 205 // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)? |
| 206 if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) { |
| 207 scaledTextSize *= maxScale; |
| 208 } |
| 209 |
| 210 // We have three sizes of distance field text, and within each size 'bucket'
there is a floor |
| 211 // and ceiling. A scale outside of this range would require regenerating th
e distance fields |
| 212 SkScalar dfMaskScaleFloor; |
| 213 SkScalar dfMaskScaleCeil; |
| 214 if (scaledTextSize <= kSmallDFFontLimit) { |
| 215 dfMaskScaleFloor = kMinDFFontSize; |
| 216 dfMaskScaleCeil = kSmallDFFontLimit; |
| 217 *textRatio = textSize / kSmallDFFontSize; |
| 218 skPaint->setTextSize(SkIntToScalar(kSmallDFFontSize)); |
| 219 } else if (scaledTextSize <= kMediumDFFontLimit) { |
| 220 dfMaskScaleFloor = kSmallDFFontLimit; |
| 221 dfMaskScaleCeil = kMediumDFFontLimit; |
| 222 *textRatio = textSize / kMediumDFFontSize; |
| 223 skPaint->setTextSize(SkIntToScalar(kMediumDFFontSize)); |
| 224 } else { |
| 225 dfMaskScaleFloor = kMediumDFFontLimit; |
| 226 dfMaskScaleCeil = kLargeDFFontLimit; |
| 227 *textRatio = textSize / kLargeDFFontSize; |
| 228 skPaint->setTextSize(SkIntToScalar(kLargeDFFontSize)); |
| 229 } |
| 230 |
| 231 // Because there can be multiple runs in the blob, we want the overall maxMi
nScale, and |
| 232 // minMaxScale to make regeneration decisions. Specifically, we want the ma
ximum minimum scale |
| 233 // we can tolerate before we'd drop to a lower mip size, and the minimum max
imum scale we can |
| 234 // tolerate before we'd have to move to a large mip size. When we actually
test these values |
| 235 // we look at the delta in scale between the new viewmatrix and the old view
matrix, and test |
| 236 // against these values to decide if we can reuse or not(ie, will a given sc
ale change our mip |
| 237 // level) |
| 238 SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScale
Ceil); |
| 239 blob->fMaxMinScale = SkMaxScalar(dfMaskScaleFloor / scaledTextSize, blob->fM
axMinScale); |
| 240 blob->fMinMaxScale = SkMinScalar(dfMaskScaleCeil / scaledTextSize, blob->fMi
nMaxScale); |
| 241 |
| 242 skPaint->setLCDRenderText(false); |
| 243 skPaint->setAutohinted(false); |
| 244 skPaint->setHinting(SkPaint::kNormal_Hinting); |
| 245 skPaint->setSubpixelText(true); |
| 246 } |
| 247 |
| 248 void GrTextUtils::DrawDFText(GrAtlasTextBlob* blob, int runIndex, |
| 249 GrBatchFontCache* fontCache, const SkSurfaceProps&
props, |
| 250 const SkPaint& skPaint, GrColor color, |
| 251 const SkMatrix& viewMatrix, |
| 252 const char text[], size_t byteLength, |
| 253 SkScalar x, SkScalar y) { |
| 254 SkASSERT(byteLength == 0 || text != nullptr); |
| 255 |
| 256 // nothing to draw |
| 257 if (text == nullptr || byteLength == 0) { |
| 258 return; |
| 259 } |
| 260 |
| 261 SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc(); |
| 262 SkAutoDescriptor desc; |
| 263 skPaint.getScalerContextDescriptor(&desc, props, nullptr, true); |
| 264 SkGlyphCache* origPaintCache = SkGlyphCache::DetachCache(skPaint.getTypeface
(), |
| 265 desc.getDesc()); |
| 266 |
| 267 SkTArray<SkScalar> positions; |
| 268 |
| 269 const char* textPtr = text; |
| 270 SkFixed stopX = 0; |
| 271 SkFixed stopY = 0; |
| 272 SkFixed origin = 0; |
| 273 switch (skPaint.getTextAlign()) { |
| 274 case SkPaint::kRight_Align: origin = SK_Fixed1; break; |
| 275 case SkPaint::kCenter_Align: origin = SK_FixedHalf; break; |
| 276 case SkPaint::kLeft_Align: origin = 0; break; |
| 277 } |
| 278 |
| 279 SkAutoKern autokern; |
| 280 const char* stop = text + byteLength; |
| 281 while (textPtr < stop) { |
| 282 // don't need x, y here, since all subpixel variants will have the |
| 283 // same advance |
| 284 const SkGlyph& glyph = glyphCacheProc(origPaintCache, &textPtr, 0, 0); |
| 285 |
| 286 SkFixed width = glyph.fAdvanceX + autokern.adjust(glyph); |
| 287 positions.push_back(SkFixedToScalar(stopX + SkFixedMul(origin, width))); |
| 288 |
| 289 SkFixed height = glyph.fAdvanceY; |
| 290 positions.push_back(SkFixedToScalar(stopY + SkFixedMul(origin, height)))
; |
| 291 |
| 292 stopX += width; |
| 293 stopY += height; |
| 294 } |
| 295 SkASSERT(textPtr == stop); |
| 296 |
| 297 SkGlyphCache::AttachCache(origPaintCache); |
| 298 |
| 299 // now adjust starting point depending on alignment |
| 300 SkScalar alignX = SkFixedToScalar(stopX); |
| 301 SkScalar alignY = SkFixedToScalar(stopY); |
| 302 if (skPaint.getTextAlign() == SkPaint::kCenter_Align) { |
| 303 alignX = SkScalarHalf(alignX); |
| 304 alignY = SkScalarHalf(alignY); |
| 305 } else if (skPaint.getTextAlign() == SkPaint::kLeft_Align) { |
| 306 alignX = 0; |
| 307 alignY = 0; |
| 308 } |
| 309 x -= alignX; |
| 310 y -= alignY; |
| 311 SkPoint offset = SkPoint::Make(x, y); |
| 312 |
| 313 DrawDFPosText(blob, runIndex, fontCache, props, skPaint, color, viewMatrix,
text, byteLength, |
| 314 positions.begin(), 2, offset); |
| 315 } |
| 316 |
| 317 void GrTextUtils::DrawDFPosText(GrAtlasTextBlob* blob, int runIndex, |
| 318 GrBatchFontCache* fontCache, const SkSurfaceProp
s& props, |
| 319 const SkPaint& origPaint, |
| 320 GrColor color, const SkMatrix& viewMatrix, |
| 321 const char text[], size_t byteLength, |
| 322 const SkScalar pos[], int scalarsPerPosition, |
| 323 const SkPoint& offset) { |
| 324 SkASSERT(byteLength == 0 || text != nullptr); |
| 325 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition); |
| 326 |
| 327 // nothing to draw |
| 328 if (text == nullptr || byteLength == 0) { |
| 329 return; |
| 330 } |
| 331 |
| 332 SkTDArray<char> fallbackTxt; |
| 333 SkTDArray<SkScalar> fallbackPos; |
| 334 |
| 335 // Setup distance field paint and text ratio |
| 336 SkScalar textRatio; |
| 337 SkPaint dfPaint(origPaint); |
| 338 GrTextUtils::InitDistanceFieldPaint(blob, &dfPaint, &textRatio, viewMatrix); |
| 339 blob->setHasDistanceField(); |
| 340 blob->setSubRunHasDistanceFields(runIndex, origPaint.isLCDRenderText()); |
| 341 |
| 342 GrBatchTextStrike* currStrike = nullptr; |
| 343 |
| 344 SkGlyphCache* cache = blob->setupCache(runIndex, props, dfPaint, nullptr, tr
ue); |
| 345 SkDrawCacheProc glyphCacheProc = dfPaint.getDrawCacheProc(); |
| 346 GrFontScaler* fontScaler = GrTextContext::GetGrFontScaler(cache); |
| 347 |
| 348 const char* stop = text + byteLength; |
| 349 |
| 350 if (SkPaint::kLeft_Align == dfPaint.getTextAlign()) { |
| 351 while (text < stop) { |
| 352 const char* lastText = text; |
| 353 // the last 2 parameters are ignored |
| 354 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
| 355 |
| 356 if (glyph.fWidth) { |
| 357 SkScalar x = offset.x() + pos[0]; |
| 358 SkScalar y = offset.y() + (2 == scalarsPerPosition ? pos[1] : 0)
; |
| 359 |
| 360 if (!DfAppendGlyph(blob, |
| 361 runIndex, |
| 362 fontCache, |
| 363 &currStrike, |
| 364 glyph, |
| 365 x, y, color, fontScaler, |
| 366 textRatio, viewMatrix)) { |
| 367 // couldn't append, send to fallback |
| 368 fallbackTxt.append(SkToInt(text-lastText), lastText); |
| 369 *fallbackPos.append() = pos[0]; |
| 370 if (2 == scalarsPerPosition) { |
| 371 *fallbackPos.append() = pos[1]; |
| 372 } |
| 373 } |
| 374 } |
| 375 pos += scalarsPerPosition; |
| 376 } |
| 377 } else { |
| 378 SkScalar alignMul = SkPaint::kCenter_Align == dfPaint.getTextAlign() ? S
K_ScalarHalf |
| 379 : S
K_Scalar1; |
| 380 while (text < stop) { |
| 381 const char* lastText = text; |
| 382 // the last 2 parameters are ignored |
| 383 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
| 384 |
| 385 if (glyph.fWidth) { |
| 386 SkScalar x = offset.x() + pos[0]; |
| 387 SkScalar y = offset.y() + (2 == scalarsPerPosition ? pos[1] : 0)
; |
| 388 |
| 389 SkScalar advanceX = SkFixedToScalar(glyph.fAdvanceX) * alignMul
* textRatio; |
| 390 SkScalar advanceY = SkFixedToScalar(glyph.fAdvanceY) * alignMul
* textRatio; |
| 391 |
| 392 if (!DfAppendGlyph(blob, |
| 393 runIndex, |
| 394 fontCache, |
| 395 &currStrike, |
| 396 glyph, |
| 397 x - advanceX, y - advanceY, color, |
| 398 fontScaler, |
| 399 textRatio, |
| 400 viewMatrix)) { |
| 401 // couldn't append, send to fallback |
| 402 fallbackTxt.append(SkToInt(text-lastText), lastText); |
| 403 *fallbackPos.append() = pos[0]; |
| 404 if (2 == scalarsPerPosition) { |
| 405 *fallbackPos.append() = pos[1]; |
| 406 } |
| 407 } |
| 408 } |
| 409 pos += scalarsPerPosition; |
| 410 } |
| 411 } |
| 412 |
| 413 SkGlyphCache::AttachCache(cache); |
| 414 if (fallbackTxt.count()) { |
| 415 blob->initOverride(runIndex); |
| 416 GrTextUtils::DrawBmpPosText(blob, runIndex, fontCache, props, |
| 417 origPaint, origPaint.getColor(), viewMatrix, |
| 418 fallbackTxt.begin(), fallbackTxt.count(), |
| 419 fallbackPos.begin(), scalarsPerPosition, off
set); |
| 420 } |
| 421 } |
| 422 |
| 423 bool GrTextUtils::DfAppendGlyph(GrAtlasTextBlob* blob, int runIndex, GrBatchFont
Cache* cache, |
| 424 GrBatchTextStrike** strike, const SkGlyph& skGly
ph, |
| 425 SkScalar sx, SkScalar sy, GrColor color, |
| 426 GrFontScaler* scaler, |
| 427 SkScalar textRatio, const SkMatrix& viewMatrix)
{ |
| 428 if (!*strike) { |
| 429 *strike = cache->getStrike(scaler); |
| 430 } |
| 431 |
| 432 GrGlyph::PackedID id = GrGlyph::Pack(skGlyph.getGlyphID(), |
| 433 skGlyph.getSubXFixed(), |
| 434 skGlyph.getSubYFixed(), |
| 435 GrGlyph::kDistance_MaskStyle); |
| 436 GrGlyph* glyph = (*strike)->getGlyph(skGlyph, id, scaler); |
| 437 if (!glyph) { |
| 438 return true; |
| 439 } |
| 440 |
| 441 // fallback to color glyph support |
| 442 if (kA8_GrMaskFormat != glyph->fMaskFormat) { |
| 443 return false; |
| 444 } |
| 445 |
| 446 SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft + SK_DistanceFieldInset); |
| 447 SkScalar dy = SkIntToScalar(glyph->fBounds.fTop + SK_DistanceFieldInset); |
| 448 SkScalar width = SkIntToScalar(glyph->fBounds.width() - 2 * SK_DistanceField
Inset); |
| 449 SkScalar height = SkIntToScalar(glyph->fBounds.height() - 2 * SK_DistanceFie
ldInset); |
| 450 |
| 451 SkScalar scale = textRatio; |
| 452 dx *= scale; |
| 453 dy *= scale; |
| 454 width *= scale; |
| 455 height *= scale; |
| 456 sx += dx; |
| 457 sy += dy; |
| 458 SkRect glyphRect = SkRect::MakeXYWH(sx, sy, width, height); |
| 459 |
| 460 blob->appendGlyph(runIndex, glyphRect, color, *strike, glyph, scaler, skGlyp
h, |
| 461 sx - dx, sy - dy, scale, true); |
| 462 return true; |
| 463 } |
| 464 |
138 void GrTextUtils::DrawTextAsPath(GrContext* context, GrDrawContext* dc, | 465 void GrTextUtils::DrawTextAsPath(GrContext* context, GrDrawContext* dc, |
139 const GrClip& clip, | 466 const GrClip& clip, |
140 const SkPaint& skPaint, const SkMatrix& viewMat
rix, | 467 const SkPaint& skPaint, const SkMatrix& viewMat
rix, |
141 const char text[], size_t byteLength, SkScalar
x, SkScalar y, | 468 const char text[], size_t byteLength, SkScalar
x, SkScalar y, |
142 const SkIRect& clipBounds) { | 469 const SkIRect& clipBounds) { |
143 SkTextToPathIter iter(text, byteLength, skPaint, true); | 470 SkTextToPathIter iter(text, byteLength, skPaint, true); |
144 | 471 |
145 SkMatrix matrix; | 472 SkMatrix matrix; |
146 matrix.setScale(iter.getPathScale(), iter.getPathScale()); | 473 matrix.setScale(iter.getPathScale(), iter.getPathScale()); |
147 matrix.postTranslate(x, y); | 474 matrix.postTranslate(x, y); |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
203 | 530 |
204 matrix[SkMatrix::kMTransX] = loc.fX; | 531 matrix[SkMatrix::kMTransX] = loc.fX; |
205 matrix[SkMatrix::kMTransY] = loc.fY; | 532 matrix[SkMatrix::kMTransY] = loc.fY; |
206 GrBlurUtils::drawPathWithMaskFilter(context, dc, clip, *path, pa
int, | 533 GrBlurUtils::drawPathWithMaskFilter(context, dc, clip, *path, pa
int, |
207 viewMatrix, &matrix, clipBou
nds, false); | 534 viewMatrix, &matrix, clipBou
nds, false); |
208 } | 535 } |
209 } | 536 } |
210 pos += scalarsPerPosition; | 537 pos += scalarsPerPosition; |
211 } | 538 } |
212 } | 539 } |
OLD | NEW |