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 SkAutoTUnref<const SkTextBlob> blob(builder.build()); 108 REPORTER_ASSERT(reporter, blob->bounds().isEmpty()); 109 } 110 111 { 112 SkRect r1 = SkRect::MakeXYWH(10, 10, 20, 20); 113 builder.allocRun(font, 16, 0, 0, &r1); 114 SkAutoTUnref<const SkTextBlob> blob(builder.build()); 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 SkAutoTUnref<const SkTextBlob> blob(builder.build()); 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 SkAutoTUnref<const SkTextBlob> blob(builder.build()); 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 SkAutoTUnref<const SkTextBlob> blob(builder.build()); 142 REPORTER_ASSERT(reporter, blob->bounds() == SkRect::MakeXYWH(0, 5, 65, 65)); 143 } 144 145 { 146 // Verify empty blob bounds after building some non-empty blobs. 147 SkAutoTUnref<const SkTextBlob> blob(builder.build()); 148 REPORTER_ASSERT(reporter, blob->bounds().isEmpty()); 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 SkAutoTUnref<const SkTextBlob> blob(builder.build()); 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 SkAutoTUnref<SkTypeface> typeface(SkTypeface::RefDefault()); 182 183 // Kitchen sink font. 184 font.setTextSize(42); 185 font.setTextScaleX(4.2f); 186 font.setTypeface(typeface); 187 font.setTextSkewX(0.42f); 188 font.setTextAlign(SkPaint::kCenter_Align); 189 font.setHinting(SkPaint::kFull_Hinting); 190 font.setAntiAlias(true); 191 font.setUnderlineText(true); 192 font.setStrikeThruText(true); 193 font.setFakeBoldText(true); 194 font.setLinearText(true); 195 font.setSubpixelText(true); 196 font.setDevKernText(true); 197 font.setLCDRenderText(true); 198 font.setEmbeddedBitmapText(true); 199 font.setAutohinted(true); 200 font.setVerticalText(true); 201 font.setFlags(font.getFlags() | SkPaint::kGenA8FromLCD_Flag); 202 203 // Ensure we didn't pick default values by mistake. 204 SkPaint defaultPaint; 205 REPORTER_ASSERT(reporter, defaultPaint.getTextSize() != font.getTextSize()); 206 REPORTER_ASSERT(reporter, defaultPaint.getTextScaleX() != font.getTextScaleX()); 207 REPORTER_ASSERT(reporter, defaultPaint.getTypeface() != font.getTypeface()); 208 REPORTER_ASSERT(reporter, defaultPaint.getTextSkewX() != font.getTextSkewX()); 209 REPORTER_ASSERT(reporter, defaultPaint.getTextAlign() != font.getTextAlign()); 210 REPORTER_ASSERT(reporter, defaultPaint.getHinting() != font.getHinting()); 211 REPORTER_ASSERT(reporter, defaultPaint.isAntiAlias() != font.isAntiAlias()); 212 REPORTER_ASSERT(reporter, defaultPaint.isUnderlineText() != font.isUnderlineText()); 213 REPORTER_ASSERT(reporter, defaultPaint.isStrikeThruText() != font.isStrikeThruText()); 214 REPORTER_ASSERT(reporter, defaultPaint.isFakeBoldText() != font.isFakeBoldText()); 215 REPORTER_ASSERT(reporter, defaultPaint.isLinearText() != font.isLinearText()); 216 REPORTER_ASSERT(reporter, defaultPaint.isSubpixelText() != font.isSubpixelText()); 217 REPORTER_ASSERT(reporter, defaultPaint.isDevKernText() != font.isDevKernText()); 218 REPORTER_ASSERT(reporter, defaultPaint.isLCDRenderText() != font.isLCDRenderText()); 219 REPORTER_ASSERT(reporter, defaultPaint.isEmbeddedBitmapText() != font.isEmbeddedBitmapText()); 220 REPORTER_ASSERT(reporter, defaultPaint.isAutohinted() != font.isAutohinted()); 221 REPORTER_ASSERT(reporter, defaultPaint.isVerticalText() != font.isVerticalText()); 222 REPORTER_ASSERT(reporter, (defaultPaint.getFlags() & SkPaint::kGenA8FromLCD_Flag) != 223 (font.getFlags() & SkPaint::kGenA8FromLCD_Flag)); 224 225 SkTextBlobBuilder builder; 226 AddRun(font, 1, SkTextBlob::kDefault_Positioning, SkPoint::Make(0, 0), builder); 227 AddRun(font, 1, SkTextBlob::kHorizontal_Positioning, SkPoint::Make(0, 0), builder); 228 AddRun(font, 1, SkTextBlob::kFull_Positioning, SkPoint::Make(0, 0), builder); 229 SkAutoTUnref<const SkTextBlob> blob(builder.build()); 230 231 SkTextBlobRunIterator it(blob); 232 while (!it.done()) { 233 SkPaint paint; 234 it.applyFontToPaint(&paint); 235 236 REPORTER_ASSERT(reporter, paint.getTextSize() == font.getTextSize()); 237 REPORTER_ASSERT(reporter, paint.getTextScaleX() == font.getTextScaleX()); 238 REPORTER_ASSERT(reporter, paint.getTypeface() == font.getTypeface()); 239 REPORTER_ASSERT(reporter, paint.getTextSkewX() == font.getTextSkewX()); 240 REPORTER_ASSERT(reporter, paint.getTextAlign() == font.getTextAlign()); 241 REPORTER_ASSERT(reporter, paint.getHinting() == font.getHinting()); 242 REPORTER_ASSERT(reporter, paint.isAntiAlias() == font.isAntiAlias()); 243 REPORTER_ASSERT(reporter, paint.isUnderlineText() == font.isUnderlineText()); 244 REPORTER_ASSERT(reporter, paint.isStrikeThruText() == font.isStrikeThruText()); 245 REPORTER_ASSERT(reporter, paint.isFakeBoldText() == font.isFakeBoldText()); 246 REPORTER_ASSERT(reporter, paint.isLinearText() == font.isLinearText()); 247 REPORTER_ASSERT(reporter, paint.isSubpixelText() == font.isSubpixelText()); 248 REPORTER_ASSERT(reporter, paint.isDevKernText() == font.isDevKernText()); 249 REPORTER_ASSERT(reporter, paint.isLCDRenderText() == font.isLCDRenderText()); 250 REPORTER_ASSERT(reporter, paint.isEmbeddedBitmapText() == font.isEmbeddedBitmapText()); 251 REPORTER_ASSERT(reporter, paint.isAutohinted() == font.isAutohinted()); 252 REPORTER_ASSERT(reporter, paint.isVerticalText() == font.isVerticalText()); 253 REPORTER_ASSERT(reporter, (paint.getFlags() & SkPaint::kGenA8FromLCD_Flag) == 254 (font.getFlags() & SkPaint::kGenA8FromLCD_Flag)); 255 256 it.next(); 257 } 258 259 } 260 261 private: 262 struct RunDef { 263 unsigned count; 264 SkTextBlob::GlyphPositioning pos; 265 SkScalar x, y; 266 }; 267 268 static void RunBuilderTest(skiatest::Reporter* reporter, SkTextBlobBuilder& builder, 269 const RunDef in[], unsigned inCount, 270 const RunDef out[], unsigned outCount) { 271 SkPaint font; 272 font.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 273 274 unsigned glyphCount = 0; 275 unsigned posCount = 0; 276 277 for (unsigned i = 0; i < inCount; ++i) { 278 AddRun(font, in[i].count, in[i].pos, SkPoint::Make(in[i].x, in[i].y), builder); 279 glyphCount += in[i].count; 280 posCount += in[i].count * in[i].pos; 281 } 282 283 SkAutoTUnref<const SkTextBlob> blob(builder.build()); 284 285 SkTextBlobRunIterator it(blob); 286 for (unsigned i = 0; i < outCount; ++i) { 287 REPORTER_ASSERT(reporter, !it.done()); 288 REPORTER_ASSERT(reporter, out[i].pos == it.positioning()); 289 REPORTER_ASSERT(reporter, out[i].count == it.glyphCount()); 290 if (SkTextBlob::kDefault_Positioning == out[i].pos) { 291 REPORTER_ASSERT(reporter, out[i].x == it.offset().x()); 292 REPORTER_ASSERT(reporter, out[i].y == it.offset().y()); 293 } else if (SkTextBlob::kHorizontal_Positioning == out[i].pos) { 294 REPORTER_ASSERT(reporter, out[i].y == it.offset().y()); 295 } 296 297 for (unsigned k = 0; k < it.glyphCount(); ++k) { 298 REPORTER_ASSERT(reporter, k % 128 == it.glyphs()[k]); 299 if (SkTextBlob::kHorizontal_Positioning == it.positioning()) { 300 REPORTER_ASSERT(reporter, SkIntToScalar(k % 128) == it.pos()[k]); 301 } else if (SkTextBlob::kFull_Positioning == it.positioning()) { 302 REPORTER_ASSERT(reporter, SkIntToScalar(k % 128) == it.pos()[k * 2]); 303 REPORTER_ASSERT(reporter, -SkIntToScalar(k % 128) == it.pos()[k * 2 + 1]); 304 } 305 } 306 307 it.next(); 308 } 309 310 REPORTER_ASSERT(reporter, it.done()); 311 } 312 313 static void AddRun(const SkPaint& font, int count, SkTextBlob::GlyphPositioning pos, 314 const SkPoint& offset, SkTextBlobBuilder& builder, 315 const SkRect* bounds = nullptr) { 316 switch (pos) { 317 case SkTextBlob::kDefault_Positioning: { 318 const SkTextBlobBuilder::RunBuffer& rb = builder.allocRun(font, count, offset.x(), 319 offset.y(), bounds); 320 for (int i = 0; i < count; ++i) { 321 rb.glyphs[i] = i; 322 } 323 } break; 324 case SkTextBlob::kHorizontal_Positioning: { 325 const SkTextBlobBuilder::RunBuffer& rb = builder.allocRunPosH(font, count, offset.y(), 326 bounds); 327 for (int i = 0; i < count; ++i) { 328 rb.glyphs[i] = i; 329 rb.pos[i] = SkIntToScalar(i); 330 } 331 } break; 332 case SkTextBlob::kFull_Positioning: { 333 const SkTextBlobBuilder::RunBuffer& rb = builder.allocRunPos(font, count, bounds); 334 for (int i = 0; i < count; ++i) { 335 rb.glyphs[i] = i; 336 rb.pos[i * 2] = SkIntToScalar(i); 337 rb.pos[i * 2 + 1] = -SkIntToScalar(i); 338 } 339 } break; 340 default: 341 SkFAIL("unhandled positioning value"); 342 } 343 } 344 }; 345 346 DEF_TEST(TextBlob_builder, reporter) { 347 TextBlobTester::TestBuilder(reporter); 348 TextBlobTester::TestBounds(reporter); 349 } 350 351 DEF_TEST(TextBlob_paint, reporter) { 352 TextBlobTester::TestPaintProps(reporter); 353 } 354