• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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