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