1 /*
2  * Copyright 2012 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 "Resources.h"
9 #include "SkAutoMalloc.h"
10 #include "SkEndian.h"
11 #include "SkFont.h"
12 #include "SkFontStream.h"
13 #include "SkOSFile.h"
14 #include "SkPaint.h"
15 #include "SkStream.h"
16 #include "SkTypeface.h"
17 #include "Test.h"
18 
19 //#define DUMP_TABLES
20 //#define DUMP_TTC_TABLES
21 
22 #define kFontTableTag_head          SkSetFourByteTag('h', 'e', 'a', 'd')
23 #define kFontTableTag_hhea          SkSetFourByteTag('h', 'h', 'e', 'a')
24 #define kFontTableTag_maxp          SkSetFourByteTag('m', 'a', 'x', 'p')
25 
26 static const struct TagSize {
27     SkFontTableTag  fTag;
28     size_t          fSize;
29 } gKnownTableSizes[] = {
30     {   kFontTableTag_head,         54 },
31     {   kFontTableTag_hhea,         36 },
32 };
33 
34 // Test that getUnitsPerEm() agrees with a direct lookup in the 'head' table
35 // (if that table is available).
test_unitsPerEm(skiatest::Reporter * reporter,const sk_sp<SkTypeface> & face)36 static void test_unitsPerEm(skiatest::Reporter* reporter, const sk_sp<SkTypeface>& face) {
37     int nativeUPEM = face->getUnitsPerEm();
38 
39     int tableUPEM = -1;
40     size_t size = face->getTableSize(kFontTableTag_head);
41     if (size) {
42         // unitsPerEm is at offset 18 into the 'head' table.
43         uint16_t rawUPEM;
44         face->getTableData(kFontTableTag_head, 18, sizeof(rawUPEM), &rawUPEM);
45         tableUPEM = SkEndian_SwapBE16(rawUPEM);
46     }
47 
48     if (tableUPEM >= 0) {
49         REPORTER_ASSERT(reporter, tableUPEM == nativeUPEM);
50     }
51 }
52 
53 // Test that countGlyphs() agrees with a direct lookup in the 'maxp' table
54 // (if that table is available).
test_countGlyphs(skiatest::Reporter * reporter,const sk_sp<SkTypeface> & face)55 static void test_countGlyphs(skiatest::Reporter* reporter, const sk_sp<SkTypeface>& face) {
56     int nativeGlyphs = face->countGlyphs();
57 
58     int tableGlyphs = -1;
59     size_t size = face->getTableSize(kFontTableTag_maxp);
60     if (size) {
61         // glyphs is at offset 4 into the 'maxp' table.
62         uint16_t rawGlyphs;
63         face->getTableData(kFontTableTag_maxp, 4, sizeof(rawGlyphs), &rawGlyphs);
64         tableGlyphs = SkEndian_SwapBE16(rawGlyphs);
65     }
66 
67     if (tableGlyphs >= 0) {
68         REPORTER_ASSERT(reporter, tableGlyphs == nativeGlyphs);
69     }
70 }
71 
72 // The following three are all the same code points in various encodings.
73 // a中Яיו��a��
74 static uint8_t utf8Chars[] = { 0x61, 0xE4,0xB8,0xAD, 0xD0,0xAF, 0xD7,0x99, 0xD7,0x95, 0xF0,0x9D,0x84,0x9E, 0x61, 0xF0,0xA0,0xAE,0x9F };
75 static uint16_t utf16Chars[] = { 0x0061, 0x4E2D, 0x042F, 0x05D9, 0x05D5, 0xD834,0xDD1E, 0x0061, 0xD842,0xDF9F };
76 static uint32_t utf32Chars[] = { 0x00000061, 0x00004E2D, 0x0000042F, 0x000005D9, 0x000005D5, 0x0001D11E, 0x00000061, 0x00020B9F };
77 
78 struct CharsToGlyphs_TestData {
79     const void* chars;
80     int charCount;
81     size_t charsByteLength;
82     SkTypeface::Encoding typefaceEncoding;
83     const char* name;
84 } static charsToGlyphs_TestData[] = {
85     { utf8Chars, 8, sizeof(utf8Chars), SkTypeface::kUTF8_Encoding, "Simple UTF-8" },
86     { utf16Chars, 8, sizeof(utf16Chars), SkTypeface::kUTF16_Encoding, "Simple UTF-16" },
87     { utf32Chars, 8, sizeof(utf32Chars), SkTypeface::kUTF32_Encoding, "Simple UTF-32" },
88 };
89 
90 // Test that SkPaint::textToGlyphs agrees with SkTypeface::charsToGlyphs.
test_charsToGlyphs(skiatest::Reporter * reporter,sk_sp<SkTypeface> face)91 static void test_charsToGlyphs(skiatest::Reporter* reporter, sk_sp<SkTypeface> face) {
92     uint16_t paintGlyphIds[256];
93     uint16_t faceGlyphIds[256];
94 
95     for (size_t testIndex = 0; testIndex < SK_ARRAY_COUNT(charsToGlyphs_TestData); ++testIndex) {
96         CharsToGlyphs_TestData& test = charsToGlyphs_TestData[testIndex];
97         SkTextEncoding encoding = static_cast<SkTextEncoding>(test.typefaceEncoding);
98 
99         SkFont font(face);
100         font.textToGlyphs(test.chars, test.charsByteLength, encoding,
101                           paintGlyphIds, SK_ARRAY_COUNT(paintGlyphIds));
102 
103         face->charsToGlyphs(test.chars, test.typefaceEncoding, faceGlyphIds, test.charCount);
104 
105         for (int i = 0; i < test.charCount; ++i) {
106             SkString name;
107             face->getFamilyName(&name);
108             SkString a;
109             a.appendf("%s, paintGlyphIds[%d] = %d, faceGlyphIds[%d] = %d, face = %s",
110                       test.name, i, (int)paintGlyphIds[i], i, (int)faceGlyphIds[i], name.c_str());
111             REPORTER_ASSERT(reporter, paintGlyphIds[i] == faceGlyphIds[i], a.c_str());
112         }
113     }
114 }
115 
test_fontstream(skiatest::Reporter * reporter,SkStream * stream,int ttcIndex)116 static void test_fontstream(skiatest::Reporter* reporter, SkStream* stream, int ttcIndex) {
117     int n = SkFontStream::GetTableTags(stream, ttcIndex, nullptr);
118     SkAutoTArray<SkFontTableTag> array(n);
119 
120     int n2 = SkFontStream::GetTableTags(stream, ttcIndex, array.get());
121     REPORTER_ASSERT(reporter, n == n2);
122 
123     for (int i = 0; i < n; ++i) {
124 #ifdef DUMP_TTC_TABLES
125         SkString str;
126         SkFontTableTag t = array[i];
127         str.appendUnichar((t >> 24) & 0xFF);
128         str.appendUnichar((t >> 16) & 0xFF);
129         str.appendUnichar((t >>  8) & 0xFF);
130         str.appendUnichar((t >>  0) & 0xFF);
131         SkDebugf("[%d:%d] '%s'\n", ttcIndex, i, str.c_str());
132 #endif
133         size_t size = SkFontStream::GetTableSize(stream, ttcIndex, array[i]);
134         for (size_t j = 0; j < SK_ARRAY_COUNT(gKnownTableSizes); ++j) {
135             if (gKnownTableSizes[j].fTag == array[i]) {
136                 REPORTER_ASSERT(reporter, gKnownTableSizes[j].fSize == size);
137             }
138         }
139     }
140 }
141 
test_fontstream(skiatest::Reporter * reporter)142 static void test_fontstream(skiatest::Reporter* reporter) {
143     std::unique_ptr<SkStreamAsset> stream(GetResourceAsStream("fonts/test.ttc"));
144     if (!stream) {
145         SkDebugf("Skipping FontHostTest::test_fontstream\n");
146         return;
147     }
148 
149     int count = SkFontStream::CountTTCEntries(stream.get());
150 #ifdef DUMP_TTC_TABLES
151     SkDebugf("CountTTCEntries %d\n", count);
152 #endif
153     for (int i = 0; i < count; ++i) {
154         test_fontstream(reporter, stream.get(), i);
155     }
156 }
157 
158 // Exercise this rare cmap format (platform 3, encoding 0)
test_symbolfont(skiatest::Reporter * reporter)159 static void test_symbolfont(skiatest::Reporter* reporter) {
160     auto tf = MakeResourceAsTypeface("fonts/SpiderSymbol.ttf");
161     if (tf) {
162         SkUnichar c = 0xf021;
163         uint16_t g = SkFont(tf).unicharToGlyph(c);
164         REPORTER_ASSERT(reporter, g == 3);
165     } else {
166         // not all platforms support data fonts, so we just note that failure
167         SkDebugf("Skipping FontHostTest::test_symbolfont\n");
168     }
169 }
170 
test_tables(skiatest::Reporter * reporter,const sk_sp<SkTypeface> & face)171 static void test_tables(skiatest::Reporter* reporter, const sk_sp<SkTypeface>& face) {
172     if (false) { // avoid bit rot, suppress warning
173         SkFontID fontID = face->uniqueID();
174         REPORTER_ASSERT(reporter, fontID);
175     }
176 
177     int count = face->countTables();
178 
179     SkAutoTMalloc<SkFontTableTag> storage(count);
180     SkFontTableTag* tags = storage.get();
181 
182     int count2 = face->getTableTags(tags);
183     REPORTER_ASSERT(reporter, count2 == count);
184 
185     for (int i = 0; i < count; ++i) {
186         size_t size = face->getTableSize(tags[i]);
187         REPORTER_ASSERT(reporter, size > 0);
188 
189 #ifdef DUMP_TABLES
190         char name[5];
191         name[0] = (tags[i] >> 24) & 0xFF;
192         name[1] = (tags[i] >> 16) & 0xFF;
193         name[2] = (tags[i] >>  8) & 0xFF;
194         name[3] = (tags[i] >>  0) & 0xFF;
195         name[4] = 0;
196         SkDebugf("%s %d\n", name, size);
197 #endif
198 
199         for (size_t j = 0; j < SK_ARRAY_COUNT(gKnownTableSizes); ++j) {
200             if (gKnownTableSizes[j].fTag == tags[i]) {
201                 REPORTER_ASSERT(reporter, gKnownTableSizes[j].fSize == size);
202             }
203         }
204 
205         // do we get the same size from GetTableData and GetTableSize
206         {
207             SkAutoMalloc data(size);
208             size_t size2 = face->getTableData(tags[i], 0, size, data.get());
209             REPORTER_ASSERT(reporter, size2 == size);
210         }
211     }
212 }
213 
test_tables(skiatest::Reporter * reporter)214 static void test_tables(skiatest::Reporter* reporter) {
215     static const char* const gNames[] = {
216         nullptr,   // default font
217         "Helvetica", "Arial",
218         "Times", "Times New Roman",
219         "Courier", "Courier New",
220         "Terminal", "MS Sans Serif",
221         "Hiragino Mincho ProN", "MS PGothic",
222     };
223 
224     for (size_t i = 0; i < SK_ARRAY_COUNT(gNames); ++i) {
225         sk_sp<SkTypeface> face(SkTypeface::MakeFromName(gNames[i], SkFontStyle()));
226         if (face) {
227 #ifdef DUMP_TABLES
228             SkDebugf("%s\n", gNames[i]);
229 #endif
230             test_tables(reporter, face);
231             test_unitsPerEm(reporter, face);
232             test_countGlyphs(reporter, face);
233             test_charsToGlyphs(reporter, face);
234         }
235     }
236 }
237 
238 /*
239  * Verifies that the advance values returned by generateAdvance and
240  * generateMetrics match.
241  */
test_advances(skiatest::Reporter * reporter)242 static void test_advances(skiatest::Reporter* reporter) {
243     static const char* const faces[] = {
244         nullptr,   // default font
245         "Arial", "Times", "Times New Roman", "Helvetica", "Courier",
246         "Courier New", "Verdana", "monospace",
247     };
248 
249     static const struct {
250         SkFontHinting   hinting;
251         bool            linear;
252         bool            subpixel;
253     } settings[] = {
254         { kNo_SkFontHinting,     false, false },
255         { kNo_SkFontHinting,     true,  false },
256         { kNo_SkFontHinting,     false, true  },
257         { kSlight_SkFontHinting, false, false },
258         { kSlight_SkFontHinting, true,  false },
259         { kSlight_SkFontHinting, false, true  },
260         { kNormal_SkFontHinting, false, false },
261         { kNormal_SkFontHinting, true,  false },
262         { kNormal_SkFontHinting, false, true  },
263     };
264 
265     static const struct {
266         SkScalar    fScaleX;
267         SkScalar    fSkewX;
268     } gScaleRec[] = {
269         { SK_Scalar1, 0 },
270         { SK_Scalar1/2, 0 },
271         // these two exercise obliquing (skew)
272         { SK_Scalar1, -SK_Scalar1/4 },
273         { SK_Scalar1/2, -SK_Scalar1/4 },
274     };
275 
276     SkFont font;
277     char txt[] = "long.text.with.lots.of.dots.";
278 
279     for (size_t i = 0; i < SK_ARRAY_COUNT(faces); i++) {
280         font.setTypeface(SkTypeface::MakeFromName(faces[i], SkFontStyle()));
281 
282         for (size_t j = 0; j  < SK_ARRAY_COUNT(settings); j++) {
283             font.setHinting(settings[j].hinting);
284             font.setLinearMetrics(settings[j].linear);
285             font.setSubpixel(settings[j].subpixel);
286 
287             for (size_t k = 0; k < SK_ARRAY_COUNT(gScaleRec); ++k) {
288                 font.setScaleX(gScaleRec[k].fScaleX);
289                 font.setSkewX(gScaleRec[k].fSkewX);
290 
291                 SkRect bounds;
292 
293                 // For no hinting and light hinting this should take the
294                 // optimized generateAdvance path.
295                 SkScalar width1 = font.measureText(txt, strlen(txt), kUTF8_SkTextEncoding);
296 
297                 // Requesting the bounds forces a generateMetrics call.
298                 SkScalar width2 = font.measureText(txt, strlen(txt), kUTF8_SkTextEncoding, &bounds);
299 
300                 // SkDebugf("Font: %s, generateAdvance: %f, generateMetrics: %f\n",
301                 //    faces[i], SkScalarToFloat(width1), SkScalarToFloat(width2));
302 
303                 REPORTER_ASSERT(reporter, width1 == width2);
304             }
305         }
306     }
307 }
308 
DEF_TEST(FontHost,reporter)309 DEF_TEST(FontHost, reporter) {
310     test_tables(reporter);
311     test_fontstream(reporter);
312     test_advances(reporter);
313     test_symbolfont(reporter);
314 }
315 
316 // need tests for SkStrSearch
317