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