1/* 2 * Copyright 2014 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "SkPaint.h" 9#include "SkPoint.h" 10#include "SkTextBlobRunIterator.h" 11#include "SkTypeface.h" 12 13#include "Test.h" 14#include "sk_tool_utils.h" 15 16class TextBlobTester { 17public: 18 // This unit test feeds an SkTextBlobBuilder various runs then checks to see if 19 // the result contains the provided data and merges runs when appropriate. 20 static void TestBuilder(skiatest::Reporter* reporter) { 21 SkTextBlobBuilder builder; 22 23 // empty run set 24 RunBuilderTest(reporter, builder, nullptr, 0, nullptr, 0); 25 26 RunDef set1[] = { 27 { 128, SkTextBlob::kDefault_Positioning, 100, 100 }, 28 }; 29 RunBuilderTest(reporter, builder, set1, SK_ARRAY_COUNT(set1), set1, SK_ARRAY_COUNT(set1)); 30 31 RunDef set2[] = { 32 { 128, SkTextBlob::kHorizontal_Positioning, 100, 100 }, 33 }; 34 RunBuilderTest(reporter, builder, set2, SK_ARRAY_COUNT(set2), set2, SK_ARRAY_COUNT(set2)); 35 36 RunDef set3[] = { 37 { 128, SkTextBlob::kFull_Positioning, 100, 100 }, 38 }; 39 RunBuilderTest(reporter, builder, set3, SK_ARRAY_COUNT(set3), set3, SK_ARRAY_COUNT(set3)); 40 41 RunDef set4[] = { 42 { 128, SkTextBlob::kDefault_Positioning, 100, 150 }, 43 { 128, SkTextBlob::kDefault_Positioning, 100, 150 }, 44 { 128, SkTextBlob::kDefault_Positioning, 100, 150 }, 45 }; 46 RunBuilderTest(reporter, builder, set4, SK_ARRAY_COUNT(set4), set4, SK_ARRAY_COUNT(set4)); 47 48 RunDef set5[] = { 49 { 128, SkTextBlob::kHorizontal_Positioning, 100, 150 }, 50 { 128, SkTextBlob::kHorizontal_Positioning, 200, 150 }, 51 { 128, SkTextBlob::kHorizontal_Positioning, 300, 250 }, 52 }; 53 RunDef mergedSet5[] = { 54 { 256, SkTextBlob::kHorizontal_Positioning, 0, 150 }, 55 { 128, SkTextBlob::kHorizontal_Positioning, 0, 250 }, 56 }; 57 RunBuilderTest(reporter, builder, set5, SK_ARRAY_COUNT(set5), mergedSet5, 58 SK_ARRAY_COUNT(mergedSet5)); 59 60 RunDef set6[] = { 61 { 128, SkTextBlob::kFull_Positioning, 100, 100 }, 62 { 128, SkTextBlob::kFull_Positioning, 200, 200 }, 63 { 128, SkTextBlob::kFull_Positioning, 300, 300 }, 64 }; 65 RunDef mergedSet6[] = { 66 { 384, SkTextBlob::kFull_Positioning, 0, 0 }, 67 }; 68 RunBuilderTest(reporter, builder, set6, SK_ARRAY_COUNT(set6), mergedSet6, 69 SK_ARRAY_COUNT(mergedSet6)); 70 71 RunDef set7[] = { 72 { 128, SkTextBlob::kDefault_Positioning, 100, 150 }, 73 { 128, SkTextBlob::kDefault_Positioning, 100, 150 }, 74 { 128, SkTextBlob::kHorizontal_Positioning, 100, 150 }, 75 { 128, SkTextBlob::kHorizontal_Positioning, 200, 150 }, 76 { 128, SkTextBlob::kFull_Positioning, 400, 350 }, 77 { 128, SkTextBlob::kFull_Positioning, 400, 350 }, 78 { 128, SkTextBlob::kDefault_Positioning, 100, 450 }, 79 { 128, SkTextBlob::kDefault_Positioning, 100, 450 }, 80 { 128, SkTextBlob::kHorizontal_Positioning, 100, 550 }, 81 { 128, SkTextBlob::kHorizontal_Positioning, 200, 650 }, 82 { 128, SkTextBlob::kFull_Positioning, 400, 750 }, 83 { 128, SkTextBlob::kFull_Positioning, 400, 850 }, 84 }; 85 RunDef mergedSet7[] = { 86 { 128, SkTextBlob::kDefault_Positioning, 100, 150 }, 87 { 128, SkTextBlob::kDefault_Positioning, 100, 150 }, 88 { 256, SkTextBlob::kHorizontal_Positioning, 0, 150 }, 89 { 256, SkTextBlob::kFull_Positioning, 0, 0 }, 90 { 128, SkTextBlob::kDefault_Positioning, 100, 450 }, 91 { 128, SkTextBlob::kDefault_Positioning, 100, 450 }, 92 { 128, SkTextBlob::kHorizontal_Positioning, 0, 550 }, 93 { 128, SkTextBlob::kHorizontal_Positioning, 0, 650 }, 94 { 256, SkTextBlob::kFull_Positioning, 0, 0 }, 95 }; 96 RunBuilderTest(reporter, builder, set7, SK_ARRAY_COUNT(set7), mergedSet7, 97 SK_ARRAY_COUNT(mergedSet7)); 98 } 99 100 // This unit test verifies blob bounds computation. 101 static void TestBounds(skiatest::Reporter* reporter) { 102 SkTextBlobBuilder builder; 103 SkPaint font; 104 font.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 105 106 // Explicit bounds. 107 { 108 sk_sp<SkTextBlob> blob(builder.make()); 109 REPORTER_ASSERT(reporter, !blob); 110 } 111 112 { 113 SkRect r1 = SkRect::MakeXYWH(10, 10, 20, 20); 114 builder.allocRun(font, 16, 0, 0, &r1); 115 sk_sp<SkTextBlob> blob(builder.make()); 116 REPORTER_ASSERT(reporter, blob->bounds() == r1); 117 } 118 119 { 120 SkRect r1 = SkRect::MakeXYWH(10, 10, 20, 20); 121 builder.allocRunPosH(font, 16, 0, &r1); 122 sk_sp<SkTextBlob> blob(builder.make()); 123 REPORTER_ASSERT(reporter, blob->bounds() == r1); 124 } 125 126 { 127 SkRect r1 = SkRect::MakeXYWH(10, 10, 20, 20); 128 builder.allocRunPos(font, 16, &r1); 129 sk_sp<SkTextBlob> blob(builder.make()); 130 REPORTER_ASSERT(reporter, blob->bounds() == r1); 131 } 132 133 { 134 SkRect r1 = SkRect::MakeXYWH(10, 10, 20, 20); 135 SkRect r2 = SkRect::MakeXYWH(15, 20, 50, 50); 136 SkRect r3 = SkRect::MakeXYWH(0, 5, 10, 5); 137 138 builder.allocRun(font, 16, 0, 0, &r1); 139 builder.allocRunPosH(font, 16, 0, &r2); 140 builder.allocRunPos(font, 16, &r3); 141 142 sk_sp<SkTextBlob> blob(builder.make()); 143 REPORTER_ASSERT(reporter, blob->bounds() == SkRect::MakeXYWH(0, 5, 65, 65)); 144 } 145 146 { 147 sk_sp<SkTextBlob> blob(builder.make()); 148 REPORTER_ASSERT(reporter, !blob); 149 } 150 151 // Implicit bounds 152 153 { 154 // Exercise the empty bounds path, and ensure that RunRecord-aligned pos buffers 155 // don't trigger asserts (http://crbug.com/542643). 156 SkPaint p; 157 p.setTextSize(0); 158 p.setTextEncoding(SkPaint::kUTF8_TextEncoding); 159 160 const char* txt = "BOOO"; 161 const size_t txtLen = strlen(txt); 162 const int glyphCount = p.textToGlyphs(txt, txtLen, nullptr); 163 164 p.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 165 const SkTextBlobBuilder::RunBuffer& buffer = builder.allocRunPos(p, glyphCount); 166 167 p.setTextEncoding(SkPaint::kUTF8_TextEncoding); 168 p.textToGlyphs(txt, txtLen, buffer.glyphs); 169 170 memset(buffer.pos, 0, sizeof(SkScalar) * glyphCount * 2); 171 sk_sp<SkTextBlob> blob(builder.make()); 172 REPORTER_ASSERT(reporter, blob->bounds().isEmpty()); 173 } 174 } 175 176 // Verify that text-related properties are captured in run paints. 177 static void TestPaintProps(skiatest::Reporter* reporter) { 178 SkPaint font; 179 font.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 180 181 // Kitchen sink font. 182 font.setTextSize(42); 183 font.setTextScaleX(4.2f); 184 font.setTypeface(SkTypeface::MakeDefault()); 185 font.setTextSkewX(0.42f); 186 font.setTextAlign(SkPaint::kCenter_Align); 187 font.setHinting(SkPaint::kFull_Hinting); 188 font.setAntiAlias(true); 189 font.setFakeBoldText(true); 190 font.setLinearText(true); 191 font.setSubpixelText(true); 192 font.setDevKernText(true); 193 font.setLCDRenderText(true); 194 font.setEmbeddedBitmapText(true); 195 font.setAutohinted(true); 196 font.setVerticalText(true); 197 font.setFlags(font.getFlags() | SkPaint::kGenA8FromLCD_Flag); 198 199 // Ensure we didn't pick default values by mistake. 200 SkPaint defaultPaint; 201 REPORTER_ASSERT(reporter, defaultPaint.getTextSize() != font.getTextSize()); 202 REPORTER_ASSERT(reporter, defaultPaint.getTextScaleX() != font.getTextScaleX()); 203 REPORTER_ASSERT(reporter, defaultPaint.getTypeface() != font.getTypeface()); 204 REPORTER_ASSERT(reporter, defaultPaint.getTextSkewX() != font.getTextSkewX()); 205 REPORTER_ASSERT(reporter, defaultPaint.getTextAlign() != font.getTextAlign()); 206 REPORTER_ASSERT(reporter, defaultPaint.getHinting() != font.getHinting()); 207 REPORTER_ASSERT(reporter, defaultPaint.isAntiAlias() != font.isAntiAlias()); 208 REPORTER_ASSERT(reporter, defaultPaint.isFakeBoldText() != font.isFakeBoldText()); 209 REPORTER_ASSERT(reporter, defaultPaint.isLinearText() != font.isLinearText()); 210 REPORTER_ASSERT(reporter, defaultPaint.isSubpixelText() != font.isSubpixelText()); 211 REPORTER_ASSERT(reporter, defaultPaint.isDevKernText() != font.isDevKernText()); 212 REPORTER_ASSERT(reporter, defaultPaint.isLCDRenderText() != font.isLCDRenderText()); 213 REPORTER_ASSERT(reporter, defaultPaint.isEmbeddedBitmapText() != font.isEmbeddedBitmapText()); 214 REPORTER_ASSERT(reporter, defaultPaint.isAutohinted() != font.isAutohinted()); 215 REPORTER_ASSERT(reporter, defaultPaint.isVerticalText() != font.isVerticalText()); 216 REPORTER_ASSERT(reporter, (defaultPaint.getFlags() & SkPaint::kGenA8FromLCD_Flag) != 217 (font.getFlags() & SkPaint::kGenA8FromLCD_Flag)); 218 219 SkTextBlobBuilder builder; 220 AddRun(font, 1, SkTextBlob::kDefault_Positioning, SkPoint::Make(0, 0), builder); 221 AddRun(font, 1, SkTextBlob::kHorizontal_Positioning, SkPoint::Make(0, 0), builder); 222 AddRun(font, 1, SkTextBlob::kFull_Positioning, SkPoint::Make(0, 0), builder); 223 sk_sp<SkTextBlob> blob(builder.make()); 224 225 SkTextBlobRunIterator it(blob.get()); 226 while (!it.done()) { 227 SkPaint paint; 228 it.applyFontToPaint(&paint); 229 230 REPORTER_ASSERT(reporter, paint.getTextSize() == font.getTextSize()); 231 REPORTER_ASSERT(reporter, paint.getTextScaleX() == font.getTextScaleX()); 232 REPORTER_ASSERT(reporter, paint.getTypeface() == font.getTypeface()); 233 REPORTER_ASSERT(reporter, paint.getTextSkewX() == font.getTextSkewX()); 234 REPORTER_ASSERT(reporter, paint.getTextAlign() == font.getTextAlign()); 235 REPORTER_ASSERT(reporter, paint.getHinting() == font.getHinting()); 236 REPORTER_ASSERT(reporter, paint.isAntiAlias() == font.isAntiAlias()); 237 REPORTER_ASSERT(reporter, paint.isFakeBoldText() == font.isFakeBoldText()); 238 REPORTER_ASSERT(reporter, paint.isLinearText() == font.isLinearText()); 239 REPORTER_ASSERT(reporter, paint.isSubpixelText() == font.isSubpixelText()); 240 REPORTER_ASSERT(reporter, paint.isDevKernText() == font.isDevKernText()); 241 REPORTER_ASSERT(reporter, paint.isLCDRenderText() == font.isLCDRenderText()); 242 REPORTER_ASSERT(reporter, paint.isEmbeddedBitmapText() == font.isEmbeddedBitmapText()); 243 REPORTER_ASSERT(reporter, paint.isAutohinted() == font.isAutohinted()); 244 REPORTER_ASSERT(reporter, paint.isVerticalText() == font.isVerticalText()); 245 REPORTER_ASSERT(reporter, (paint.getFlags() & SkPaint::kGenA8FromLCD_Flag) == 246 (font.getFlags() & SkPaint::kGenA8FromLCD_Flag)); 247 248 it.next(); 249 } 250 251 } 252 253private: 254 struct RunDef { 255 unsigned count; 256 SkTextBlob::GlyphPositioning pos; 257 SkScalar x, y; 258 }; 259 260 static void RunBuilderTest(skiatest::Reporter* reporter, SkTextBlobBuilder& builder, 261 const RunDef in[], unsigned inCount, 262 const RunDef out[], unsigned outCount) { 263 SkPaint font; 264 font.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 265 266 unsigned glyphCount = 0; 267 unsigned posCount = 0; 268 269 for (unsigned i = 0; i < inCount; ++i) { 270 AddRun(font, in[i].count, in[i].pos, SkPoint::Make(in[i].x, in[i].y), builder); 271 glyphCount += in[i].count; 272 posCount += in[i].count * in[i].pos; 273 } 274 275 sk_sp<SkTextBlob> blob(builder.make()); 276 REPORTER_ASSERT(reporter, (inCount > 0) == SkToBool(blob)); 277 if (!blob) { 278 return; 279 } 280 281 SkTextBlobRunIterator it(blob.get()); 282 for (unsigned i = 0; i < outCount; ++i) { 283 REPORTER_ASSERT(reporter, !it.done()); 284 REPORTER_ASSERT(reporter, out[i].pos == it.positioning()); 285 REPORTER_ASSERT(reporter, out[i].count == it.glyphCount()); 286 if (SkTextBlob::kDefault_Positioning == out[i].pos) { 287 REPORTER_ASSERT(reporter, out[i].x == it.offset().x()); 288 REPORTER_ASSERT(reporter, out[i].y == it.offset().y()); 289 } else if (SkTextBlob::kHorizontal_Positioning == out[i].pos) { 290 REPORTER_ASSERT(reporter, out[i].y == it.offset().y()); 291 } 292 293 for (unsigned k = 0; k < it.glyphCount(); ++k) { 294 REPORTER_ASSERT(reporter, k % 128 == it.glyphs()[k]); 295 if (SkTextBlob::kHorizontal_Positioning == it.positioning()) { 296 REPORTER_ASSERT(reporter, SkIntToScalar(k % 128) == it.pos()[k]); 297 } else if (SkTextBlob::kFull_Positioning == it.positioning()) { 298 REPORTER_ASSERT(reporter, SkIntToScalar(k % 128) == it.pos()[k * 2]); 299 REPORTER_ASSERT(reporter, -SkIntToScalar(k % 128) == it.pos()[k * 2 + 1]); 300 } 301 } 302 303 it.next(); 304 } 305 306 REPORTER_ASSERT(reporter, it.done()); 307 } 308 309 static void AddRun(const SkPaint& font, int count, SkTextBlob::GlyphPositioning pos, 310 const SkPoint& offset, SkTextBlobBuilder& builder, 311 const SkRect* bounds = nullptr) { 312 switch (pos) { 313 case SkTextBlob::kDefault_Positioning: { 314 const SkTextBlobBuilder::RunBuffer& rb = builder.allocRun(font, count, offset.x(), 315 offset.y(), bounds); 316 for (int i = 0; i < count; ++i) { 317 rb.glyphs[i] = i; 318 } 319 } break; 320 case SkTextBlob::kHorizontal_Positioning: { 321 const SkTextBlobBuilder::RunBuffer& rb = builder.allocRunPosH(font, count, offset.y(), 322 bounds); 323 for (int i = 0; i < count; ++i) { 324 rb.glyphs[i] = i; 325 rb.pos[i] = SkIntToScalar(i); 326 } 327 } break; 328 case SkTextBlob::kFull_Positioning: { 329 const SkTextBlobBuilder::RunBuffer& rb = builder.allocRunPos(font, count, bounds); 330 for (int i = 0; i < count; ++i) { 331 rb.glyphs[i] = i; 332 rb.pos[i * 2] = SkIntToScalar(i); 333 rb.pos[i * 2 + 1] = -SkIntToScalar(i); 334 } 335 } break; 336 default: 337 SK_ABORT("unhandled positioning value"); 338 } 339 } 340}; 341 342DEF_TEST(TextBlob_builder, reporter) { 343 TextBlobTester::TestBuilder(reporter); 344 TextBlobTester::TestBounds(reporter); 345} 346 347DEF_TEST(TextBlob_paint, reporter) { 348 TextBlobTester::TestPaintProps(reporter); 349} 350 351DEF_TEST(TextBlob_extended, reporter) { 352 SkTextBlobBuilder textBlobBuilder; 353 SkPaint paint; 354 const char text1[] = "Foo"; 355 const char text2[] = "Bar"; 356 357 int glyphCount = paint.textToGlyphs(text1, strlen(text1), nullptr); 358 SkAutoTMalloc<uint16_t> glyphs(glyphCount); 359 (void)paint.textToGlyphs(text1, strlen(text1), glyphs.get()); 360 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 361 362 auto run = textBlobBuilder.allocRunText( 363 paint, glyphCount, 0, 0, SkToInt(strlen(text2)), SkString(), nullptr); 364 memcpy(run.glyphs, glyphs.get(), sizeof(uint16_t) * glyphCount); 365 memcpy(run.utf8text, text2, strlen(text2)); 366 for (int i = 0; i < glyphCount; ++i) { 367 run.clusters[i] = SkTMin(SkToU32(i), SkToU32(strlen(text2))); 368 } 369 sk_sp<SkTextBlob> blob(textBlobBuilder.make()); 370 REPORTER_ASSERT(reporter, blob); 371 372 for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) { 373 REPORTER_ASSERT(reporter, it.glyphCount() == (uint32_t)glyphCount); 374 for (uint32_t i = 0; i < it.glyphCount(); ++i) { 375 REPORTER_ASSERT(reporter, it.glyphs()[i] == glyphs[i]); 376 } 377 REPORTER_ASSERT(reporter, SkTextBlob::kDefault_Positioning == it.positioning()); 378 REPORTER_ASSERT(reporter, (SkPoint{0.0f, 0.0f}) == it.offset()); 379 REPORTER_ASSERT(reporter, it.textSize() > 0); 380 REPORTER_ASSERT(reporter, it.clusters()); 381 for (uint32_t i = 0; i < it.glyphCount(); ++i) { 382 REPORTER_ASSERT(reporter, i == it.clusters()[i]); 383 } 384 REPORTER_ASSERT(reporter, 0 == strncmp(text2, it.text(), it.textSize())); 385 } 386} 387 388/////////////////////////////////////////////////////////////////////////////////////////////////// 389#include "SkCanvas.h" 390#include "SkSurface.h" 391#include "SkTDArray.h" 392 393static void add_run(SkTextBlobBuilder* builder, const char text[], SkScalar x, SkScalar y, 394 sk_sp<SkTypeface> tf) { 395 SkPaint paint; 396 paint.setAntiAlias(true); 397 paint.setSubpixelText(true); 398 paint.setTextSize(16); 399 paint.setTypeface(tf); 400 401 int glyphCount = paint.textToGlyphs(text, strlen(text), nullptr); 402 403 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 404 SkTextBlobBuilder::RunBuffer buffer = builder->allocRun(paint, glyphCount, x, y); 405 406 paint.setTextEncoding(SkPaint::kUTF8_TextEncoding); 407 (void)paint.textToGlyphs(text, strlen(text), buffer.glyphs); 408} 409 410static sk_sp<SkImage> render(const SkTextBlob* blob) { 411 auto surf = SkSurface::MakeRasterN32Premul(SkScalarRoundToInt(blob->bounds().width()), 412 SkScalarRoundToInt(blob->bounds().height())); 413 if (!surf) { 414 return nullptr; // bounds are empty? 415 } 416 surf->getCanvas()->clear(SK_ColorWHITE); 417 surf->getCanvas()->drawTextBlob(blob, -blob->bounds().left(), -blob->bounds().top(), SkPaint()); 418 return surf->makeImageSnapshot(); 419} 420 421/* 422 * Build a blob with more than one typeface. 423 * Draw it into an offscreen, 424 * then serialize and deserialize, 425 * Then draw the new instance and assert it draws the same as the original. 426 */ 427DEF_TEST(TextBlob_serialize, reporter) { 428 sk_sp<SkTextBlob> blob0 = []() { 429 sk_sp<SkTypeface> tf = SkTypeface::MakeDefault(); 430 431 SkTextBlobBuilder builder; 432 add_run(&builder, "Hello", 10, 20, nullptr); // we don't flatten this in the paint 433 add_run(&builder, "World", 10, 40, tf); // we will flatten this in the paint 434 return builder.make(); 435 }(); 436 437 SkTDArray<SkTypeface*> array; 438 sk_sp<SkData> data = blob0->serialize([](SkTypeface* tf, void* ctx) { 439 auto array = (SkTDArray<SkTypeface*>*)ctx; 440 if (array->find(tf) < 0) { 441 *array->append() = tf; 442 } 443 }, &array); 444 // we only expect 1, since null would not have been serialized, but the default would 445 REPORTER_ASSERT(reporter, array.count() == 1); 446 447 sk_sp<SkTextBlob> blob1 = SkTextBlob::Deserialize(data->data(), data->size(), 448 [](uint32_t uniqueID, void* ctx) { 449 auto array = (SkTDArray<SkTypeface*>*)ctx; 450 for (int i = 0; i < array->count(); ++i) { 451 if ((*array)[i]->uniqueID() == uniqueID) { 452 return sk_ref_sp((*array)[i]); 453 } 454 } 455 SkASSERT(false); 456 return sk_sp<SkTypeface>(nullptr); 457 }, &array); 458 459 sk_sp<SkImage> img0 = render(blob0.get()); 460 sk_sp<SkImage> img1 = render(blob1.get()); 461 if (img0 && img1) { 462 REPORTER_ASSERT(reporter, sk_tool_utils::equal_pixels(img0.get(), img1.get())); 463 } 464} 465