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/SkData.h"
9 #include "include/core/SkFont.h"
10 #include "include/core/SkFontArguments.h"
11 #include "include/core/SkFontMgr.h"
12 #include "include/core/SkFontParameters.h"
13 #include "include/core/SkFontStyle.h"
14 #include "include/core/SkRect.h"
15 #include "include/core/SkRefCnt.h"
16 #include "include/core/SkScalar.h"
17 #include "include/core/SkStream.h"
18 #include "include/core/SkString.h"
19 #include "include/core/SkTypeface.h"
20 #include "include/core/SkTypes.h"
21 #include "include/private/base/SkFixed.h"
22 #include "include/private/base/SkTemplates.h"
23 #include "src/base/SkEndian.h"
24 #include "src/base/SkUTF.h"
25 #include "src/core/SkFontDescriptor.h"
26 #include "src/core/SkFontPriv.h"
27 #include "src/core/SkTypefaceCache.h"
28 #include "src/sfnt/SkOTTable_OS_2.h"
29 #include "src/sfnt/SkOTTable_OS_2_V0.h"
30 #include "src/sfnt/SkSFNTHeader.h"
31 #include "tests/Test.h"
32 #include "tools/Resources.h"
33 #include "tools/ToolUtils.h"
34 #include "tools/fonts/FontToolUtils.h"
35 #include "tools/fonts/TestEmptyTypeface.h"
36
37 #include <algorithm>
38 #include <array>
39 #include <cinttypes>
40 #include <cstddef>
41 #include <cstdint>
42 #include <cstring>
43 #include <memory>
44 #include <utility>
45
TypefaceStyle_test(skiatest::Reporter * reporter,uint16_t weight,uint16_t width,SkData * data)46 static void TypefaceStyle_test(skiatest::Reporter* reporter,
47 uint16_t weight, uint16_t width, SkData* data)
48 {
49 sk_sp<SkData> dataCopy;
50 if (!data->unique()) {
51 dataCopy = SkData::MakeWithCopy(data->data(), data->size());
52 data = dataCopy.get();
53 }
54 SkSFNTHeader* sfntHeader = static_cast<SkSFNTHeader*>(data->writable_data());
55
56 SkSFNTHeader::TableDirectoryEntry* tableEntry =
57 SkTAfter<SkSFNTHeader::TableDirectoryEntry>(sfntHeader);
58 SkSFNTHeader::TableDirectoryEntry* os2TableEntry = nullptr;
59 int numTables = SkEndian_SwapBE16(sfntHeader->numTables);
60 for (int tableEntryIndex = 0; tableEntryIndex < numTables; ++tableEntryIndex) {
61 if (SkOTTableOS2::TAG == tableEntry[tableEntryIndex].tag) {
62 os2TableEntry = tableEntry + tableEntryIndex;
63 break;
64 }
65 }
66 SkASSERT_RELEASE(os2TableEntry);
67
68 size_t os2TableOffset = SkEndian_SwapBE32(os2TableEntry->offset);
69 SkOTTableOS2_V0* os2Table = SkTAddOffset<SkOTTableOS2_V0>(sfntHeader, os2TableOffset);
70 os2Table->usWeightClass.value = SkEndian_SwapBE16(weight);
71 using WidthType = SkOTTableOS2_V0::WidthClass::Value;
72 os2Table->usWidthClass.value = static_cast<WidthType>(SkEndian_SwapBE16(width));
73
74 sk_sp<SkTypeface> newTypeface(ToolUtils::TestFontMgr()->makeFromData(sk_ref_sp(data)));
75 if (!newTypeface) {
76 // Not all SkFontMgr can MakeFromStream().
77 return;
78 }
79
80 SkFontStyle newStyle = newTypeface->fontStyle();
81
82 //printf("%d, %f\n", weight, (newStyle.weight() - (float)0x7FFF) / (float)0x7FFF);
83 //printf("%d, %f\n", width , (newStyle.width() - (float)0x7F) / (float)0x7F);
84 //printf("%d, %d\n", weight, newStyle.weight());
85 //printf("%d, %d\n", width , newStyle.width());
86
87 // Some back-ends (CG, GDI, DW) support OS/2 version A which uses 0 - 10 (but all differently).
88 REPORTER_ASSERT(reporter,
89 newStyle.weight() == weight ||
90 (weight <= 10 && newStyle.weight() == 100 * weight) ||
91 (weight == 4 && newStyle.weight() == 350) || // GDI weirdness
92 (weight == 5 && newStyle.weight() == 400) || // GDI weirdness
93 (weight == 0 && newStyle.weight() == 1) || // DW weirdness
94 (weight == 1000 && newStyle.weight() == 999) // DW weirdness
95 );
96
97 // Some back-ends (GDI) don't support width, ensure these always report 'normal'.
98 REPORTER_ASSERT(
99 reporter,
100 newStyle.width() == width || newStyle.width() == SkFontStyle::Width::kNormal_Width,
101 "newStyle.width(): %d width: %" PRIu16, newStyle.width(), width);
102 }
DEF_TEST(TypefaceStyle,reporter)103 DEF_TEST(TypefaceStyle, reporter) {
104 std::unique_ptr<SkStreamAsset> stream(GetResourceAsStream("fonts/Em.ttf"));
105 if (!stream) {
106 REPORT_FAILURE(reporter, "fonts/Em.ttf", SkString("Cannot load resource"));
107 return;
108 }
109 sk_sp<SkData> data(SkData::MakeFromStream(stream.get(), stream->getLength()));
110
111 using SkFS = SkFontStyle;
112 for (int weight = SkFS::kInvisible_Weight; weight <= SkFS::kExtraBlack_Weight; ++weight) {
113 TypefaceStyle_test(reporter, weight, 5, data.get());
114 }
115 for (int width = SkFS::kUltraCondensed_Width; width <= SkFS::kUltraExpanded_Width; ++width) {
116 TypefaceStyle_test(reporter, 400, width, data.get());
117 }
118 }
119
DEF_TEST(TypefaceStyleVariable,reporter)120 DEF_TEST(TypefaceStyleVariable, reporter) {
121 using Variation = SkFontArguments::VariationPosition;
122 sk_sp<SkFontMgr> fm = ToolUtils::TestFontMgr();
123
124 std::unique_ptr<SkStreamAsset> stream(GetResourceAsStream("fonts/Variable.ttf"));
125 if (!stream) {
126 REPORT_FAILURE(reporter, "fonts/Variable.ttf", SkString("Cannot load resource"));
127 return;
128 }
129 sk_sp<SkTypeface> typeface(ToolUtils::TestFontMgr()->makeFromStream(stream->duplicate()));
130 if (!typeface) {
131 // Not all SkFontMgr can MakeFromStream().
132 return;
133 }
134
135 // Creating Variable.ttf without any extra parameters should have a normal font style.
136 SkFontStyle fs = typeface->fontStyle();
137 REPORTER_ASSERT(reporter, fs == SkFontStyle::Normal(),
138 "fs: %d %d %d", fs.weight(), fs.width(), fs.slant());
139
140 // Ensure that the font supports variable stuff
141 Variation::Coordinate varPos[2];
142 int numAxes = typeface->getVariationDesignPosition(varPos, std::size(varPos));
143 if (numAxes <= 0) {
144 // Not all SkTypeface can get the variation.
145 return;
146 }
147 if (numAxes != 2) {
148 // Variable.ttf has two axes.
149 REPORTER_ASSERT(reporter, numAxes == 2);
150 return;
151 }
152
153 // If a fontmgr or typeface can do variations, ensure the variation affects the reported style.
154 struct TestCase {
155 std::vector<Variation::Coordinate> position;
156 SkFontStyle expected;
157
158 // On Mac10.15 and earlier, the wdth affected the style using the old gx ranges.
159 // On macOS 11 and later, the wdth affects the style using the new OpenType ranges.
160 // Allow old CoreText to report the wrong width values.
161 SkFontStyle mac1015expected;
162 } testCases[] = {
163 // In range but non-default
164 { {{ SkSetFourByteTag('w','g','h','t'), 200.0f },
165 { SkSetFourByteTag('w','d','t','h'), 75.0f }},
166 {200, 3, SkFontStyle::kUpright_Slant},
167 {200, 9, SkFontStyle::kUpright_Slant}},
168
169 // Out of range low, should clamp
170 { {{ SkSetFourByteTag('w','g','h','t'), 0.0f },
171 { SkSetFourByteTag('w','d','t','h'), 75.0f }},
172 {100, 3, SkFontStyle::kUpright_Slant},
173 {100, 9, SkFontStyle::kUpright_Slant}},
174
175 // Out of range high, should clamp
176 { {{ SkSetFourByteTag('w','g','h','t'), 10000.0f },
177 { SkSetFourByteTag('w','d','t','h'), 75.0f }},
178 {900, 3, SkFontStyle::kUpright_Slant},
179 {900, 9, SkFontStyle::kUpright_Slant}},
180 };
181
182 auto runTest = [&fm, &typeface, &stream, &reporter](TestCase& test){
183 static const constexpr bool isMac =
184 #if defined(SK_BUILD_FOR_MAC)
185 true;
186 #else
187 false;
188 #endif
189 SkFontArguments args;
190 args.setVariationDesignPosition(Variation{test.position.data(), (int)test.position.size()});
191
192 sk_sp<SkTypeface> nonDefaultTypeface = fm->makeFromStream(stream->duplicate(), args);
193 SkFontStyle ndfs = nonDefaultTypeface->fontStyle();
194 REPORTER_ASSERT(reporter, ndfs == test.expected || (isMac && ndfs == test.mac1015expected),
195 "ndfs: %d %d %d", ndfs.weight(), ndfs.width(), ndfs.slant());
196
197 sk_sp<SkTypeface> cloneTypeface = typeface->makeClone(args);
198 SkFontStyle cfs = cloneTypeface->fontStyle();
199 REPORTER_ASSERT(reporter, cfs == test.expected || (isMac && cfs == test.mac1015expected),
200 "cfs: %d %d %d", cfs.weight(), cfs.width(), cfs.slant());
201
202 };
203
204 for (auto&& testCase : testCases) {
205 runTest(testCase);
206 }
207 }
208
DEF_TEST(TypefacePostScriptName,reporter)209 DEF_TEST(TypefacePostScriptName, reporter) {
210 sk_sp<SkTypeface> typeface(ToolUtils::CreateTypefaceFromResource("fonts/Em.ttf"));
211 if (!typeface) {
212 // Not all SkFontMgr can MakeFromStream().
213 return;
214 }
215
216 SkString postScriptName;
217 bool hasName = typeface->getPostScriptName(&postScriptName);
218 bool hasName2 = typeface->getPostScriptName(nullptr);
219 REPORTER_ASSERT(reporter, hasName == hasName2);
220 if (hasName) {
221 REPORTER_ASSERT(reporter, postScriptName == SkString("Em"));
222 }
223 }
224
DEF_TEST(TypefaceRoundTrip,reporter)225 DEF_TEST(TypefaceRoundTrip, reporter) {
226 sk_sp<SkTypeface> typeface(ToolUtils::CreateTypefaceFromResource("fonts/7630.otf"));
227 if (!typeface) {
228 // Not all SkFontMgr can MakeFromStream().
229 return;
230 }
231
232 int fontIndex;
233 std::unique_ptr<SkStreamAsset> stream = typeface->openStream(&fontIndex);
234
235 sk_sp<SkTypeface> typeface2 =
236 ToolUtils::TestFontMgr()->makeFromStream(std::move(stream), fontIndex);
237 REPORTER_ASSERT(reporter, typeface2);
238 }
239
DEF_TEST(FontDescriptorNegativeVariationSerialize,reporter)240 DEF_TEST(FontDescriptorNegativeVariationSerialize, reporter) {
241 SkFontDescriptor desc;
242 SkFontStyle style(2, 9, SkFontStyle::kOblique_Slant);
243 desc.setStyle(style);
244 const char postscriptName[] = "postscript";
245 desc.setPostscriptName(postscriptName);
246 SkFontArguments::VariationPosition::Coordinate* variation = desc.setVariationCoordinates(1);
247 variation[0] = { 0, -1.0f };
248
249 SkDynamicMemoryWStream stream;
250 desc.serialize(&stream);
251 SkFontDescriptor descD;
252 SkFontDescriptor::Deserialize(stream.detachAsStream().get(), &descD);
253
254 REPORTER_ASSERT(reporter, descD.getStyle() == style);
255 REPORTER_ASSERT(reporter, 0 == strcmp(desc.getPostscriptName(), postscriptName));
256 if (descD.getVariationCoordinateCount() != 1) {
257 REPORT_FAILURE(reporter, "descD.getVariationCoordinateCount() != 1", SkString());
258 return;
259 }
260
261 REPORTER_ASSERT(reporter, descD.getVariation()[0].value == -1.0f);
262 }
263
DEF_TEST(TypefaceAxes,reporter)264 DEF_TEST(TypefaceAxes, reporter) {
265 using Variation = SkFontArguments::VariationPosition;
266 // In DWrite in at least up to 1901 18363.1198 IDWriteFontFace5::GetFontAxisValues and
267 // GetFontAxisValueCount along with IDWriteFontResource::GetFontAxisAttributes and
268 // GetFontAxisCount (and related) seem to incorrectly collapse multiple axes with the same tag.
269 // Since this is a limitation of the underlying implementation, for now allow the test to pass
270 // with the axis tag count (as opposed to the axis count). Eventually all implementations should
271 // pass this test without 'alsoAcceptedAxisTagCount'.
272 auto test = [&](SkTypeface* typeface, const Variation& expected, int alsoAcceptedAxisTagCount) {
273 if (!typeface) {
274 return; // Not all SkFontMgr can makeFromStream().
275 }
276
277 int actualCount = typeface->getVariationDesignPosition(nullptr, 0);
278 if (actualCount == -1) {
279 return; // The number of axes is unknown.
280 }
281 REPORTER_ASSERT(reporter, actualCount == expected.coordinateCount ||
282 actualCount == alsoAcceptedAxisTagCount);
283
284 // Variable font conservative bounds don't vary, so ensure they aren't reported.
285 REPORTER_ASSERT(reporter, typeface->getBounds().isEmpty());
286
287 std::unique_ptr<Variation::Coordinate[]> actual(new Variation::Coordinate[actualCount]);
288 actualCount = typeface->getVariationDesignPosition(actual.get(), actualCount);
289 if (actualCount == -1) {
290 return; // The position cannot be determined.
291 }
292 REPORTER_ASSERT(reporter, actualCount == expected.coordinateCount ||
293 actualCount == alsoAcceptedAxisTagCount);
294
295 // Every actual must be expected.
296 std::unique_ptr<bool[]> expectedUsed(new bool[expected.coordinateCount]());
297 for (int actualIdx = 0; actualIdx < actualCount; ++actualIdx) {
298 bool actualFound = false;
299 for (int expectedIdx = 0; expectedIdx < expected.coordinateCount; ++expectedIdx) {
300 if (expectedUsed[expectedIdx]) {
301 continue;
302 }
303
304 if (actual[actualIdx].axis != expected.coordinates[expectedIdx].axis) {
305 continue;
306 }
307
308 // Convert to fixed for "almost equal".
309 SkFixed fixedRead = SkScalarToFixed(actual[actualIdx].value);
310 SkFixed fixedOriginal = SkScalarToFixed(expected.coordinates[expectedIdx].value);
311 if (!(SkTAbs(fixedRead - fixedOriginal) < 2)) {
312 continue;
313 }
314
315 // This actual matched an unused expected.
316 actualFound = true;
317 expectedUsed[expectedIdx] = true;
318 break;
319 }
320 REPORTER_ASSERT(reporter, actualFound,
321 "Actual axis '%c%c%c%c' with value '%f' not expected",
322 (char)((actual[actualIdx].axis >> 24) & 0xFF),
323 (char)((actual[actualIdx].axis >> 16) & 0xFF),
324 (char)((actual[actualIdx].axis >> 8) & 0xFF),
325 (char)((actual[actualIdx].axis ) & 0xFF),
326 SkScalarToDouble(actual[actualIdx].value));
327 }
328 };
329
330 sk_sp<SkFontMgr> fm = ToolUtils::TestFontMgr();
331
332 // Not specifying a position should produce the default.
333 {
334 std::unique_ptr<SkStreamAsset> variable(GetResourceAsStream("fonts/Variable.ttf"));
335 if (!variable) {
336 REPORT_FAILURE(reporter, "variable", SkString());
337 return;
338 }
339 const Variation::Coordinate defaultPosition[] = {
340 { SkSetFourByteTag('w','g','h','t'), 400.0f },
341 { SkSetFourByteTag('w','d','t','h'), 100.0f },
342 };
343 sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(variable), 0);
344 test(typeface.get(), Variation{&defaultPosition[0], 2}, -1);
345 }
346
347 // Multiple axes with the same tag (and min, max, default) works.
348 {
349 std::unique_ptr<SkStreamAsset> dupTags(GetResourceAsStream("fonts/VaryAlongQuads.ttf"));
350 if (!dupTags) {
351 REPORT_FAILURE(reporter, "dupTags", SkString());
352 return;
353 }
354
355 // The position may be over specified. If there are multiple values for a given axis,
356 // ensure the last one since that's what css-fonts-4 requires.
357 const Variation::Coordinate position[] = {
358 { SkSetFourByteTag('w','g','h','t'), 700.0f },
359 { SkSetFourByteTag('w','g','h','t'), 600.0f },
360 { SkSetFourByteTag('w','g','h','t'), 600.0f },
361 };
362 SkFontArguments params;
363 params.setVariationDesignPosition({position, std::size(position)});
364 sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(dupTags), params);
365 test(typeface.get(), Variation{&position[1], 2}, 1);
366 }
367
368 // Overspecifying an axis tag value applies the last one in the list.
369 {
370 std::unique_ptr<SkStreamAsset> distortable(GetResourceAsStream("fonts/Distortable.ttf"));
371 if (!distortable) {
372 REPORT_FAILURE(reporter, "distortable", SkString());
373 return;
374 }
375
376 // The position may be over specified. If there are multiple values for a given axis,
377 // ensure the last one since that's what css-fonts-4 requires.
378 const Variation::Coordinate position[] = {
379 { SkSetFourByteTag('w','g','h','t'), 1.618033988749895f },
380 { SkSetFourByteTag('w','g','h','t'), SK_ScalarSqrt2 },
381 };
382 SkFontArguments params;
383 params.setVariationDesignPosition({position, std::size(position)});
384 sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(distortable), params);
385 test(typeface.get(), Variation{&position[1], 1}, -1);
386
387 if (typeface) {
388 // Cloning without specifying any parameters should produce an equivalent variation.
389 sk_sp<SkTypeface> clone = typeface->makeClone(SkFontArguments());
390 test(clone.get(), Variation{&position[1], 1}, -1);
391 }
392 }
393 }
394
DEF_TEST(TypefaceVariationIndex,reporter)395 DEF_TEST(TypefaceVariationIndex, reporter) {
396 std::unique_ptr<SkStreamAsset> distortable(GetResourceAsStream("fonts/Distortable.ttf"));
397 if (!distortable) {
398 REPORT_FAILURE(reporter, "distortable", SkString());
399 return;
400 }
401
402 sk_sp<SkFontMgr> fm = ToolUtils::TestFontMgr();
403 SkFontArguments params;
404 // The first named variation position in Distortable is 'Thin'.
405 params.setCollectionIndex(0x00010000);
406 sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(distortable), params);
407 if (!typeface) {
408 // FreeType is the only weird thing that supports this, Skia just needs to make sure if it
409 // gets one of these things make sense.
410 return;
411 }
412
413 int count = typeface->getVariationDesignPosition(nullptr, 0);
414 if (!(count == 1)) {
415 REPORT_FAILURE(reporter, "count == 1", SkString());
416 return;
417 }
418
419 SkFontArguments::VariationPosition::Coordinate positionRead[1];
420 count = typeface->getVariationDesignPosition(positionRead, std::size(positionRead));
421 if (count == -1) {
422 return;
423 }
424 if (!(count == 1)) {
425 REPORT_FAILURE(reporter, "count == 1", SkString());
426 return;
427 }
428 REPORTER_ASSERT(reporter, positionRead[0].axis == SkSetFourByteTag('w','g','h','t'));
429 REPORTER_ASSERT(reporter, positionRead[0].value == 0.5,
430 "positionRead[0].value: %f", positionRead[0].value);
431 }
432
DEF_TEST(Typeface,reporter)433 DEF_TEST(Typeface, reporter) {
434
435 sk_sp<SkTypeface> t1(ToolUtils::CreateTestTypeface(nullptr, SkFontStyle()));
436 sk_sp<SkTypeface> t2(ToolUtils::DefaultTypeface());
437
438 REPORTER_ASSERT(reporter, SkTypeface::Equal(t1.get(), t2.get()));
439 REPORTER_ASSERT(reporter, SkTypeface::Equal(nullptr, nullptr));
440
441 REPORTER_ASSERT(reporter, !SkTypeface::Equal(nullptr, t1.get()));
442 REPORTER_ASSERT(reporter, !SkTypeface::Equal(nullptr, t2.get()));
443 REPORTER_ASSERT(reporter, !SkTypeface::Equal(t1.get(), nullptr));
444 REPORTER_ASSERT(reporter, !SkTypeface::Equal(t2.get(), nullptr));
445 }
446
DEF_TEST(TypefaceAxesParameters,reporter)447 DEF_TEST(TypefaceAxesParameters, reporter) {
448 using Axis = SkFontParameters::Variation::Axis;
449
450 // In DWrite in at least up to 1901 18363.1198 IDWriteFontFace5::GetFontAxisValues and
451 // GetFontAxisValueCount along with IDWriteFontResource::GetFontAxisAttributes and
452 // GetFontAxisCount (and related) seem to incorrectly collapse multiple axes with the same tag.
453 // Since this is a limitation of the underlying implementation, for now allow the test to pass
454 // with the axis tag count (as opposed to the axis count). Eventually all implementations should
455 // pass this test without 'alsoAcceptedAxisTagCount'.
456 auto test = [&](SkTypeface* typeface, const Axis* expected, int expectedCount,
457 int alsoAcceptedAxisTagCount)
458 {
459 if (!typeface) {
460 return; // Not all SkFontMgr can makeFromStream().
461 }
462
463 int actualCount = typeface->getVariationDesignParameters(nullptr, 0);
464 if (actualCount == -1) {
465 return; // The number of axes is unknown.
466 }
467 REPORTER_ASSERT(reporter, actualCount == expectedCount ||
468 actualCount == alsoAcceptedAxisTagCount);
469
470 std::unique_ptr<Axis[]> actual(new Axis[actualCount]);
471 actualCount = typeface->getVariationDesignParameters(actual.get(), actualCount);
472 if (actualCount == -1) {
473 return; // The position cannot be determined.
474 }
475 REPORTER_ASSERT(reporter, actualCount == expectedCount ||
476 actualCount == alsoAcceptedAxisTagCount);
477
478 // Every actual must be expected.
479 std::unique_ptr<bool[]> expectedUsed(new bool[expectedCount]());
480 for (int actualIdx = 0; actualIdx < actualCount; ++actualIdx) {
481 bool actualFound = false;
482 for (int expectedIdx = 0; expectedIdx < expectedCount; ++expectedIdx) {
483 if (expectedUsed[expectedIdx]) {
484 continue;
485 }
486
487 if (actual[actualIdx].tag != expected[expectedIdx].tag) {
488 continue;
489 }
490
491 // Convert to fixed for "almost equal".
492 SkFixed fixedActualMin = SkScalarToFixed(actual[actualIdx].min);
493 SkFixed fixedExpectedMin = SkScalarToFixed(expected[expectedIdx].min);
494 if (!(SkTAbs(fixedActualMin - fixedExpectedMin) < 2)) {
495 continue;
496 }
497
498 SkFixed fixedActualMax = SkScalarToFixed(actual[actualIdx].max);
499 SkFixed fixedExpectedMax = SkScalarToFixed(expected[expectedIdx].max);
500 if (!(SkTAbs(fixedActualMax - fixedExpectedMax) < 2)) {
501 continue;
502 }
503
504 SkFixed fixedActualDefault = SkScalarToFixed(actual[actualIdx].def);
505 SkFixed fixedExpectedDefault = SkScalarToFixed(expected[expectedIdx].def);
506 if (!(SkTAbs(fixedActualDefault - fixedExpectedDefault) < 2)) {
507 continue;
508 }
509
510 // This seems silly, but allows MSAN to ensure that isHidden is initialized.
511 // In GDI or before macOS 10.12, Win10, or FreeType 2.8.1 API for hidden is missing.
512 if (actual[actualIdx].isHidden() &&
513 actual[actualIdx].isHidden() != expected[expectedIdx].isHidden())
514 {
515 continue;
516 }
517
518 // This actual matched an unused expected.
519 actualFound = true;
520 expectedUsed[expectedIdx] = true;
521 break;
522 }
523 REPORTER_ASSERT(reporter, actualFound,
524 "Actual axis '%c%c%c%c' with min %f max %f default %f hidden %s not expected",
525 (char)((actual[actualIdx].tag >> 24) & 0xFF),
526 (char)((actual[actualIdx].tag >> 16) & 0xFF),
527 (char)((actual[actualIdx].tag >> 8) & 0xFF),
528 (char)((actual[actualIdx].tag ) & 0xFF),
529 actual[actualIdx].min,
530 actual[actualIdx].def,
531 actual[actualIdx].max,
532 actual[actualIdx].isHidden() ? "true" : "false");
533 }
534 };
535
536 sk_sp<SkFontMgr> fm = ToolUtils::TestFontMgr();
537
538 // Two axis OpenType variable font.
539 {
540 std::unique_ptr<SkStreamAsset> variable(GetResourceAsStream("fonts/Variable.ttf"));
541 if (!variable) {
542 REPORT_FAILURE(reporter, "variable", SkString());
543 return;
544 }
545 constexpr Axis expected[] = {
546 Axis(SkSetFourByteTag('w','g','h','t'), 100.0f, 400.0f, 900.0f, true ),
547 Axis(SkSetFourByteTag('w','d','t','h'), 50.0f, 100.0f, 200.0f, false),
548 };
549 sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(variable), 0);
550 test(typeface.get(), &expected[0], std::size(expected), -1);
551 }
552
553 // Multiple axes with the same tag (and min, max, default) works.
554 {
555 std::unique_ptr<SkStreamAsset> dupTags(GetResourceAsStream("fonts/VaryAlongQuads.ttf"));
556 if (!dupTags) {
557 REPORT_FAILURE(reporter, "dupTags", SkString());
558 return;
559 }
560
561 // The position may be over specified. If there are multiple values for a given axis,
562 // ensure the last one since that's what css-fonts-4 requires.
563 constexpr Axis expected[] = {
564 Axis(SkSetFourByteTag('w','g','h','t'), 100.0f, 400.0f, 900.0f, false),
565 Axis(SkSetFourByteTag('w','g','h','t'), 100.0f, 400.0f, 900.0f, false),
566 };
567 sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(dupTags), 0);
568 test(typeface.get(), &expected[0], std::size(expected), 1);
569 }
570
571 // Simple single axis GX variable font.
572 {
573 std::unique_ptr<SkStreamAsset> distortable(GetResourceAsStream("fonts/Distortable.ttf"));
574 if (!distortable) {
575 REPORT_FAILURE(reporter, "distortable", SkString());
576 return;
577 }
578 constexpr Axis expected[] = {
579 Axis(SkSetFourByteTag('w','g','h','t'), 0.5f, 1.0f, 2.0f, true),
580 };
581 sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(distortable), 0);
582 test(typeface.get(), &expected[0], std::size(expected), -1);
583 }
584 }
585
count_proc(SkTypeface * face,void * ctx)586 static bool count_proc(SkTypeface* face, void* ctx) {
587 int* count = static_cast<int*>(ctx);
588 *count = *count + 1;
589 return false;
590 }
count(skiatest::Reporter * reporter,const SkTypefaceCache & cache)591 static int count(skiatest::Reporter* reporter, const SkTypefaceCache& cache) {
592 int count = 0;
593 sk_sp<SkTypeface> none = cache.findByProcAndRef(count_proc, &count);
594 REPORTER_ASSERT(reporter, none == nullptr);
595 return count;
596 }
597
DEF_TEST(TypefaceCache,reporter)598 DEF_TEST(TypefaceCache, reporter) {
599 sk_sp<SkTypeface> t1(TestEmptyTypeface::Make());
600 {
601 SkTypefaceCache cache;
602 REPORTER_ASSERT(reporter, count(reporter, cache) == 0);
603 {
604 sk_sp<SkTypeface> t0(TestEmptyTypeface::Make());
605 cache.add(t0);
606 REPORTER_ASSERT(reporter, count(reporter, cache) == 1);
607 cache.add(t1);
608 REPORTER_ASSERT(reporter, count(reporter, cache) == 2);
609 cache.purgeAll();
610 REPORTER_ASSERT(reporter, count(reporter, cache) == 2);
611 }
612 REPORTER_ASSERT(reporter, count(reporter, cache) == 2);
613 cache.purgeAll();
614 REPORTER_ASSERT(reporter, count(reporter, cache) == 1);
615 }
616 REPORTER_ASSERT(reporter, t1->unique());
617 }
618
check_serialize_behaviors(sk_sp<SkTypeface> tf,skiatest::Reporter * reporter)619 static void check_serialize_behaviors(sk_sp<SkTypeface> tf, skiatest::Reporter* reporter) {
620 if (!tf) {
621 return;
622 }
623
624 SkFontDescriptor desc;
625 bool serialize;
626 tf->getFontDescriptor(&desc, &serialize);
627
628 auto data0 = tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
629 auto data1 = tf->serialize(SkTypeface::SerializeBehavior::kDontIncludeData);
630 auto data2 = tf->serialize(SkTypeface::SerializeBehavior::kIncludeDataIfLocal);
631
632 REPORTER_ASSERT(reporter, data0->size() >= data1->size());
633
634 if (serialize) {
635 REPORTER_ASSERT(reporter, data0->equals(data2.get()));
636 } else {
637 REPORTER_ASSERT(reporter, data1->equals(data2.get()));
638 }
639 }
640
DEF_TEST(Typeface_serialize,reporter)641 DEF_TEST(Typeface_serialize, reporter) {
642 check_serialize_behaviors(ToolUtils::DefaultTypeface(), reporter);
643 check_serialize_behaviors(
644 ToolUtils::TestFontMgr()->makeFromStream(GetResourceAsStream("fonts/Distortable.ttf")),
645 reporter);
646 }
647
DEF_TEST(Typeface_glyph_to_char,reporter)648 DEF_TEST(Typeface_glyph_to_char, reporter) {
649 ToolUtils::EmojiTestSample emojiSample = ToolUtils::EmojiSample();
650 SkFont font(emojiSample.typeface, 12);
651 SkASSERT(font.getTypeface());
652 char const * text = emojiSample.sampleText;
653 size_t const textLen = strlen(text);
654 SkString familyName;
655 font.getTypeface()->getFamilyName(&familyName);
656
657 size_t const codepointCount = SkUTF::CountUTF8(text, textLen);
658 char const * const textEnd = text + textLen;
659 std::unique_ptr<SkUnichar[]> originalCodepoints(new SkUnichar[codepointCount]);
660 for (size_t i = 0; i < codepointCount; ++i) {
661 originalCodepoints[i] = SkUTF::NextUTF8(&text, textEnd);
662 }
663 std::unique_ptr<SkGlyphID[]> glyphs(new SkGlyphID[codepointCount]);
664 font.unicharsToGlyphs(originalCodepoints.get(), codepointCount, glyphs.get());
665 if (std::any_of(glyphs.get(), glyphs.get()+codepointCount, [](SkGlyphID g){ return g == 0;})) {
666 ERRORF(reporter, "Unexpected typeface \"%s\". Expected full support for emoji_sample_text.",
667 familyName.c_str());
668 return;
669 }
670
671 std::unique_ptr<SkUnichar[]> newCodepoints(new SkUnichar[codepointCount]);
672 SkFontPriv::GlyphsToUnichars(font, glyphs.get(), codepointCount, newCodepoints.get());
673
674 for (size_t i = 0; i < codepointCount; ++i) {
675 // GDI does not support character to glyph mapping outside BMP.
676 if (ToolUtils::FontMgrIsGDI() && 0xFFFF < originalCodepoints[i] && newCodepoints[i] == 0) {
677 continue;
678 }
679 // If two codepoints map to the same glyph then this assert is not valid.
680 // However, the emoji test font should never have multiple characters map to the same glyph.
681 REPORTER_ASSERT(reporter, originalCodepoints[i] == newCodepoints[i],
682 "name:%s i:%zu original:%d new:%d glyph:%d", familyName.c_str(), i,
683 originalCodepoints[i], newCodepoints[i], glyphs[i]);
684 }
685 }
686
687 // This test makes sure the legacy typeface creation does not lose its specified
688 // style. See https://bugs.chromium.org/p/skia/issues/detail?id=8447 for more
689 // context.
DEF_TEST(LegacyMakeTypeface,reporter)690 DEF_TEST(LegacyMakeTypeface, reporter) {
691 sk_sp<SkFontMgr> fm = ToolUtils::TestFontMgr();
692 sk_sp<SkTypeface> typeface1 = fm->legacyMakeTypeface(nullptr, SkFontStyle::Italic());
693 sk_sp<SkTypeface> typeface2 = fm->legacyMakeTypeface(nullptr, SkFontStyle::Bold());
694 sk_sp<SkTypeface> typeface3 = fm->legacyMakeTypeface(nullptr, SkFontStyle::BoldItalic());
695
696 if (typeface1 || typeface2 || typeface3) {
697 REPORTER_ASSERT(reporter, typeface1 && typeface2 && typeface1);
698 }
699
700 if (typeface1) {
701 REPORTER_ASSERT(reporter, typeface1->isItalic());
702 REPORTER_ASSERT(reporter, !typeface1->isBold());
703 }
704 if (typeface2) {
705 REPORTER_ASSERT(reporter, !typeface2->isItalic());
706 REPORTER_ASSERT(reporter, typeface2->isBold());
707 }
708 if (typeface3) {
709 REPORTER_ASSERT(reporter, typeface3->isItalic());
710 REPORTER_ASSERT(reporter, typeface3->isBold());
711 }
712 }
713