Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
Shaper.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2022 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
11#include "tests/Test.h"
12#include "tools/ToolUtils.h"
14
15using namespace skottie;
16
17DEF_TEST(Skottie_Shaper_Clusters, r) {
18 const SkString text("Foo \rbar \rBaz.");
19
20 auto check_clusters = [](skiatest::Reporter* r, const SkString& text, Shaper::Flags flags,
21 const std::vector<size_t>& expected_clusters) {
22 const Shaper::TextDesc desc = {
24 18,
25 0, 18,
26 18,
27 0,
28 0,
30 Shaper::VAlign::kTop,
31 Shaper::ResizePolicy::kNone,
32 Shaper::LinebreakPolicy::kParagraph,
33 Shaper::Direction::kLTR,
34 Shaper::Capitalization::kNone,
35 0,
36 flags,
37 nullptr,
38 };
39 const auto result =
42 REPORTER_ASSERT(r, !result.fFragments.empty());
43
44 size_t i = 0;
45 for (const auto& frag : result.fFragments) {
46 const auto& glyphs = frag.fGlyphs;
47
49 REPORTER_ASSERT(r, glyphs.fClusters.size() == glyphs.fGlyphIDs.size());
50 }
51
52 for (const auto& utf_cluster : glyphs.fClusters) {
53 REPORTER_ASSERT(r, i < expected_clusters.size());
54 REPORTER_ASSERT(r, utf_cluster == expected_clusters[i++]);
55 }
56 }
57
58 REPORTER_ASSERT(r, i == expected_clusters.size());
59 };
60
61 check_clusters(r, text, Shaper::kNone, {});
62 check_clusters(r, text, Shaper::kFragmentGlyphs, {});
63 check_clusters(r, text, Shaper::kClusters,
64 {0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13});
66 {0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13});
67}
68
69DEF_TEST(Skottie_Shaper_HAlign, reporter) {
71 REPORTER_ASSERT(reporter, typeface);
72
73 static constexpr struct {
74 SkScalar text_size,
75 tolerance;
76 } kTestSizes[] = {
77 // These gross tolerances are required for the test to pass on NativeFonts bots.
78 // Might be worth investigating why we need so much slack.
79 { 5, 2.0f },
80 { 10, 2.0f },
81 { 15, 2.4f },
82 { 25, 4.4f },
83 };
84
85 static constexpr struct {
87 SkScalar l_selector,
88 r_selector;
89 } kTestAligns[] = {
90 { SkTextUtils:: kLeft_Align, 0.0f, 1.0f },
91 { SkTextUtils::kCenter_Align, 0.5f, 0.5f },
92 { SkTextUtils:: kRight_Align, 1.0f, 0.0f },
93 };
94
95 const SkString text("Foo, bar.\rBaz.");
96 const SkPoint text_point = SkPoint::Make(100, 100);
97
98 for (const auto& tsize : kTestSizes) {
99 for (const auto& talign : kTestAligns) {
100 const skottie::Shaper::TextDesc desc = {
101 typeface,
102 tsize.text_size,
103 0, tsize.text_size,
104 tsize.text_size,
105 0,
106 0,
107 talign.align,
108 Shaper::VAlign::kTopBaseline,
109 Shaper::ResizePolicy::kNone,
110 Shaper::LinebreakPolicy::kExplicit,
111 Shaper::Direction::kLTR,
112 Shaper::Capitalization::kNone,
113 0,
114 0,
115 nullptr
116 };
117
118 const auto shape_result =
120 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
121 REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
122
123 const auto shape_bounds = shape_result.computeVisualBounds();
124 REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
125
126 const auto expected_l = text_point.x() - shape_bounds.width() * talign.l_selector;
128 std::fabs(shape_bounds.left() - expected_l) < tsize.tolerance,
129 "%f %f %f %f %d", shape_bounds.left(), expected_l, tsize.tolerance,
130 tsize.text_size, talign.align);
131
132 const auto expected_r = text_point.x() + shape_bounds.width() * talign.r_selector;
134 std::fabs(shape_bounds.right() - expected_r) < tsize.tolerance,
135 "%f %f %f %f %d", shape_bounds.right(), expected_r, tsize.tolerance,
136 tsize.text_size, talign.align);
137
138 }
139 }
140}
141
142DEF_TEST(Skottie_Shaper_VAlign, reporter) {
144 REPORTER_ASSERT(reporter, typeface);
145
146 static constexpr struct {
147 SkScalar text_size,
148 tolerance;
149 } kTestSizes[] = {
150 // These gross tolerances are required for the test to pass on NativeFonts bots.
151 // Might be worth investigating why we need so much slack.
152 { 5, 2.0f },
153 { 10, 4.0f },
154 { 15, 5.5f },
155 { 25, 8.0f },
156 };
157
158 struct {
160 SkScalar topFactor;
161 } kTestAligns[] = {
164 // TODO: any way to test kTopBaseline?
165 };
166
167 const SkString text("Foo, bar.\rBaz.");
168 const auto text_box = SkRect::MakeXYWH(100, 100, 1000, 1000); // large-enough to avoid breaks.
169
170
171 for (const auto& tsize : kTestSizes) {
172 for (const auto& talign : kTestAligns) {
173 const skottie::Shaper::TextDesc desc = {
174 typeface,
175 tsize.text_size,
176 0, tsize.text_size,
177 tsize.text_size,
178 0,
179 0,
181 talign.align,
182 Shaper::ResizePolicy::kNone,
183 Shaper::LinebreakPolicy::kParagraph,
184 Shaper::Direction::kLTR,
185 Shaper::Capitalization::kNone,
186 0,
187 0,
188 nullptr
189 };
190
191 const auto shape_result = Shaper::Shape(text, desc, text_box, ToolUtils::TestFontMgr(), SkShapers::BestAvailable());
192 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
193 REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
194
195 const auto shape_bounds = shape_result.computeVisualBounds();
196 REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
197
198 const auto v_diff = text_box.height() - shape_bounds.height();
199
200 const auto expected_t = text_box.top() + v_diff * talign.topFactor;
202 std::fabs(shape_bounds.top() - expected_t) < tsize.tolerance,
203 "%f %f %f %f %u", shape_bounds.top(), expected_t, tsize.tolerance,
204 tsize.text_size, SkToU32(talign.align));
205
206 const auto expected_b = text_box.bottom() - v_diff * (1 - talign.topFactor);
208 std::fabs(shape_bounds.bottom() - expected_b) < tsize.tolerance,
209 "%f %f %f %f %u", shape_bounds.bottom(), expected_b, tsize.tolerance,
210 tsize.text_size, SkToU32(talign.align));
211 }
212 }
213}
214
215DEF_TEST(Skottie_Shaper_FragmentGlyphs, reporter) {
218 18,
219 0, 18,
220 18,
221 0,
222 0,
224 Shaper::VAlign::kTop,
225 Shaper::ResizePolicy::kNone,
226 Shaper::LinebreakPolicy::kParagraph,
227 Shaper::Direction::kLTR,
228 Shaper::Capitalization::kNone,
229 0,
230 0,
231 nullptr
232 };
233
234 const SkString text("Foo bar baz");
235 const auto text_box = SkRect::MakeWH(100, 100);
236
237 {
238 const auto shape_result = Shaper::Shape(text, desc, text_box, ToolUtils::TestFontMgr(), SkShapers::BestAvailable());
239 // Default/consolidated mode => single blob result.
240 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
241 SkASSERT(!shape_result.fFragments.empty());
242 REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
243 }
244
245 {
246 desc.fFlags = Shaper::Flags::kFragmentGlyphs;
247 const auto shape_result =
249 // Fragmented mode => one blob per glyph.
250 const size_t expectedSize = text.size();
251 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == expectedSize);
252 SkASSERT(!shape_result.fFragments.empty());
253 for (size_t i = 0; i < expectedSize; ++i) {
254 REPORTER_ASSERT(reporter, !shape_result.fFragments[i].fGlyphs.fRuns.empty());
255 }
256 }
257}
258
259#if defined(SK_SHAPER_HARFBUZZ_AVAILABLE) && !defined(SK_BUILD_FOR_WIN)
260
261DEF_TEST(Skottie_Shaper_ExplicitFontMgr, reporter) {
262 class CountingFontMgr : public SkFontMgr {
263 public:
264 size_t fallbackCount() const { return fFallbackCount; }
265
266 protected:
267 int onCountFamilies() const override { return 0; }
268 void onGetFamilyName(int index, SkString* familyName) const override {
269 SkDEBUGFAIL("onGetFamilyName called with bad index");
270 }
271 sk_sp<SkFontStyleSet> onCreateStyleSet(int index) const override {
272 SkDEBUGFAIL("onCreateStyleSet called with bad index");
273 return nullptr;
274 }
275 sk_sp<SkFontStyleSet> onMatchFamily(const char[]) const override {
277 }
278
279 sk_sp<SkTypeface> onMatchFamilyStyle(const char[], const SkFontStyle&) const override {
280 return nullptr;
281 }
282 sk_sp<SkTypeface> onMatchFamilyStyleCharacter(const char familyName[],
283 const SkFontStyle& style,
284 const char* bcp47[],
285 int bcp47Count,
286 SkUnichar character) const override {
287 fFallbackCount++;
288 return nullptr;
289 }
290
291 sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int) const override {
292 return nullptr;
293 }
294 sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>, int) const override {
295 return nullptr;
296 }
297 sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
298 const SkFontArguments&) const override {
299 return nullptr;
300 }
301 sk_sp<SkTypeface> onMakeFromFile(const char[], int) const override {
302 return nullptr;
303 }
304 sk_sp<SkTypeface> onLegacyMakeTypeface(const char [], SkFontStyle) const override {
305 return nullptr;
306 }
307 private:
308 mutable size_t fFallbackCount = 0;
309 };
310
311 auto fontmgr = sk_make_sp<CountingFontMgr>();
312
315 18,
316 0, 18,
317 18,
318 0,
319 0,
321 Shaper::VAlign::kTop,
322 Shaper::ResizePolicy::kNone,
323 Shaper::LinebreakPolicy::kParagraph,
324 Shaper::Direction::kLTR,
325 Shaper::Capitalization::kNone,
326 0,
327 0,
328 nullptr
329 };
330
331 const auto text_box = SkRect::MakeWH(100, 100);
332
333 {
334 const auto shape_result = Shaper::Shape(SkString("foo bar"), desc, text_box, fontmgr, SkShapers::BestAvailable());
335
336 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
337 REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
338 REPORTER_ASSERT(reporter, fontmgr->fallbackCount() == 0ul);
339 REPORTER_ASSERT(reporter, shape_result.fMissingGlyphCount == 0);
340 }
341
342 {
343 // An unassigned codepoint should trigger fallback.
344 const auto shape_result = skottie::Shaper::Shape(SkString("foo\U000DFFFFbar"),
345 desc, text_box, fontmgr, SkShapers::BestAvailable());
346
347 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
348 REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
349 REPORTER_ASSERT(reporter, fontmgr->fallbackCount() == 1ul);
350 REPORTER_ASSERT(reporter, shape_result.fMissingGlyphCount == 1ul);
351 }
352}
353
354#endif
355
reporter
uint16_t glyphs[5]
#define SkDEBUGFAIL(message)
Definition SkAssert.h:118
#define SkASSERT(cond)
Definition SkAssert.h:116
constexpr uint32_t SkToU32(S x)
Definition SkTo.h:26
int32_t SkUnichar
Definition SkTypes.h:175
#define DEF_TEST(name, reporter)
Definition Test.h:312
#define REPORTER_ASSERT(r, cond,...)
Definition Test.h:286
virtual void onGetFamilyName(int index, SkString *familyName) const =0
virtual sk_sp< SkTypeface > onMakeFromData(sk_sp< SkData >, int ttcIndex) const =0
virtual sk_sp< SkFontStyleSet > onCreateStyleSet(int index) const =0
virtual sk_sp< SkTypeface > onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle &, const char *bcp47[], int bcp47Count, SkUnichar character) const =0
virtual sk_sp< SkFontStyleSet > onMatchFamily(const char familyName[]) const =0
virtual sk_sp< SkTypeface > onMakeFromStreamIndex(std::unique_ptr< SkStreamAsset >, int ttcIndex) const =0
virtual sk_sp< SkTypeface > onMakeFromStreamArgs(std::unique_ptr< SkStreamAsset >, const SkFontArguments &) const =0
virtual sk_sp< SkTypeface > onMatchFamilyStyle(const char familyName[], const SkFontStyle &) const =0
virtual sk_sp< SkTypeface > onLegacyMakeTypeface(const char familyName[], SkFontStyle) const =0
virtual sk_sp< SkTypeface > onMakeFromFile(const char path[], int ttcIndex) const =0
virtual int onCountFamilies() const =0
static sk_sp< SkFontStyleSet > CreateEmpty()
Definition SkFontMgr.cpp:35
static Result Shape(const SkString &text, const TextDesc &desc, const SkPoint &point, const sk_sp< SkFontMgr > &, const sk_sp< SkShapers::Factory > &)
float SkScalar
Definition extension.cpp:12
FlutterSemanticsFlag flags
GAsyncResult * result
std::u16string text
sk_sp< Factory > BestAvailable()
sk_sp< SkTypeface > DefaultPortableTypeface()
sk_sp< SkTypeface > CreatePortableTypeface(const char *name, SkFontStyle style)
sk_sp< SkTypeface > DefaultTypeface()
sk_sp< SkFontMgr > TestFontMgr()
static constexpr SkPoint Make(float x, float y)
constexpr float x() const
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition SkRect.h:659
static constexpr SkRect MakeWH(float w, float h)
Definition SkRect.h:609