1 /*
2 * Copyright 2021 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/SkFontMgr.h"
9 #include "include/core/SkRefCnt.h"
10 #include "include/core/SkTypeface.h"
11 #include "include/ports/SkTypeface_mac.h"
12 #include "src/base/SkZip.h"
13 #include "src/utils/SkFloatUtils.h"
14 #include "tests/Test.h"
15
16 #include <stdarg.h>
17 #include <string>
18 #include <vector>
19
20 static void SkMaybeDebugf(const char* fmt, ...) SK_PRINTF_LIKE(1, 2);
21
SkMaybeDebugf(const char * format,...)22 static void SkMaybeDebugf(const char* format, ...) {
23 if ((false)) {
24 va_list args;
25 va_start(args, format);
26 vprintf(format, args);
27 va_end(args);
28 }
29 }
30
DEF_TEST(TypefaceMacVariation,reporter)31 DEF_TEST(TypefaceMacVariation, reporter) {
32 auto makeSystemFont = [](float size) -> CTFontRef {
33 // kCTFontUIFontSystem, kCTFontUIFontMessage
34 return CTFontCreateUIFontForLanguage(kCTFontUIFontSystem, size, nullptr);
35 };
36
37 auto tagToString = [](SkFourByteTag tag) -> std::string {
38 char buffer[5];
39 buffer[0] = (tag & 0xff000000) >> 24;
40 buffer[1] = (tag & 0xff0000) >> 16;
41 buffer[2] = (tag & 0xff00) >> 8;
42 buffer[3] = tag & 0xff;
43 buffer[4] = 0;
44 return std::string(buffer);
45 };
46
47 // This typeface has the issue.
48 sk_sp<SkTypeface> typeface(SkMakeTypefaceFromCTFont(makeSystemFont(30)));
49
50 // Since MakeFromFile creates at default size 12, these two are more comparable.
51 // The first one has the issue and the second does not.
52 //typeface = SkMakeTypefaceFromCTFont(makeSystemFont(12));
53 //typeface = SkTypeface::MakeFromFile("/System/Library/Fonts/SFNS.ttf");
54
55 // Setting the initial opsz <= min, the reported wght axis is strange, but draws the same?
56 //typeface = SkMakeTypefaceFromCTFont(makeSystemFont(17));
57 //typeface = SkMakeTypefaceFromCTFont(makeSystemFont(17.01));
58
59 // Setting the initial opsz >= max, the requested variation doesn't take effect?
60 //typeface = SkMakeTypefaceFromCTFont(makeSystemFont(95.9));
61 //typeface = SkMakeTypefaceFromCTFont(makeSystemFont(96));
62
63 if (!typeface) {
64 REPORTER_ASSERT(reporter, typeface);
65 return;
66 }
67 using Coordinate = SkFontArguments::VariationPosition::Coordinate;
68 using Axis = SkFontParameters::Variation::Axis;
69
70 const int originalPositionCount = typeface->getVariationDesignPosition(nullptr, 0);
71 std::vector<Coordinate> originalPosition(originalPositionCount);
72 const int retrievedOriginalPositionCount =
73 typeface->getVariationDesignPosition(originalPosition.data(), originalPosition.size());
74 if (!(retrievedOriginalPositionCount == originalPositionCount)) {
75 REPORTER_ASSERT(reporter, retrievedOriginalPositionCount == originalPositionCount);
76 return;
77 }
78
79 constexpr SkFourByteTag kGRADTag = SkSetFourByteTag('G', 'R', 'A', 'D');
80 constexpr SkFourByteTag kWghtTag = SkSetFourByteTag('w', 'g', 'h', 't');
81 constexpr SkFourByteTag kWdthTag = SkSetFourByteTag('w', 'd', 't', 'h');
82 constexpr SkFourByteTag kOpszTag = SkSetFourByteTag('o', 'p', 's', 'z');
83
84 SkMaybeDebugf("Original: ");
85 for (auto& originalCoordinate : originalPosition) {
86 SkMaybeDebugf("(%s: %f) ", tagToString(originalCoordinate.axis).c_str(),
87 originalCoordinate.value);
88 }
89 SkMaybeDebugf("\n\n");
90
91 const int originalAxisCount = typeface->getVariationDesignParameters(nullptr, 0);
92 std::vector<Axis> originalAxes(originalAxisCount);
93 const int returnedOriginalAxisCount =
94 typeface->getVariationDesignParameters(originalAxes.data(), originalAxes.size());
95 if (!(returnedOriginalAxisCount == originalAxisCount)) {
96 REPORTER_ASSERT(reporter, returnedOriginalAxisCount == originalAxisCount);
97 return;
98 }
99
100 for (bool omitOpsz : {false, true}) {
101 for (SkFourByteTag axisToBump : { 0u, kOpszTag, kWdthTag, kGRADTag }) {
102 for (float testCoordinate : {100, 300, 400, 500, 700, 900}) {
103 std::vector<Coordinate> expectedPosition;
104 std::vector<Coordinate> requestPosition;
105 SkMaybeDebugf("Request : ");
106 for (auto& originalCoordinate : originalPosition) {
107 float requestValue = originalCoordinate.value;
108 if (originalCoordinate.axis == kOpszTag && omitOpsz) {
109 SkMaybeDebugf("#%s: %f# ", tagToString(originalCoordinate.axis).c_str(),
110 requestValue);
111 } else {
112 if (originalCoordinate.axis == axisToBump) {
113 // CoreText floats for the variation coordinates have limited precision.
114 // 'opsz' sems to have more precision since it is set somewhat independently.
115 // Though in mocOS 14 this seems to have changed and it can be more rounded.
116 //requestValue = nextafter(requestValue, HUGE_VALF); // Does not work.
117 requestValue += requestValue / 1024.0f; // Expect at least 10 bits.
118 }
119 if (originalCoordinate.axis == kWghtTag) {
120 requestValue = testCoordinate;
121 }
122 SkMaybeDebugf("(%s: %f) ", tagToString(originalCoordinate.axis).c_str(),
123 requestValue);
124 requestPosition.push_back({originalCoordinate.axis, requestValue});
125 }
126
127 float expectedValue = requestValue;
128 for (auto& originalAxis : originalAxes) {
129 if (originalAxis.tag == originalCoordinate.axis) {
130 expectedValue = std::min(expectedValue, originalAxis.max);
131 expectedValue = std::max(expectedValue, originalAxis.min);
132 }
133 }
134 expectedPosition.push_back({originalCoordinate.axis, expectedValue});
135 }
136 SkMaybeDebugf("\n");
137
138 SkMaybeDebugf("Expected: ");
139 for (auto& expectedCoordinate : expectedPosition) {
140 SkMaybeDebugf("(%s: %f) ", tagToString(expectedCoordinate.axis).c_str(),
141 expectedCoordinate.value);
142 }
143 SkMaybeDebugf("\n");
144
145 SkFontArguments::VariationPosition variationPosition =
146 { requestPosition.data(), (int)requestPosition.size() };
147 sk_sp<SkTypeface> cloneTypeface(
148 typeface->makeClone(SkFontArguments().setVariationDesignPosition(variationPosition)));
149
150 const int cloneAxisCount = cloneTypeface->getVariationDesignPosition(nullptr, 0);
151 std::vector<Coordinate> clonePosition(cloneAxisCount);
152 const int retrievedCloneAxisCount =
153 cloneTypeface->getVariationDesignPosition(clonePosition.data(), clonePosition.size());
154 if (!(retrievedCloneAxisCount == cloneAxisCount)) {
155 REPORTER_ASSERT(reporter, retrievedCloneAxisCount == cloneAxisCount);
156 continue;
157 }
158
159 SkMaybeDebugf("Result : ");
160 for (auto& cloneCoordinate : clonePosition) {
161 SkMaybeDebugf("(%s: %f) ", tagToString(cloneCoordinate.axis).c_str(),
162 cloneCoordinate.value);
163 }
164 SkMaybeDebugf("\n");
165
166 if (clonePosition.size() != expectedPosition.size()) {
167 REPORTER_ASSERT(reporter, clonePosition.size() == expectedPosition.size());
168 continue;
169 }
170
171 auto compareCoordinate = [](const Coordinate& a, const Coordinate& b) -> bool {
172 return a.axis < b.axis;
173 };
174 std::sort(clonePosition.begin(), clonePosition.end(), compareCoordinate);
175 std::sort(expectedPosition.begin(), expectedPosition.end(), compareCoordinate);
176 for (const auto&& [clone, expected] : SkMakeZip(clonePosition, expectedPosition)) {
177 REPORTER_ASSERT(reporter, clone.axis == expected.axis, "%s == %s",
178 tagToString(clone.axis).c_str(), tagToString(expected.axis).c_str());
179
180 // Allow a lot of slop here, ignoring the bottom 6 of the 23 bits.
181 // CoreText appears to round `opsz` a lot starting in macOS 14.
182 const SkFloatingPoint<float, 64> cloneValue(clone.value), expectedValue(expected.value);
183 REPORTER_ASSERT(reporter, cloneValue.AlmostEquals(expectedValue), "%s:%f == %s:%f",
184 tagToString(clone.axis).c_str(), clone.value,
185 tagToString(expected.axis).c_str(), expected.value);
186 }
187
188 SkMaybeDebugf("\n");
189 }
190 }
191 }
192 }
193