xref: /aosp_15_r20/external/skia/tests/FontationsFtCompTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2023 Google LLC
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/SkFontMgr.h"
10 #include "include/core/SkPath.h"
11 #include "include/core/SkStream.h"
12 #include "include/core/SkTextBlob.h"
13 #include "include/core/SkTypeface.h"
14 #include "include/ports/SkTypeface_fontations.h"
15 #include "modules/skshaper/include/SkShaper.h"
16 #include "src/ports/SkTypeface_FreeType.h"
17 #include "tests/Test.h"
18 #include "tools/TestFontDataProvider.h"
19 
20 #include <memory>
21 
22 namespace {
23 
textBlobsAllPathsEqual(sk_sp<const SkTextBlob> blobA,sk_sp<const SkTextBlob> blobB,SkString fontName)24 bool textBlobsAllPathsEqual(sk_sp<const SkTextBlob> blobA,
25                             sk_sp<const SkTextBlob> blobB,
26                             SkString fontName) {
27     SkTextBlob::Iter iterA(*blobA);
28     SkTextBlob::Iter iterB(*blobB);
29     SkTextBlob::Iter::ExperimentalRun runAInfo;
30     SkTextBlob::Iter::ExperimentalRun runBInfo;
31     SkPath pathA, pathB;
32     while (iterA.experimentalNext(&runAInfo)) {
33         pathA.reset();
34         pathB.reset();
35         SkASSERT(iterB.experimentalNext(&runBInfo));
36         for (int i = 0; i < runAInfo.count; ++i) {
37             runAInfo.font.getPath(runAInfo.glyphs[i], &pathA);
38             runBInfo.font.getPath(runBInfo.glyphs[i], &pathB);
39             SkDynamicMemoryWStream streamA, streamB;
40             // Re-use the logic in Path::dump() to canonicalize the output and account for
41             // differences between FreeType (inserting a lineTo() to original moveTo() coordinate)
42             // or Fontations (which saves the extra lineTo() before close()).
43             pathA.dump(&streamA, false);
44             pathB.dump(&streamB, false);
45             sk_sp<SkData> dataA = streamA.detachAsData();
46             sk_sp<SkData> dataB = streamB.detachAsData();
47             if (dataA->size() != dataB->size() ||
48                 memcmp(dataA->data(), dataB->data(), dataA->size() - 1)) {
49                 // See https://issues.skia.org/345178242 for details.
50                 // If there are path differences between FreeType and Fontations,
51                 // it might be needed to test for path equality after PathOps::Simplify()
52                 // as FreeType does the simplification, but Fontations does not.
53                 SkDebugf("Different path in font %s for glyph index: %d glyph id: %d, data sizes "
54                          "%zu vs %zu\n",
55                          fontName.c_str(), i, runAInfo.glyphs[i],
56                          dataA->size(), dataB->size());
57                 std::string fontationsPath(reinterpret_cast<const char*>(dataA->bytes()),
58                                            dataA->size());
59                 std::string freetypePath(reinterpret_cast<const char*>(dataB->bytes()),
60                                          dataB->size());
61                 SkDebugf("Path A (Fontations): \n%s\n", fontationsPath.c_str());
62                 SkDebugf("Path B (FreeType): \n%s\n", freetypePath.c_str());
63                 return false;
64             }
65         }
66     }
67     return true;
68 }
69 
70 class FontationsFtComparison {
71 public:
FontationsFtComparison(std::string fontMatch,std::string langMatch)72     FontationsFtComparison(std::string fontMatch, std::string langMatch)
73             : fTestDataIterator(fontMatch, langMatch) {}
74 
assertAllPathsEqual(skiatest::Reporter * reporter)75     void assertAllPathsEqual(skiatest::Reporter* reporter) {
76         TestFontDataProvider::TestSet testSet;
77         size_t numTestsExecuted = 0;
78         while (fTestDataIterator.next(&testSet)) {
79             sk_sp<SkTypeface> fontationsTypeface = SkTypeface_Make_Fontations(
80                     SkStream::MakeFromFile(testSet.fontFilename.c_str()), SkFontArguments());
81             sk_sp<SkTypeface> freetypeTypeface = SkTypeface_FreeType::MakeFromStream(
82                     SkStream::MakeFromFile(testSet.fontFilename.c_str()), SkFontArguments());
83 
84             SkASSERT_RELEASE(fontationsTypeface);
85             SkASSERT_RELEASE(freetypeTypeface);
86 
87             int upem = fontationsTypeface->getUnitsPerEm();
88             SkFont fontationsFont(fontationsTypeface);
89             SkFont freetypeFont(freetypeTypeface);
90 
91             auto configureFont = [upem](SkFont& font) {
92                 font.setSize(upem);
93                 font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
94                 font.setSubpixel(true);
95                 font.setHinting(SkFontHinting::kNone);
96             };
97 
98             configureFont(fontationsFont);
99             configureFont(freetypeFont);
100 
101             for (const auto& sampleLang : testSet.langSamples) {
102                 sk_sp<const SkTextBlob> fontationsTextBlob =
103                         makeTextBlobWithFontAndText(fontationsFont, sampleLang.sampleLong);
104                 sk_sp<const SkTextBlob> freetypeTextBlob =
105                         makeTextBlobWithFontAndText(freetypeFont, sampleLang.sampleLong);
106 
107                 REPORTER_ASSERT(reporter,
108                                 textBlobsAllPathsEqual(
109                                         fontationsTextBlob, freetypeTextBlob, testSet.fontName),
110                                 "paths not equal for %s",
111                                 testSet.fontName.c_str());
112             }
113             numTestsExecuted++;
114         }
115         REPORTER_ASSERT(reporter,
116                         numTestsExecuted > 0,
117                         "Error: FontationsFtComparison did not run any tests, missing third-party "
118                         "googlefonts_testdata resource? See bin/fetch-fonts-testdata.");
119     }
120 
121 private:
makeTextBlobWithFontAndText(const SkFont & font,const SkString & testPhrase)122     sk_sp<const SkTextBlob> makeTextBlobWithFontAndText(const SkFont& font,
123                                                         const SkString& testPhrase) {
124         std::unique_ptr<SkShaper> shaper = SkShaper::Make();
125         SkTextBlobBuilderRunHandler textBlobBuilder(testPhrase.c_str(), {0, 0});
126         SkString fam;
127         font.getTypeface()->getFamilyName(&fam);
128         SkASSERT_RELEASE(testPhrase.size());
129         // Reserve enough space for the test phrase to fit into one line.
130         SkScalar shapeWidth = font.getTypeface()->getUnitsPerEm() * testPhrase.size() * 1.10f;
131         shaper->shape(
132                 testPhrase.c_str(), testPhrase.size(), font, true, shapeWidth, &textBlobBuilder);
133         return textBlobBuilder.makeBlob();
134     }
135 
136     TestFontDataProvider fTestDataIterator;
137 };
138 
139 }  // namespace
140 
DEF_TEST(Fontations_NotoSans,reporter)141 DEF_TEST(Fontations_NotoSans, reporter) {
142     FontationsFtComparison("Noto Sans",
143                            "en_Latn|es_Latn|pt_Latn|id_Latn|ru_Cyrl|fr_Latn|tr_Latn|vi_Latn|de_"
144                            "Latn|it_Latn|pl_Latn|nl_Latn|uk_Cyrl|gl_Latn|ro_Latn|cs_Latn|hu_Latn|"
145                            "el_Grek|se_Latn|da_Latn|bg_Latn|sk_Latn|fi_Latn|bs_Latn|ca_Latn|no_"
146                            "Latn|sr_Latn|sr_Cyrl|lt_Latn|hr_Latn|sl_Latn|uz_Latn|uz_Cyrl|lv_Latn|"
147                            "et_Latn|az_Latn|az_Cyrl|la_Latn|tg_Latn|tg_Cyrl|sw_Latn|mn_Cyrl|kk_"
148                            "Latn|kk_Cyrl|sq_Latn|af_Latn|ha_Latn|ky_Cyrl")
149             .assertAllPathsEqual(reporter);
150 }
151 
DEF_TEST(Fontations_NotoSans_Deva,reporter)152 DEF_TEST(Fontations_NotoSans_Deva, reporter) {
153     FontationsFtComparison("Noto Sans Devanagari", "hi_Deva|mr_Deva").assertAllPathsEqual(reporter);
154 }
155 
DEF_TEST(Fontations_NotoSans_ar_Arab,reporter)156 DEF_TEST(Fontations_NotoSans_ar_Arab, reporter) {
157     FontationsFtComparison("Noto Sans Arabic", "ar_Arab|uz_Arab|kk_Arab|ky_Arab")
158             .assertAllPathsEqual(reporter);
159 }
160 
DEF_TEST(Fontations_NotoSans_Beng,reporter)161 DEF_TEST(Fontations_NotoSans_Beng, reporter) {
162     FontationsFtComparison("Noto Sans Bengali", "bn_Beng").assertAllPathsEqual(reporter);
163 }
164 
DEF_TEST(Fontations_NotoSans_Jpan,reporter)165 DEF_TEST(Fontations_NotoSans_Jpan, reporter) {
166     FontationsFtComparison("Noto Sans JP", "ja_Jpan").assertAllPathsEqual(reporter);
167 }
168 
DEF_TEST(Fontations_NotoSans_Thai,reporter)169 DEF_TEST(Fontations_NotoSans_Thai, reporter) {
170     FontationsFtComparison("Noto Sans Thai", "th_Thai").assertAllPathsEqual(reporter);
171 }
172 
DEF_TEST(Fontations_NotoSans_Hans,reporter)173 DEF_TEST(Fontations_NotoSans_Hans, reporter) {
174     FontationsFtComparison("Noto Sans SC", "zh_Hans").assertAllPathsEqual(reporter);
175 }
176 
DEF_TEST(Fontations_NotoSans_Hant,reporter)177 DEF_TEST(Fontations_NotoSans_Hant, reporter) {
178     FontationsFtComparison("Noto Sans TC", "zh_Hant").assertAllPathsEqual(reporter);
179 }
180 
DEF_TEST(Fontations_NotoSans_Kore,reporter)181 DEF_TEST(Fontations_NotoSans_Kore, reporter) {
182     FontationsFtComparison("Noto Sans KR", "ko_Kore").assertAllPathsEqual(reporter);
183 }
184 
DEF_TEST(Fontations_NotoSans_Taml,reporter)185 DEF_TEST(Fontations_NotoSans_Taml, reporter) {
186     FontationsFtComparison("Noto Sans Tamil", "ta_Taml").assertAllPathsEqual(reporter);
187 }
188 
DEF_TEST(Fontations_NotoSans_Newa,reporter)189 DEF_TEST(Fontations_NotoSans_Newa, reporter) {
190     FontationsFtComparison("Noto Sans Newa", "new_Newa").assertAllPathsEqual(reporter);
191 }
192 
DEF_TEST(Fontations_NotoSans_Knda,reporter)193 DEF_TEST(Fontations_NotoSans_Knda, reporter) {
194     FontationsFtComparison("Noto Sans Kannada", "kn_Knda").assertAllPathsEqual(reporter);
195 }
196 
DEF_TEST(Fontations_NotoSans_Tglg,reporter)197 DEF_TEST(Fontations_NotoSans_Tglg, reporter) {
198     FontationsFtComparison("Noto Sans Tagalog", "fil_Tglg").assertAllPathsEqual(reporter);
199 }
200 
DEF_TEST(Fontations_NotoSans_Telu,reporter)201 DEF_TEST(Fontations_NotoSans_Telu, reporter) {
202     FontationsFtComparison("Noto Sans Telugu", "te_Telu").assertAllPathsEqual(reporter);
203 }
204 
DEF_TEST(Fontations_NotoSans_Gujr,reporter)205 DEF_TEST(Fontations_NotoSans_Gujr, reporter) {
206     FontationsFtComparison("Noto Sans Gujarati", "gu_Gujr").assertAllPathsEqual(reporter);
207 }
208 
DEF_TEST(Fontations_NotoSans_Geor,reporter)209 DEF_TEST(Fontations_NotoSans_Geor, reporter) {
210     FontationsFtComparison("Noto Sans Georgian", "ka_Geor").assertAllPathsEqual(reporter);
211 }
212 
DEF_TEST(Fontations_NotoSans_Mlym,reporter)213 DEF_TEST(Fontations_NotoSans_Mlym, reporter) {
214     FontationsFtComparison("Noto Sans Malayalam", "ml_Mlym").assertAllPathsEqual(reporter);
215 }
216 
DEF_TEST(Fontations_NotoSans_Khmr,reporter)217 DEF_TEST(Fontations_NotoSans_Khmr, reporter) {
218     FontationsFtComparison("Noto Sans Khmer", "km_Khmr").assertAllPathsEqual(reporter);
219 }
220 
DEF_TEST(Fontations_NotoSans_Sinh,reporter)221 DEF_TEST(Fontations_NotoSans_Sinh, reporter) {
222     FontationsFtComparison("Noto Sans Sinhala", "si_Sinh").assertAllPathsEqual(reporter);
223 }
224 
DEF_TEST(Fontations_NotoSans_Mymr,reporter)225 DEF_TEST(Fontations_NotoSans_Mymr, reporter) {
226     FontationsFtComparison("Noto Sans Myanmar", "my_Mymr").assertAllPathsEqual(reporter);
227 }
228 
DEF_TEST(Fontations_NotoSans_Java,reporter)229 DEF_TEST(Fontations_NotoSans_Java, reporter) {
230     FontationsFtComparison("Noto Sans Javanese", "jv_Java").assertAllPathsEqual(reporter);
231 }
232 
DEF_TEST(Fontations_NotoSans_Mong,reporter)233 DEF_TEST(Fontations_NotoSans_Mong, reporter) {
234     FontationsFtComparison("Noto Sans Mongolian", "mn_Mong").assertAllPathsEqual(reporter);
235 }
236 
DEF_TEST(Fontations_NotoSans_Armn,reporter)237 DEF_TEST(Fontations_NotoSans_Armn, reporter) {
238     FontationsFtComparison("Noto Sans Armenian", "hy_Armn").assertAllPathsEqual(reporter);
239 }
240 
DEF_TEST(Fontations_NotoSans_Elba,reporter)241 DEF_TEST(Fontations_NotoSans_Elba, reporter) {
242     FontationsFtComparison("Noto Sans Elbasan", "sq_Elba").assertAllPathsEqual(reporter);
243 }
244 
DEF_TEST(Fontations_NotoSans_Vith,reporter)245 DEF_TEST(Fontations_NotoSans_Vith, reporter) {
246     FontationsFtComparison("Noto Sans Vithkuqi", "sq_Vith").assertAllPathsEqual(reporter);
247 }
248 
DEF_TEST(Fontations_NotoSans_Guru,reporter)249 DEF_TEST(Fontations_NotoSans_Guru, reporter) {
250     FontationsFtComparison("Noto Sans Gurmukhi", "pa_Guru").assertAllPathsEqual(reporter);
251 }
252