xref: /third_party/skia/tests/FontHostTest.cpp (revision cb93a386)
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 "include/core/SkFont.h"
9#include "include/core/SkPaint.h"
10#include "include/core/SkStream.h"
11#include "include/core/SkTypeface.h"
12#include "src/core/SkAutoMalloc.h"
13#include "src/core/SkEndian.h"
14#include "src/core/SkFontStream.h"
15#include "src/core/SkOSFile.h"
16#include "tests/Test.h"
17#include "tools/Resources.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
26static 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).
36static 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).
55static 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
72static void test_fontstream(skiatest::Reporter* reporter, SkStream* stream, int ttcIndex) {
73    int n = SkFontStream::GetTableTags(stream, ttcIndex, nullptr);
74    SkAutoTArray<SkFontTableTag> array(n);
75
76    int n2 = SkFontStream::GetTableTags(stream, ttcIndex, array.get());
77    REPORTER_ASSERT(reporter, n == n2);
78
79    for (int i = 0; i < n; ++i) {
80#ifdef DUMP_TTC_TABLES
81        SkString str;
82        SkFontTableTag t = array[i];
83        str.appendUnichar((t >> 24) & 0xFF);
84        str.appendUnichar((t >> 16) & 0xFF);
85        str.appendUnichar((t >>  8) & 0xFF);
86        str.appendUnichar((t >>  0) & 0xFF);
87        SkDebugf("[%d:%d] '%s'\n", ttcIndex, i, str.c_str());
88#endif
89        size_t size = SkFontStream::GetTableSize(stream, ttcIndex, array[i]);
90        for (size_t j = 0; j < SK_ARRAY_COUNT(gKnownTableSizes); ++j) {
91            if (gKnownTableSizes[j].fTag == array[i]) {
92                REPORTER_ASSERT(reporter, gKnownTableSizes[j].fSize == size);
93            }
94        }
95    }
96}
97
98static void test_fontstream(skiatest::Reporter* reporter) {
99    std::unique_ptr<SkStreamAsset> stream(GetResourceAsStream("fonts/test.ttc"));
100    if (!stream) {
101        SkDebugf("Skipping FontHostTest::test_fontstream\n");
102        return;
103    }
104
105    int count = SkFontStream::CountTTCEntries(stream.get());
106#ifdef DUMP_TTC_TABLES
107    SkDebugf("CountTTCEntries %d\n", count);
108#endif
109    for (int i = 0; i < count; ++i) {
110        test_fontstream(reporter, stream.get(), i);
111    }
112}
113
114// Exercise this rare cmap format (platform 3, encoding 0)
115static void test_symbolfont(skiatest::Reporter* reporter) {
116    auto tf = MakeResourceAsTypeface("fonts/SpiderSymbol.ttf");
117    if (tf) {
118        SkUnichar c = 0xf021;
119        uint16_t g = SkFont(tf).unicharToGlyph(c);
120        REPORTER_ASSERT(reporter, g == 3);
121    } else {
122        // not all platforms support data fonts, so we just note that failure
123        SkDebugf("Skipping FontHostTest::test_symbolfont\n");
124    }
125}
126
127static void test_tables(skiatest::Reporter* reporter, const sk_sp<SkTypeface>& face) {
128    if (false) { // avoid bit rot, suppress warning
129        SkFontID fontID = face->uniqueID();
130        REPORTER_ASSERT(reporter, fontID);
131    }
132
133    int count = face->countTables();
134
135    SkAutoTMalloc<SkFontTableTag> storage(count);
136    SkFontTableTag* tags = storage.get();
137
138    int count2 = face->getTableTags(tags);
139    REPORTER_ASSERT(reporter, count2 == count);
140
141    for (int i = 0; i < count; ++i) {
142        size_t size = face->getTableSize(tags[i]);
143        REPORTER_ASSERT(reporter, size > 0);
144
145#ifdef DUMP_TABLES
146        char name[5];
147        name[0] = (tags[i] >> 24) & 0xFF;
148        name[1] = (tags[i] >> 16) & 0xFF;
149        name[2] = (tags[i] >>  8) & 0xFF;
150        name[3] = (tags[i] >>  0) & 0xFF;
151        name[4] = 0;
152        SkDebugf("%s %d\n", name, size);
153#endif
154
155        for (size_t j = 0; j < SK_ARRAY_COUNT(gKnownTableSizes); ++j) {
156            if (gKnownTableSizes[j].fTag == tags[i]) {
157                REPORTER_ASSERT(reporter, gKnownTableSizes[j].fSize == size);
158            }
159        }
160
161        // do we get the same size from GetTableData and GetTableSize
162        {
163            SkAutoMalloc data(size);
164            size_t size2 = face->getTableData(tags[i], 0, size, data.get());
165            REPORTER_ASSERT(reporter, size2 == size);
166            sk_sp<SkData> data2 = face->copyTableData(tags[i]);
167            REPORTER_ASSERT(reporter, size == data2->size());
168            REPORTER_ASSERT(reporter, !memcmp(data.get(), data2->data(), size));
169        }
170    }
171}
172
173static void test_tables(skiatest::Reporter* reporter) {
174    static const char* const gNames[] = {
175        nullptr,   // default font
176        "Helvetica", "Arial",
177        "Times", "Times New Roman",
178        "Courier", "Courier New",
179        "Terminal", "MS Sans Serif",
180        "Hiragino Mincho ProN", "MS PGothic",
181    };
182
183    for (size_t i = 0; i < SK_ARRAY_COUNT(gNames); ++i) {
184        sk_sp<SkTypeface> face(SkTypeface::MakeFromName(gNames[i], SkFontStyle()));
185        if (face) {
186#ifdef DUMP_TABLES
187            SkDebugf("%s\n", gNames[i]);
188#endif
189            test_tables(reporter, face);
190            test_unitsPerEm(reporter, face);
191            test_countGlyphs(reporter, face);
192        }
193    }
194}
195
196/*
197 * Verifies that the advance values returned by generateAdvance and
198 * generateMetrics match.
199 */
200static void test_advances(skiatest::Reporter* reporter) {
201    static const char* const faces[] = {
202        nullptr,   // default font
203        "Arial", "Times", "Times New Roman", "Helvetica", "Courier",
204        "Courier New", "Verdana", "monospace",
205    };
206
207    static const struct {
208        SkFontHinting   hinting;
209        bool            linear;
210        bool            subpixel;
211    } settings[] = {
212        { SkFontHinting::kNone,   false, false },
213        { SkFontHinting::kNone,   true,  false },
214        { SkFontHinting::kNone,   false, true  },
215        { SkFontHinting::kSlight, false, false },
216        { SkFontHinting::kSlight, true,  false },
217        { SkFontHinting::kSlight, false, true  },
218        { SkFontHinting::kNormal, false, false },
219        { SkFontHinting::kNormal, true,  false },
220        { SkFontHinting::kNormal, false, true  },
221    };
222
223    static const struct {
224        SkScalar    fScaleX;
225        SkScalar    fSkewX;
226    } gScaleRec[] = {
227        { SK_Scalar1, 0 },
228        { SK_Scalar1/2, 0 },
229        // these two exercise obliquing (skew)
230        { SK_Scalar1, -SK_Scalar1/4 },
231        { SK_Scalar1/2, -SK_Scalar1/4 },
232    };
233
234    SkFont font;
235    char const * const txt = "long.text.with.lots.of.dots.";
236    size_t textLen = strlen(txt);
237
238    for (size_t i = 0; i < SK_ARRAY_COUNT(faces); i++) {
239        font.setTypeface(SkTypeface::MakeFromName(faces[i], SkFontStyle()));
240
241        for (size_t j = 0; j  < SK_ARRAY_COUNT(settings); j++) {
242            font.setHinting(settings[j].hinting);
243            font.setLinearMetrics(settings[j].linear);
244            font.setSubpixel(settings[j].subpixel);
245
246            for (size_t k = 0; k < SK_ARRAY_COUNT(gScaleRec); ++k) {
247                font.setScaleX(gScaleRec[k].fScaleX);
248                font.setSkewX(gScaleRec[k].fSkewX);
249
250                SkRect bounds;
251
252                // For no hinting and light hinting this should take the
253                // optimized generateAdvance path.
254                SkScalar width1 = font.measureText(txt, textLen, SkTextEncoding::kUTF8);
255
256                // Requesting the bounds forces a generateMetrics call.
257                SkScalar width2 = font.measureText(txt, textLen, SkTextEncoding::kUTF8, &bounds);
258
259                // SkDebugf("Font: %s, generateAdvance: %f, generateMetrics: %f\n",
260                //    faces[i], SkScalarToFloat(width1), SkScalarToFloat(width2));
261
262                REPORTER_ASSERT(reporter, width1 == width2);
263            }
264        }
265    }
266}
267
268DEF_TEST(FontHost, reporter) {
269    test_tables(reporter);
270    test_fontstream(reporter);
271    test_advances(reporter);
272    test_symbolfont(reporter);
273}
274
275// need tests for SkStrSearch
276