• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 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/SkFontMetrics.h"
10 #include "include/core/SkStream.h"
11 #include "include/core/SkTypeface.h"
12 #include "include/ports/SkTypeface_fontations.h"
13 #include "src/ports/SkTypeface_FreeType.h"
14 #include "tests/Test.h"
15 #include "tools/Resources.h"
16 
17 #include <memory>
18 
19 namespace {
20 const char kFontResource[] = "fonts/ahem.ttf";
21 const char kTtcResource[] = "fonts/test.ttc";
22 const char kNoCapHeightResource[] = "fonts/DejaVuSans.subset.ttf";
23 const char kNoCapHeightNoHxResource[] = "fonts/DejaVuSans.subset_noHx.ttf";
24 const char kVariableResource[] = "fonts/test_glyphs-glyf_colr_1_variable.ttf";
25 constexpr size_t kNumVariableAxes = 44;
26 
27 struct AxisExpectation {
28     SkFourByteTag tag;
29     float minValue;
30     float defValue;
31     float maxValue;
32 } axisExpectations[] = {
33         {SkSetFourByteTag('S', 'W', 'P', 'S'), -90.0, 0.0, 90.0},
34         {SkSetFourByteTag('S', 'W', 'P', 'E'), -90.0, 0.0, 90.0},
35         {SkSetFourByteTag('S', 'W', 'C', '1'), -2.0, 0.0, 2.0},
36         {SkSetFourByteTag('S', 'W', 'C', '2'), -2.0, 0.0, 2.0},
37         {SkSetFourByteTag('S', 'W', 'C', '3'), -2.0, 0.0, 2.0},
38         {SkSetFourByteTag('S', 'W', 'C', '4'), -2.0, 0.0, 2.0},
39         {SkSetFourByteTag('S', 'C', 'O', 'X'), -200., 0.0, 200.},
40         {SkSetFourByteTag('S', 'C', 'O', 'Y'), -200., 0.0, 200.},
41         {SkSetFourByteTag('S', 'C', 'S', 'X'), -2.0, 0.0, 1.9999389648437},
42         {SkSetFourByteTag('S', 'C', 'S', 'Y'), -2.0, 0.0, 1.9999389648437},
43         {SkSetFourByteTag('G', 'R', 'X', '0'), -1000, 0.0, 1000},
44         {SkSetFourByteTag('G', 'R', 'Y', '0'), -1000, 0.0, 1000},
45         {SkSetFourByteTag('G', 'R', 'X', '1'), -1000, 0.0, 1000},
46         {SkSetFourByteTag('G', 'R', 'Y', '1'), -1000, 0.0, 1000},
47         {SkSetFourByteTag('G', 'R', 'X', '2'), -1000, 0.0, 1000},
48         {SkSetFourByteTag('G', 'R', 'Y', '2'), -1000, 0.0, 1000},
49         {SkSetFourByteTag('G', 'R', 'R', '0'), -1000, 0.0, 1000},
50         {SkSetFourByteTag('G', 'R', 'R', '1'), -1000, 0.0, 1000},
51         {SkSetFourByteTag('C', 'O', 'L', '1'), -2.0, 0.0, 2.0},
52         {SkSetFourByteTag('C', 'O', 'L', '2'), -2.0, 0.0, 2.0},
53         {SkSetFourByteTag('C', 'O', 'L', '3'), -2.0, 0.0, 2.0},
54         {SkSetFourByteTag('R', 'O', 'T', 'A'), 0.0, 0.0, 539.989013671875},
55         {SkSetFourByteTag('R', 'O', 'T', 'X'), -500.0, 0.0, 500.0},
56         {SkSetFourByteTag('R', 'O', 'T', 'Y'), -500.0, 0.0, 500.0},
57         {SkSetFourByteTag('S', 'K', 'X', 'A'), -90.0, 0.0, 90.0},
58         {SkSetFourByteTag('S', 'K', 'Y', 'A'), -90.0, 0.0, 90.0},
59         {SkSetFourByteTag('S', 'K', 'C', 'X'), -500.0, 0.0, 500.0},
60         {SkSetFourByteTag('S', 'K', 'C', 'Y'), -500.0, 0.0, 500.0},
61         {SkSetFourByteTag('T', 'R', 'X', 'X'), -2.0, 0.0, 2.0},
62         {SkSetFourByteTag('T', 'R', 'Y', 'X'), -2.0, 0.0, 2.0},
63         {SkSetFourByteTag('T', 'R', 'X', 'Y'), -2.0, 0.0, 2.0},
64         {SkSetFourByteTag('T', 'R', 'Y', 'Y'), -2.0, 0.0, 2.0},
65         {SkSetFourByteTag('T', 'R', 'D', 'X'), -500.0, 0.0, 500.0},
66         {SkSetFourByteTag('T', 'R', 'D', 'Y'), -500.0, 0.0, 500.0},
67         {SkSetFourByteTag('T', 'L', 'D', 'X'), -500.0, 0.0, 500.0},
68         {SkSetFourByteTag('T', 'L', 'D', 'Y'), -500.0, 0.0, 500.0},
69         {SkSetFourByteTag('C', 'L', 'X', 'I'), -500.0, 0.0, 500.0},
70         {SkSetFourByteTag('C', 'L', 'Y', 'I'), -500.0, 0.0, 500.0},
71         {SkSetFourByteTag('C', 'L', 'X', 'A'), -500.0, 0.0, 500.0},
72         {SkSetFourByteTag('C', 'L', 'Y', 'A'), -500.0, 0.0, 500.0},
73         {SkSetFourByteTag('C', 'L', 'I', 'O'), -500.0, 0.0, 500.0},
74         {SkSetFourByteTag('A', 'P', 'H', '1'), -1.0, 0.0, 0.0},
75         {SkSetFourByteTag('A', 'P', 'H', '2'), -1.0, 0.0, 0.0},
76         {SkSetFourByteTag('A', 'P', 'H', '3'), -1.0, 0.0, 0.0},
77 };
78 }  // namespace
79 
DEF_TEST(Fontations_DoNotMakeFromNull,reporter)80 DEF_TEST(Fontations_DoNotMakeFromNull, reporter) {
81     std::unique_ptr<SkStreamAsset> nullStream = SkMemoryStream::MakeDirect(nullptr, 0);
82     sk_sp<SkTypeface> probeTypeface(
83             SkTypeface_Make_Fontations(std::move(nullStream), SkFontArguments()));
84     REPORTER_ASSERT(reporter, !probeTypeface);
85 }
86 
DEF_TEST(Fontations_DoNotMakeFromNonSfnt,reporter)87 DEF_TEST(Fontations_DoNotMakeFromNonSfnt, reporter) {
88     char notAnSfnt[] = "I_AM_NOT_AN_SFNT";
89     std::unique_ptr<SkStreamAsset> notSfntStream =
90             SkMemoryStream::MakeDirect(notAnSfnt, std::size(notAnSfnt));
91     sk_sp<SkTypeface> probeTypeface(
92             SkTypeface_Make_Fontations(std::move(notSfntStream), SkFontArguments()));
93     REPORTER_ASSERT(reporter, !probeTypeface);
94 }
95 
DEF_TEST(Fontations_MakeFromFont,reporter)96 DEF_TEST(Fontations_MakeFromFont, reporter) {
97     sk_sp<SkTypeface> probeTypeface(
98             SkTypeface_Make_Fontations(GetResourceAsStream(kFontResource), SkFontArguments()));
99     REPORTER_ASSERT(reporter, probeTypeface);
100 }
101 
DEF_TEST(Fontations_MakeFromCollection,reporter)102 DEF_TEST(Fontations_MakeFromCollection, reporter) {
103     sk_sp<SkTypeface> probeTypeface(
104             SkTypeface_Make_Fontations(GetResourceAsStream(kTtcResource), SkFontArguments()));
105     REPORTER_ASSERT(reporter, probeTypeface);
106 }
107 
DEF_TEST(Fontations_MakeFromCollectionNonNullIndex,reporter)108 DEF_TEST(Fontations_MakeFromCollectionNonNullIndex, reporter) {
109     SkFontArguments args;
110     args.setCollectionIndex(1);
111     sk_sp<SkTypeface> probeTypeface(
112             SkTypeface_Make_Fontations(GetResourceAsStream(kTtcResource), args));
113     REPORTER_ASSERT(reporter, probeTypeface);
114 }
115 
DEF_TEST(Fontations_DoNotMakeFromCollection_Invalid_Index,reporter)116 DEF_TEST(Fontations_DoNotMakeFromCollection_Invalid_Index, reporter) {
117     SkFontArguments args;
118     args.setCollectionIndex(1000);
119     sk_sp<SkTypeface> probeTypeface(
120             SkTypeface_Make_Fontations(GetResourceAsStream(kTtcResource), args));
121     REPORTER_ASSERT(reporter, !probeTypeface);
122 }
123 
DEF_TEST(Fontations_TableData,reporter)124 DEF_TEST(Fontations_TableData, reporter) {
125     constexpr size_t kNameTableSize = 11310;
126     constexpr size_t kTestOffset = 1310;
127     constexpr size_t kTestLength = 500;
128     char destBuffer[kNameTableSize] = {0};
129     sk_sp<SkTypeface> testTypeface(
130             SkTypeface_Make_Fontations(GetResourceAsStream(kFontResource), SkFontArguments()));
131     SkFourByteTag nameTableTag = SkSetFourByteTag('n', 'a', 'm', 'e');
132     SkFourByteTag nonExistantTag = SkSetFourByteTag('0', 'X', '0', 'X');
133 
134     // Getting size without buffer.
135     REPORTER_ASSERT(reporter,
136                     testTypeface->getTableData(nameTableTag, 0, kNameTableSize, nullptr) ==
137                             kNameTableSize);
138     // Reading full table.
139     REPORTER_ASSERT(reporter,
140                     testTypeface->getTableData(nameTableTag, 0, kNameTableSize, destBuffer) ==
141                             kNameTableSize);
142     // Reading restricted length.
143     REPORTER_ASSERT(
144             reporter,
145             testTypeface->getTableData(nameTableTag, 0, kTestLength, destBuffer) == kTestLength);
146     REPORTER_ASSERT(reporter,
147                     testTypeface->getTableData(
148                             nameTableTag, kTestOffset, kTestLength, destBuffer) == kTestLength);
149     // Reading at an offset.
150     REPORTER_ASSERT(
151             reporter,
152             testTypeface->getTableData(nameTableTag, kTestOffset, kNameTableSize, destBuffer) ==
153                     kNameTableSize - kTestOffset);
154 
155     // Reading from offset past table.
156     REPORTER_ASSERT(reporter,
157                     testTypeface->getTableData(
158                             nameTableTag, kNameTableSize, kNameTableSize, destBuffer) == 0);
159     REPORTER_ASSERT(reporter,
160                     testTypeface->getTableData(nameTableTag, kNameTableSize, 0, nullptr) == 0);
161     // Reading one byte before end of table.
162     REPORTER_ASSERT(reporter,
163                     testTypeface->getTableData(
164                             nameTableTag, kNameTableSize - 1, kNameTableSize, destBuffer) == 1);
165     // Trying to start reading at an offset past table start.
166     REPORTER_ASSERT(reporter,
167                     testTypeface->getTableData(nameTableTag, 0, kNameTableSize + 10, destBuffer) ==
168                             kNameTableSize);
169     // Restricting length without target buffer.
170     REPORTER_ASSERT(reporter,
171                     testTypeface->getTableData(nameTableTag, 0, kTestLength, nullptr) ==
172                             kTestLength);
173 
174     // Trying to access non-existant table.
175     REPORTER_ASSERT(reporter,
176                     testTypeface->getTableData(nonExistantTag, 0, kNameTableSize, destBuffer) ==
177                             0);
178     REPORTER_ASSERT(reporter,
179                     testTypeface->getTableData(nonExistantTag, 0, 0, nullptr) ==
180                             0);
181     REPORTER_ASSERT(reporter,
182                     testTypeface->getTableData(nonExistantTag, kTestOffset, 0, nullptr) == 0);
183 }
184 
DEF_TEST(Fontations_TableTags,reporter)185 DEF_TEST(Fontations_TableTags, reporter) {
186     constexpr size_t kNumTags = 11;
187     SkFourByteTag tagsBuffer[kNumTags] = {0};
188     sk_sp<SkTypeface> testTypeface(
189             SkTypeface_Make_Fontations(GetResourceAsStream(kFontResource), SkFontArguments()));
190     SkFourByteTag firstTag = SkSetFourByteTag('O', 'S', '/', '2');
191     SkFourByteTag lastTag = SkSetFourByteTag('p', 'o', 's', 't');
192 
193     REPORTER_ASSERT(reporter, testTypeface->getTableTags(nullptr) == kNumTags);
194 
195     REPORTER_ASSERT(reporter, testTypeface->getTableTags(tagsBuffer) == kNumTags);
196     REPORTER_ASSERT(reporter, tagsBuffer[0] == firstTag);
197     REPORTER_ASSERT(reporter, tagsBuffer[kNumTags - 1] == lastTag);
198 }
199 
DEF_TEST(Fontations_VariationPosition,reporter)200 DEF_TEST(Fontations_VariationPosition, reporter) {
201     sk_sp<SkTypeface> variableTypeface(
202             SkTypeface_Make_Fontations(GetResourceAsStream(kVariableResource), SkFontArguments()));
203     // Everything at default.
204     const int numAxes = variableTypeface->getVariationDesignPosition(nullptr, 0);
205     REPORTER_ASSERT(reporter, numAxes == kNumVariableAxes, "numAxes: %d", numAxes);
206 
207     SkFontArguments::VariationPosition::Coordinate kSwpsCoordinateFirst = {SkSetFourByteTag('S', 'W', 'P', 'S'), 25};
208     SkFontArguments::VariationPosition::Coordinate kSwpsCoordinateSecond = {SkSetFourByteTag('S', 'W', 'P', 'S'), 55};
209     SkFontArguments::VariationPosition::Coordinate kSwpeCoordinate = {SkSetFourByteTag('S', 'W', 'P', 'E'), 45};
210     SkFontArguments::VariationPosition::Coordinate kInvalidCoordinate = {SkSetFourByteTag('_', '_', '_', '_'), 0};
211 
212     // 'SWPS' and 'SWPE' exist. Second 'SWPS' should override first, invalid tag should be stripped.
213     SkFontArguments::VariationPosition::Coordinate cloneCoordinates[4] = {
214             kSwpsCoordinateFirst, kSwpsCoordinateSecond, kSwpeCoordinate, kInvalidCoordinate};
215 
216     SkFontArguments::VariationPosition clonePosition;
217     clonePosition.coordinates = cloneCoordinates;
218     clonePosition.coordinateCount = 4;
219 
220     sk_sp<SkTypeface> cloneTypeface = variableTypeface->makeClone(
221             SkFontArguments().setVariationDesignPosition(clonePosition));
222     const int cloneNumAxes = cloneTypeface->getVariationDesignPosition(nullptr, 0);
223     REPORTER_ASSERT(reporter, cloneNumAxes == kNumVariableAxes, "clonedNumAxes: %d", cloneNumAxes);
224 
225     SkFontArguments::VariationPosition::Coordinate retrieveCoordinates[kNumVariableAxes] = {};
226 
227     // Error when providing too little space.
228     const int badClonedNumAxes = cloneTypeface->getVariationDesignPosition(retrieveCoordinates, 1);
229     REPORTER_ASSERT(reporter, badClonedNumAxes == -1, "badClonedNumAxes: %d", badClonedNumAxes);
230 
231     const int retrievedClonedNumAxes =
232             cloneTypeface->getVariationDesignPosition(retrieveCoordinates, kNumVariableAxes);
233     REPORTER_ASSERT(reporter, retrievedClonedNumAxes == kNumVariableAxes,
234                     "retrievedClonedNumAxes: %d", retrievedClonedNumAxes);
235     REPORTER_ASSERT(reporter,
236                     retrieveCoordinates[0].axis  == kSwpsCoordinateSecond.axis &&
237                     retrieveCoordinates[0].value == kSwpsCoordinateSecond.value);
238     REPORTER_ASSERT(reporter,
239                     retrieveCoordinates[1].axis  == kSwpeCoordinate.axis &&
240                     retrieveCoordinates[1].value == kSwpeCoordinate.value);
241 }
242 
DEF_TEST(Fontations_VariationParameters,reporter)243 DEF_TEST(Fontations_VariationParameters, reporter) {
244     sk_sp<SkTypeface> variableTypeface(
245             SkTypeface_Make_Fontations(GetResourceAsStream(kVariableResource), SkFontArguments()));
246     REPORTER_ASSERT(reporter,
247                     variableTypeface->getVariationDesignParameters(nullptr, 0) == kNumVariableAxes);
248 
249     SkFontParameters::Variation::Axis axes[kNumVariableAxes] = {};
250     REPORTER_ASSERT(reporter,
251                     variableTypeface->getVariationDesignParameters(axes, kNumVariableAxes) ==
252                             kNumVariableAxes);
253 
254     for (size_t i = 0; i < kNumVariableAxes; ++i) {
255         REPORTER_ASSERT(reporter, axes[i].tag == axisExpectations[i].tag);
256         REPORTER_ASSERT(reporter, axes[i].min == axisExpectations[i].minValue);
257         REPORTER_ASSERT(reporter, axes[i].def == axisExpectations[i].defValue);
258         REPORTER_ASSERT(reporter, axes[i].max == axisExpectations[i].maxValue);
259     }
260 }
261 
DEF_TEST(Fontations_VariationParameters_BufferTooSmall,reporter)262 DEF_TEST(Fontations_VariationParameters_BufferTooSmall, reporter) {
263     sk_sp<SkTypeface> variableTypeface(
264             SkTypeface_Make_Fontations(GetResourceAsStream(kVariableResource), SkFontArguments()));
265     REPORTER_ASSERT(reporter,
266                     variableTypeface->getVariationDesignParameters(nullptr, 0) == kNumVariableAxes);
267 
268     constexpr size_t kArrayTooSmall = 3;
269     SkFontParameters::Variation::Axis axes[kArrayTooSmall] = {};
270     REPORTER_ASSERT(reporter,
271                     variableTypeface->getVariationDesignParameters(axes, kArrayTooSmall) == -1);
272 }
273 
DEF_TEST(Fontations_SyntheticCapHeight,reporter)274 DEF_TEST(Fontations_SyntheticCapHeight, reporter) {
275     sk_sp<SkTypeface> noCapHeightTypeface(SkTypeface_Make_Fontations(
276             GetResourceAsStream(kNoCapHeightResource), SkFontArguments()));
277     sk_sp<SkTypeface> noCapHeightNoHxTypeface(SkTypeface_Make_Fontations(
278             GetResourceAsStream(kNoCapHeightNoHxResource), SkFontArguments()));
279     SkASSERT_RELEASE(noCapHeightTypeface);
280     SkASSERT_RELEASE(noCapHeightNoHxTypeface);
281 
282     SkFont capHeightFont(noCapHeightTypeface);
283     SkFont capHeightFontNoHx(noCapHeightNoHxTypeface);
284 
285     capHeightFont.setSize(12);
286     capHeightFontNoHx.setSize(12);
287 
288     SkFontMetrics metrics;
289 
290     capHeightFont.getMetrics(&metrics);
291     const SkScalar kHCharHeight = 9.0;
292     REPORTER_ASSERT(reporter, metrics.fCapHeight == kHCharHeight);
293 
294     capHeightFontNoHx.getMetrics(&metrics);
295     SkGlyphID glyphId = noCapHeightNoHxTypeface->unicharToGlyph('H');
296     REPORTER_ASSERT(reporter, glyphId == 0, "Glyph lookup for H should fail, but was: %u", glyphId);
297 
298     const SkScalar kExpected = 11.138672;
299     REPORTER_ASSERT(reporter, metrics.fCapHeight == kExpected, "Metrics mismatch: %f vs. %f", kExpected, metrics.fCapHeight);
300 }
301 
DEF_TEST(Fontations_SyntheticXHeight,reporter)302 DEF_TEST(Fontations_SyntheticXHeight, reporter) {
303     sk_sp<SkTypeface> noXHeightTypeface(SkTypeface_Make_Fontations(
304             GetResourceAsStream(kNoCapHeightResource), SkFontArguments()));
305     sk_sp<SkTypeface> noXHeightNoHxTypeface(SkTypeface_Make_Fontations(
306             GetResourceAsStream(kNoCapHeightNoHxResource), SkFontArguments()));
307     SkASSERT_RELEASE(noXHeightTypeface);
308     SkASSERT_RELEASE(noXHeightNoHxTypeface);
309 
310     SkFont xHeightFont(noXHeightTypeface);
311     SkFont xHeightFontNoHx(noXHeightNoHxTypeface);
312 
313     xHeightFont.setSize(12);
314     xHeightFontNoHx.setSize(12);
315 
316     SkFontMetrics metrics;
317 
318     xHeightFont.getMetrics(&metrics);
319     const SkScalar kXCharHeight = 7.0;
320     REPORTER_ASSERT(reporter,
321                     metrics.fXHeight == kXCharHeight,
322                     "Expected: %f vs actual: %f\n",
323                     kXCharHeight,
324                     metrics.fXHeight);
325 
326     xHeightFontNoHx.getMetrics(&metrics);
327     SkGlyphID glyphId = noXHeightNoHxTypeface->unicharToGlyph('x');
328     REPORTER_ASSERT(reporter, glyphId == 0, "Glyph lookup for x should fail, but was: %u", glyphId);
329 
330     // xHeight falls back to ascent as well.
331     const SkScalar kExpected = 11.138672;
332     REPORTER_ASSERT(reporter, metrics.fXHeight == kExpected, "Metrics mismatch: %f vs. %f", kExpected, metrics.fXHeight);
333 }
334