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