1 /* 2 * Copyright 2011 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 "Test.h" 9 #include "SkPath.h" 10 #include "SkPaint.h" 11 #include "SkLayerDrawLooper.h" 12 #include "SkBlurMaskFilter.h" 13 #include "SkRandom.h" 14 #include "SkTypeface.h" 15 #include "SkUtils.h" 16 17 static size_t uni_to_utf8(const SkUnichar src[], void* dst, int count) { 18 char* u8 = (char*)dst; 19 for (int i = 0; i < count; ++i) { 20 int n = SkUTF8_FromUnichar(src[i], u8); 21 u8 += n; 22 } 23 return u8 - (char*)dst; 24 } 25 26 static size_t uni_to_utf16(const SkUnichar src[], void* dst, int count) { 27 uint16_t* u16 = (uint16_t*)dst; 28 for (int i = 0; i < count; ++i) { 29 int n = SkUTF16_FromUnichar(src[i], u16); 30 u16 += n; 31 } 32 return (char*)u16 - (char*)dst; 33 } 34 35 static size_t uni_to_utf32(const SkUnichar src[], void* dst, int count) { 36 SkUnichar* u32 = (SkUnichar*)dst; 37 if (src != u32) { 38 memcpy(u32, src, count * sizeof(SkUnichar)); 39 } 40 return count * sizeof(SkUnichar); 41 } 42 43 static SkTypeface::Encoding paint2encoding(const SkPaint& paint) { 44 SkPaint::TextEncoding enc = paint.getTextEncoding(); 45 SkASSERT(SkPaint::kGlyphID_TextEncoding != enc); 46 return (SkTypeface::Encoding)enc; 47 } 48 49 static int find_first_zero(const uint16_t glyphs[], int count) { 50 for (int i = 0; i < count; ++i) { 51 if (0 == glyphs[i]) { 52 return i; 53 } 54 } 55 return count; 56 } 57 58 static void test_cmap(skiatest::Reporter* reporter) { 59 static const int NGLYPHS = 64; 60 61 SkUnichar src[NGLYPHS]; 62 SkUnichar dst[NGLYPHS]; // used for utf8, utf16, utf32 storage 63 64 static const struct { 65 size_t (*fSeedTextProc)(const SkUnichar[], void* dst, int count); 66 SkPaint::TextEncoding fEncoding; 67 } gRec[] = { 68 { uni_to_utf8, SkPaint::kUTF8_TextEncoding }, 69 { uni_to_utf16, SkPaint::kUTF16_TextEncoding }, 70 { uni_to_utf32, SkPaint::kUTF32_TextEncoding }, 71 }; 72 73 SkRandom rand; 74 SkPaint paint; 75 paint.setTypeface(SkTypeface::RefDefault())->unref(); 76 SkTypeface* face = paint.getTypeface(); 77 78 for (int i = 0; i < 1000; ++i) { 79 // generate some random text 80 for (int j = 0; j < NGLYPHS; ++j) { 81 src[j] = ' ' + j; 82 } 83 // inject some random chars, to sometimes abort early 84 src[rand.nextU() & 63] = rand.nextU() & 0xFFF; 85 86 for (size_t k = 0; k < SK_ARRAY_COUNT(gRec); ++k) { 87 paint.setTextEncoding(gRec[k].fEncoding); 88 89 size_t len = gRec[k].fSeedTextProc(src, dst, NGLYPHS); 90 91 uint16_t glyphs0[NGLYPHS], glyphs1[NGLYPHS]; 92 93 bool contains = paint.containsText(dst, len); 94 int nglyphs = paint.textToGlyphs(dst, len, glyphs0); 95 int first = face->charsToGlyphs(dst, paint2encoding(paint), glyphs1, NGLYPHS); 96 int index = find_first_zero(glyphs1, NGLYPHS); 97 98 REPORTER_ASSERT(reporter, NGLYPHS == nglyphs); 99 REPORTER_ASSERT(reporter, index == first); 100 REPORTER_ASSERT(reporter, 101 !memcmp(glyphs0, glyphs1, NGLYPHS * sizeof(uint16_t))); 102 if (contains) { 103 REPORTER_ASSERT(reporter, NGLYPHS == first); 104 } else { 105 REPORTER_ASSERT(reporter, NGLYPHS > first); 106 } 107 } 108 } 109 } 110 111 // temparary api for bicubic, just be sure we can set/clear it 112 static void test_filterlevel(skiatest::Reporter* reporter) { 113 SkPaint p0, p1; 114 115 REPORTER_ASSERT(reporter, 116 SkPaint::kNone_FilterLevel == p0.getFilterLevel()); 117 118 static const SkPaint::FilterLevel gLevels[] = { 119 SkPaint::kNone_FilterLevel, 120 SkPaint::kLow_FilterLevel, 121 SkPaint::kMedium_FilterLevel, 122 SkPaint::kHigh_FilterLevel 123 }; 124 for (size_t i = 0; i < SK_ARRAY_COUNT(gLevels); ++i) { 125 p0.setFilterLevel(gLevels[i]); 126 REPORTER_ASSERT(reporter, gLevels[i] == p0.getFilterLevel()); 127 p1 = p0; 128 REPORTER_ASSERT(reporter, gLevels[i] == p1.getFilterLevel()); 129 130 p0.reset(); 131 REPORTER_ASSERT(reporter, 132 SkPaint::kNone_FilterLevel == p0.getFilterLevel()); 133 } 134 } 135 136 static void test_copy(skiatest::Reporter* reporter) { 137 SkPaint paint; 138 // set a few member variables 139 paint.setStyle(SkPaint::kStrokeAndFill_Style); 140 paint.setTextAlign(SkPaint::kLeft_Align); 141 paint.setStrokeWidth(SkIntToScalar(2)); 142 // set a few pointers 143 SkLayerDrawLooper* looper = new SkLayerDrawLooper(); 144 paint.setLooper(looper)->unref(); 145 SkMaskFilter* mask = SkBlurMaskFilter::Create(1, SkBlurMaskFilter::kNormal_BlurStyle); 146 paint.setMaskFilter(mask)->unref(); 147 148 // copy the paint using the copy constructor and check they are the same 149 SkPaint copiedPaint = paint; 150 REPORTER_ASSERT(reporter, paint == copiedPaint); 151 152 #ifdef SK_BUILD_FOR_ANDROID 153 // the copy constructor should preserve the Generation ID 154 uint32_t paintGenID = paint.getGenerationID(); 155 uint32_t copiedPaintGenID = copiedPaint.getGenerationID(); 156 REPORTER_ASSERT(reporter, paintGenID == copiedPaintGenID); 157 REPORTER_ASSERT(reporter, !memcmp(&paint, &copiedPaint, sizeof(paint))); 158 #endif 159 160 // copy the paint using the equal operator and check they are the same 161 copiedPaint = paint; 162 REPORTER_ASSERT(reporter, paint == copiedPaint); 163 164 #ifdef SK_BUILD_FOR_ANDROID 165 // the equals operator should increment the Generation ID 166 REPORTER_ASSERT(reporter, paint.getGenerationID() == paintGenID); 167 REPORTER_ASSERT(reporter, copiedPaint.getGenerationID() != copiedPaintGenID); 168 copiedPaintGenID = copiedPaint.getGenerationID(); // reset to the new value 169 REPORTER_ASSERT(reporter, memcmp(&paint, &copiedPaint, sizeof(paint))); 170 #endif 171 172 // clean the paint and check they are back to their initial states 173 SkPaint cleanPaint; 174 paint.reset(); 175 copiedPaint.reset(); 176 REPORTER_ASSERT(reporter, cleanPaint == paint); 177 REPORTER_ASSERT(reporter, cleanPaint == copiedPaint); 178 179 #ifdef SK_BUILD_FOR_ANDROID 180 // the reset function should increment the Generation ID 181 REPORTER_ASSERT(reporter, paint.getGenerationID() != paintGenID); 182 REPORTER_ASSERT(reporter, copiedPaint.getGenerationID() != copiedPaintGenID); 183 REPORTER_ASSERT(reporter, memcmp(&cleanPaint, &paint, sizeof(cleanPaint))); 184 REPORTER_ASSERT(reporter, memcmp(&cleanPaint, &copiedPaint, sizeof(cleanPaint))); 185 #endif 186 } 187 188 // found and fixed for webkit: mishandling when we hit recursion limit on 189 // mostly degenerate cubic flatness test 190 static void regression_cubic(skiatest::Reporter* reporter) { 191 SkPath path, stroke; 192 SkPaint paint; 193 194 path.moveTo(SkFloatToScalar(460.2881309415525f), 195 SkFloatToScalar(303.250847066498f)); 196 path.cubicTo(SkFloatToScalar(463.36378422175284f), 197 SkFloatToScalar(302.1169735073363f), 198 SkFloatToScalar(456.32239330810046f), 199 SkFloatToScalar(304.720354932878f), 200 SkFloatToScalar(453.15255460013304f), 201 SkFloatToScalar(305.788586869862f)); 202 203 SkRect fillR, strokeR; 204 fillR = path.getBounds(); 205 206 paint.setStyle(SkPaint::kStroke_Style); 207 paint.setStrokeWidth(SkIntToScalar(2)); 208 paint.getFillPath(path, &stroke); 209 strokeR = stroke.getBounds(); 210 211 SkRect maxR = fillR; 212 SkScalar miter = SkMaxScalar(SK_Scalar1, paint.getStrokeMiter()); 213 SkScalar inset = paint.getStrokeJoin() == SkPaint::kMiter_Join ? 214 SkScalarMul(paint.getStrokeWidth(), miter) : 215 paint.getStrokeWidth(); 216 maxR.inset(-inset, -inset); 217 218 // test that our stroke didn't explode 219 REPORTER_ASSERT(reporter, maxR.contains(strokeR)); 220 } 221 222 // found and fixed for android: not initializing rect for string's of length 0 223 static void regression_measureText(skiatest::Reporter* reporter) { 224 225 SkPaint paint; 226 paint.setTextSize(SkFloatToScalar(12.0f)); 227 228 SkRect r; 229 r.setLTRB(SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN); 230 231 // test that the rect was reset 232 paint.measureText("", 0, &r, SkFloatToScalar(1.0f)); 233 REPORTER_ASSERT(reporter, r.isEmpty()); 234 } 235 236 static void TestPaint(skiatest::Reporter* reporter) { 237 // TODO add general paint tests 238 test_copy(reporter); 239 240 // regression tests 241 regression_cubic(reporter); 242 regression_measureText(reporter); 243 244 test_filterlevel(reporter); 245 246 // need to implement charsToGlyphs on other backends (e.g. linux, win) 247 // before we can run this tests everywhere 248 if (false) { 249 test_cmap(reporter); 250 } 251 } 252 253 #include "TestClassDef.h" 254 DEFINE_TESTCLASS("Paint", TestPaintClass, TestPaint) 255