1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h"
6
7 #include <unicode/uscript.h>
8
9 #include "base/stl_util.h"
10 #include "base/test/task_environment.h"
11 #include "build/build_config.h"
12 #include "testing/gmock/include/gmock/gmock.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14 #include "third_party/blink/renderer/platform/fonts/font.h"
15 #include "third_party/blink/renderer/platform/fonts/font_cache.h"
16 #include "third_party/blink/renderer/platform/fonts/font_test_utilities.h"
17 #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h"
18 #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h"
19 #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h"
20 #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h"
21 #include "third_party/blink/renderer/platform/testing/font_test_helpers.h"
22 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
23 #include "third_party/blink/renderer/platform/text/text_break_iterator.h"
24 #include "third_party/blink/renderer/platform/text/text_run.h"
25 #include "third_party/blink/renderer/platform/web_test_support.h"
26 #include "third_party/blink/renderer/platform/wtf/vector.h"
27
28 using testing::ElementsAre;
29
30 namespace blink {
31
32 namespace {
33
TestInfo(const scoped_refptr<ShapeResult> & result)34 ShapeResultTestInfo* TestInfo(const scoped_refptr<ShapeResult>& result) {
35 return static_cast<ShapeResultTestInfo*>(result.get());
36 }
37
38 // Test helper to compare all RunInfo with the expected array.
39 struct ShapeResultRunData {
40 unsigned start_index;
41 unsigned num_characters;
42 unsigned num_glyphs;
43 hb_script_t script;
44
Getblink::__anonecef9ee50111::ShapeResultRunData45 static Vector<ShapeResultRunData> Get(
46 const scoped_refptr<ShapeResult>& result) {
47 const ShapeResultTestInfo* test_info = TestInfo(result);
48 const unsigned num_runs = test_info->NumberOfRunsForTesting();
49 Vector<ShapeResultRunData> runs(num_runs);
50 for (unsigned i = 0; i < num_runs; i++) {
51 ShapeResultRunData& run = runs[i];
52 test_info->RunInfoForTesting(i, run.start_index, run.num_characters,
53 run.num_glyphs, run.script);
54 }
55 return runs;
56 }
57 };
58
operator ==(const ShapeResultRunData & x,const ShapeResultRunData & y)59 bool operator==(const ShapeResultRunData& x, const ShapeResultRunData& y) {
60 return x.start_index == y.start_index &&
61 x.num_characters == y.num_characters && x.num_glyphs == y.num_glyphs &&
62 x.script == y.script;
63 }
64
operator <<(std::ostream & output,const ShapeResultRunData & x)65 void operator<<(std::ostream& output, const ShapeResultRunData& x) {
66 output << "{ start_index=" << x.start_index
67 << ", num_characters=" << x.num_characters
68 << ", num_glyphs=" << x.num_glyphs << ", script=" << x.script << " }";
69 }
70
71 // Create a string of the specified length, filled with |ch|.
CreateStringOf(UChar ch,unsigned length)72 String CreateStringOf(UChar ch, unsigned length) {
73 UChar* data;
74 String string(StringImpl::CreateUninitialized(length, data));
75 string.Fill(ch);
76 return string;
77 }
78
79 } // namespace
80
81 class HarfBuzzShaperTest : public testing::Test {
82 protected:
SetUp()83 void SetUp() override {
84 font_description.SetComputedSize(12.0);
85 font = Font(font_description);
86 }
87
TearDown()88 void TearDown() override {}
89
SelectDevanagariFont()90 void SelectDevanagariFont() {
91 FontFamily devanagari_family;
92 // Windows 10
93 devanagari_family.SetFamily("Nirmala UI");
94 // Windows 7
95 devanagari_family.AppendFamily("Mangal");
96 // Linux
97 devanagari_family.AppendFamily("Lohit Devanagari");
98 // Mac
99 devanagari_family.AppendFamily("ITF Devanagari");
100
101 font_description.SetFamily(devanagari_family);
102 font = Font(font_description);
103 }
104
CreateAhem(float size)105 Font CreateAhem(float size) {
106 FontDescription::VariantLigatures ligatures;
107 return blink::test::CreateTestFont(
108 "Ahem", blink::test::PlatformTestDataPath("Ahem.woff"), size,
109 &ligatures);
110 }
111
SplitRun(scoped_refptr<ShapeResult> shape_result,unsigned offset)112 scoped_refptr<ShapeResult> SplitRun(scoped_refptr<ShapeResult> shape_result,
113 unsigned offset) {
114 unsigned length = shape_result->NumCharacters();
115 scoped_refptr<ShapeResult> run2 = shape_result->SubRange(offset, length);
116 shape_result = shape_result->SubRange(0, offset);
117 run2->CopyRange(offset, length, shape_result.get());
118 return shape_result;
119 }
120
CreateMissingRunResult(TextDirection direction)121 scoped_refptr<ShapeResult> CreateMissingRunResult(TextDirection direction) {
122 scoped_refptr<ShapeResult> result =
123 ShapeResult::Create(&font, 2, 8, direction);
124 result->InsertRunForTesting(2, 1, direction, {0});
125 result->InsertRunForTesting(3, 3, direction, {0, 1});
126 // The character index 6 and 7 is missing.
127 result->InsertRunForTesting(8, 2, direction, {0});
128 return result;
129 }
130
131 base::test::TaskEnvironment task_environment_;
132 FontCachePurgePreventer font_cache_purge_preventer;
133 FontDescription font_description;
134 Font font;
135 unsigned start_index = 0;
136 unsigned num_characters = 0;
137 unsigned num_glyphs = 0;
138 hb_script_t script = HB_SCRIPT_INVALID;
139 };
140
141 class ScopedSubpixelOverride {
142 public:
ScopedSubpixelOverride(bool b)143 ScopedSubpixelOverride(bool b) {
144 prev_web_test_ = WebTestSupport::IsRunningWebTest();
145 prev_subpixel_allowed_ =
146 WebTestSupport::IsTextSubpixelPositioningAllowedForTest();
147 prev_antialias_ = WebTestSupport::IsFontAntialiasingEnabledForTest();
148 prev_fd_subpixel_ = FontDescription::SubpixelPositioning();
149
150 // This is required for all WebTestSupport settings to have effects.
151 WebTestSupport::SetIsRunningWebTest(true);
152
153 if (b) {
154 // Allow subpixel positioning.
155 WebTestSupport::SetTextSubpixelPositioningAllowedForTest(true);
156
157 // Now, enable subpixel positioning in platform-specific ways.
158
159 // Mac always enables subpixel positioning.
160
161 // On Windows, subpixel positioning also requires antialiasing.
162 WebTestSupport::SetFontAntialiasingEnabledForTest(true);
163
164 // On platforms other than Windows and Mac this needs to be set as
165 // well.
166 FontDescription::SetSubpixelPositioning(true);
167 } else {
168 // Explicitly disallow all subpixel positioning.
169 WebTestSupport::SetTextSubpixelPositioningAllowedForTest(false);
170 }
171 }
~ScopedSubpixelOverride()172 ~ScopedSubpixelOverride() {
173 FontDescription::SetSubpixelPositioning(prev_fd_subpixel_);
174 WebTestSupport::SetFontAntialiasingEnabledForTest(prev_antialias_);
175 WebTestSupport::SetTextSubpixelPositioningAllowedForTest(
176 prev_subpixel_allowed_);
177 WebTestSupport::SetIsRunningWebTest(prev_web_test_);
178
179 // Fonts cached with a different subpixel positioning state are not
180 // automatically invalidated and need to be cleared between test
181 // runs.
182 FontCache::GetFontCache()->Invalidate();
183 }
184
185 private:
186 bool prev_web_test_;
187 bool prev_subpixel_allowed_;
188 bool prev_antialias_;
189 bool prev_fd_subpixel_;
190 };
191
192 class ShapeParameterTest : public HarfBuzzShaperTest,
193 public testing::WithParamInterface<TextDirection> {
194 protected:
ShapeWithParameter(HarfBuzzShaper * shaper)195 scoped_refptr<ShapeResult> ShapeWithParameter(HarfBuzzShaper* shaper) {
196 TextDirection direction = GetParam();
197 return shaper->Shape(&font, direction);
198 }
199 };
200
201 INSTANTIATE_TEST_SUITE_P(HarfBuzzShaperTest,
202 ShapeParameterTest,
203 testing::Values(TextDirection::kLtr,
204 TextDirection::kRtl));
205
TEST_F(HarfBuzzShaperTest,MutableUnique)206 TEST_F(HarfBuzzShaperTest, MutableUnique) {
207 scoped_refptr<ShapeResult> result =
208 ShapeResult::Create(&font, 0, 0, TextDirection::kLtr);
209 EXPECT_TRUE(result->HasOneRef());
210
211 // At this point, |result| has only one ref count.
212 scoped_refptr<ShapeResult> result2 = result->MutableUnique();
213 EXPECT_EQ(result.get(), result2.get());
214 EXPECT_FALSE(result2->HasOneRef());
215
216 // Since |result| has 2 ref counts, it should return a clone.
217 scoped_refptr<ShapeResult> result3 = result->MutableUnique();
218 EXPECT_NE(result.get(), result3.get());
219 EXPECT_TRUE(result3->HasOneRef());
220 }
221
TEST_F(HarfBuzzShaperTest,ResolveCandidateRunsLatin)222 TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsLatin) {
223 String latin_common = To16Bit("ABC DEF.", 8);
224 HarfBuzzShaper shaper(latin_common);
225 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
226
227 EXPECT_EQ(1u, TestInfo(result)->NumberOfRunsForTesting());
228 ASSERT_TRUE(
229 TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script));
230 EXPECT_EQ(0u, start_index);
231 EXPECT_EQ(8u, num_glyphs);
232 EXPECT_EQ(HB_SCRIPT_LATIN, script);
233 }
234
TEST_F(HarfBuzzShaperTest,ResolveCandidateRunsLeadingCommon)235 TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsLeadingCommon) {
236 String leading_common = To16Bit("... test", 8);
237 HarfBuzzShaper shaper(leading_common);
238 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
239
240 EXPECT_EQ(1u, TestInfo(result)->NumberOfRunsForTesting());
241 ASSERT_TRUE(
242 TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script));
243 EXPECT_EQ(0u, start_index);
244 EXPECT_EQ(8u, num_glyphs);
245 EXPECT_EQ(HB_SCRIPT_LATIN, script);
246 }
247
TEST_F(HarfBuzzShaperTest,ResolveCandidateRunsUnicodeVariants)248 TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsUnicodeVariants) {
249 struct {
250 const char* name;
251 UChar string[4];
252 unsigned length;
253 hb_script_t script;
254 } testlist[] = {
255 {"Standard Variants text style", {0x30, 0xFE0E}, 2, HB_SCRIPT_COMMON},
256 {"Standard Variants emoji style", {0x203C, 0xFE0F}, 2, HB_SCRIPT_COMMON},
257 {"Standard Variants of Ideograph", {0x4FAE, 0xFE00}, 2, HB_SCRIPT_HAN},
258 {"Ideographic Variants", {0x3402, 0xDB40, 0xDD00}, 3, HB_SCRIPT_HAN},
259 {"Not-defined Variants", {0x41, 0xDB40, 0xDDEF}, 3, HB_SCRIPT_LATIN},
260 };
261 for (auto& test : testlist) {
262 HarfBuzzShaper shaper(test.string);
263 scoped_refptr<ShapeResult> result =
264 shaper.Shape(&font, TextDirection::kLtr);
265
266 EXPECT_EQ(1u, TestInfo(result)->NumberOfRunsForTesting()) << test.name;
267 ASSERT_TRUE(
268 TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script))
269 << test.name;
270 EXPECT_EQ(0u, start_index) << test.name;
271 if (num_glyphs == 2) {
272 // If the specified VS is not in the font, it's mapped to .notdef.
273 // then hb_ot_hide_default_ignorables() swaps it to a space with zero-advance.
274 // http://lists.freedesktop.org/archives/harfbuzz/2015-May/004888.html
275 EXPECT_EQ(TestInfo(result)->FontDataForTesting(0)->SpaceGlyph(),
276 TestInfo(result)->GlyphForTesting(0, 1))
277 << test.name;
278 EXPECT_EQ(0.f, TestInfo(result)->AdvanceForTesting(0, 1)) << test.name;
279 } else {
280 EXPECT_EQ(1u, num_glyphs) << test.name;
281 }
282 EXPECT_EQ(test.script, script) << test.name;
283 }
284 }
285
TEST_F(HarfBuzzShaperTest,ResolveCandidateRunsDevanagariCommon)286 TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsDevanagariCommon) {
287 SelectDevanagariFont();
288 UChar devanagari_common_string[] = {0x915, 0x94d, 0x930, 0x28, 0x20, 0x29};
289 String devanagari_common_latin(devanagari_common_string, 6);
290 HarfBuzzShaper shaper(devanagari_common_latin);
291 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
292
293 // Depending on font coverage we cannot assume that all text is in one
294 // run, the parenthesis U+0029 may be in a separate font.
295 EXPECT_GT(TestInfo(result)->NumberOfRunsForTesting(), 0u);
296 EXPECT_LE(TestInfo(result)->NumberOfRunsForTesting(), 2u);
297
298 // Common part of the run must be resolved as Devanagari.
299 for (unsigned i = 0; i < TestInfo(result)->NumberOfRunsForTesting(); ++i) {
300 ASSERT_TRUE(TestInfo(result)->RunInfoForTesting(i, start_index, num_glyphs,
301 script));
302 EXPECT_EQ(HB_SCRIPT_DEVANAGARI, script);
303 }
304 }
305
TEST_F(HarfBuzzShaperTest,ResolveCandidateRunsDevanagariCommonLatinCommon)306 TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsDevanagariCommonLatinCommon) {
307 SelectDevanagariFont();
308 UChar devanagari_common_latin_string[] = {0x915, 0x94d, 0x930, 0x20,
309 0x61, 0x62, 0x2E};
310 HarfBuzzShaper shaper(String(devanagari_common_latin_string, 7));
311 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
312
313 // Ensure that there are only two scripts, Devanagari first, then Latin.
314 EXPECT_GT(TestInfo(result)->NumberOfRunsForTesting(), 0u);
315 EXPECT_LE(TestInfo(result)->NumberOfRunsForTesting(), 3u);
316
317 bool finished_devanagari = false;
318 for (unsigned i = 0; i < TestInfo(result)->NumberOfRunsForTesting(); ++i) {
319 ASSERT_TRUE(TestInfo(result)->RunInfoForTesting(i, start_index, num_glyphs,
320 script));
321 finished_devanagari = finished_devanagari | (script == HB_SCRIPT_LATIN);
322 EXPECT_EQ(script,
323 finished_devanagari ? HB_SCRIPT_LATIN : HB_SCRIPT_DEVANAGARI);
324 }
325 }
326
TEST_F(HarfBuzzShaperTest,ResolveCandidateRunsArabicThaiHanLatin)327 TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsArabicThaiHanLatin) {
328 UChar mixed_string[] = {0x628, 0x64A, 0x629, 0xE20, 0x65E5, 0x62};
329 HarfBuzzShaper shaper(String(mixed_string, 6));
330 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
331
332 EXPECT_EQ(4u, TestInfo(result)->NumberOfRunsForTesting());
333 ASSERT_TRUE(
334 TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script));
335 EXPECT_EQ(0u, start_index);
336 EXPECT_EQ(3u, num_glyphs);
337 EXPECT_EQ(HB_SCRIPT_ARABIC, script);
338
339 ASSERT_TRUE(
340 TestInfo(result)->RunInfoForTesting(1, start_index, num_glyphs, script));
341 EXPECT_EQ(3u, start_index);
342 EXPECT_EQ(1u, num_glyphs);
343 EXPECT_EQ(HB_SCRIPT_THAI, script);
344
345 ASSERT_TRUE(
346 TestInfo(result)->RunInfoForTesting(2, start_index, num_glyphs, script));
347 EXPECT_EQ(4u, start_index);
348 EXPECT_EQ(1u, num_glyphs);
349 EXPECT_EQ(HB_SCRIPT_HAN, script);
350
351 ASSERT_TRUE(
352 TestInfo(result)->RunInfoForTesting(3, start_index, num_glyphs, script));
353 EXPECT_EQ(5u, start_index);
354 EXPECT_EQ(1u, num_glyphs);
355 EXPECT_EQ(HB_SCRIPT_LATIN, script);
356 }
357
TEST_F(HarfBuzzShaperTest,ResolveCandidateRunsArabicThaiHanLatinTwice)358 TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsArabicThaiHanLatinTwice) {
359 UChar mixed_string[] = {0x628, 0x64A, 0x629, 0xE20, 0x65E5, 0x62};
360 HarfBuzzShaper shaper(String(mixed_string, 6));
361 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
362 EXPECT_EQ(4u, TestInfo(result)->NumberOfRunsForTesting());
363
364 // Shape again on the same shape object and check the number of runs.
365 // Should be equal if no state was retained between shape calls.
366 scoped_refptr<ShapeResult> result2 = shaper.Shape(&font, TextDirection::kLtr);
367 EXPECT_EQ(4u, TestInfo(result2)->NumberOfRunsForTesting());
368 }
369
TEST_F(HarfBuzzShaperTest,ResolveCandidateRunsArabic)370 TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsArabic) {
371 UChar arabic_string[] = {0x628, 0x64A, 0x629};
372 HarfBuzzShaper shaper(String(arabic_string, 3));
373 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kRtl);
374
375 EXPECT_EQ(1u, TestInfo(result)->NumberOfRunsForTesting());
376 ASSERT_TRUE(
377 TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script));
378 EXPECT_EQ(0u, start_index);
379 EXPECT_EQ(3u, num_glyphs);
380 EXPECT_EQ(HB_SCRIPT_ARABIC, script);
381 }
382
383 // This is a simplified test and doesn't accuratly reflect how the shape range
384 // is to be used. If you instead of the string you imagine the following HTML:
385 // <div>Hello <span>World</span>!</div>
386 // It better reflects the intended use where the range given to each shape call
387 // corresponds to the text content of a TextNode.
TEST_F(HarfBuzzShaperTest,ShapeLatinSegment)388 TEST_F(HarfBuzzShaperTest, ShapeLatinSegment) {
389 String string("Hello World!", 12u);
390 TextDirection direction = TextDirection::kLtr;
391
392 HarfBuzzShaper shaper(string);
393 scoped_refptr<ShapeResult> combined = shaper.Shape(&font, direction);
394 scoped_refptr<ShapeResult> first = shaper.Shape(&font, direction, 0, 6);
395 scoped_refptr<ShapeResult> second = shaper.Shape(&font, direction, 6, 11);
396 scoped_refptr<ShapeResult> third = shaper.Shape(&font, direction, 11, 12);
397
398 ASSERT_TRUE(TestInfo(first)->RunInfoForTesting(0, start_index, num_characters,
399 num_glyphs, script));
400 EXPECT_EQ(0u, start_index);
401 EXPECT_EQ(6u, num_characters);
402 ASSERT_TRUE(TestInfo(second)->RunInfoForTesting(
403 0, start_index, num_characters, num_glyphs, script));
404 EXPECT_EQ(6u, start_index);
405 EXPECT_EQ(5u, num_characters);
406 ASSERT_TRUE(TestInfo(third)->RunInfoForTesting(0, start_index, num_characters,
407 num_glyphs, script));
408 EXPECT_EQ(11u, start_index);
409 EXPECT_EQ(1u, num_characters);
410
411 HarfBuzzShaper shaper2(string.Substring(0, 6));
412 scoped_refptr<ShapeResult> first_reference = shaper2.Shape(&font, direction);
413
414 HarfBuzzShaper shaper3(string.Substring(6, 5));
415 scoped_refptr<ShapeResult> second_reference = shaper3.Shape(&font, direction);
416
417 HarfBuzzShaper shaper4(string.Substring(11, 1));
418 scoped_refptr<ShapeResult> third_reference = shaper4.Shape(&font, direction);
419
420 // Width of each segment should be the same when shaped using start and end
421 // offset as it is when shaping the three segments using separate shaper
422 // instances.
423 // A full pixel is needed for tolerance to account for kerning on some
424 // platforms.
425 ASSERT_NEAR(first_reference->Width(), first->Width(), 1);
426 ASSERT_NEAR(second_reference->Width(), second->Width(), 1);
427 ASSERT_NEAR(third_reference->Width(), third->Width(), 1);
428
429 // Width of shape results for the entire string should match the combined
430 // shape results from the three segments.
431 float total_width = first->Width() + second->Width() + third->Width();
432 ASSERT_NEAR(combined->Width(), total_width, 1);
433 }
434
435 // Represents the case where a part of a cluster has a different color.
436 // <div>0x647<span style="color: red;">0x64A</span></
437 // Cannot be enabled on Mac yet, compare
438 // https:// https://github.com/harfbuzz/harfbuzz/issues/1415
439 #if defined(OS_MACOSX)
440 #define MAYBE_ShapeArabicWithContext DISABLED_ShapeArabicWithContext
441 #else
442 #define MAYBE_ShapeArabicWithContext ShapeArabicWithContext
443 #endif
TEST_F(HarfBuzzShaperTest,MAYBE_ShapeArabicWithContext)444 TEST_F(HarfBuzzShaperTest, MAYBE_ShapeArabicWithContext) {
445 UChar arabic_string[] = {0x647, 0x64A};
446 HarfBuzzShaper shaper(String(arabic_string, 2));
447
448 scoped_refptr<ShapeResult> combined =
449 shaper.Shape(&font, TextDirection::kRtl);
450
451 scoped_refptr<ShapeResult> first =
452 shaper.Shape(&font, TextDirection::kRtl, 0, 1);
453 scoped_refptr<ShapeResult> second =
454 shaper.Shape(&font, TextDirection::kRtl, 1, 2);
455
456 // Combined width should be the same when shaping the two characters
457 // separately as when shaping them combined.
458 ASSERT_NEAR(combined->Width(), first->Width() + second->Width(), 0.1);
459 }
460
TEST_F(HarfBuzzShaperTest,ShapeTabulationCharacters)461 TEST_F(HarfBuzzShaperTest, ShapeTabulationCharacters) {
462 const unsigned length = HarfBuzzRunGlyphData::kMaxGlyphs * 2 + 1;
463 scoped_refptr<ShapeResult> result =
464 ShapeResult::CreateForTabulationCharacters(&font, TextDirection::kLtr,
465 TabSize(8), 0.f, 0, length);
466 EXPECT_EQ(result->NumCharacters(), length);
467 EXPECT_EQ(result->NumGlyphs(), length);
468 }
469
TEST_F(HarfBuzzShaperTest,ShapeVerticalUpright)470 TEST_F(HarfBuzzShaperTest, ShapeVerticalUpright) {
471 font_description.SetOrientation(FontOrientation::kVerticalUpright);
472 font = Font(font_description);
473
474 // This string should create 2 runs, ideographic and Latin, both in upright.
475 String string(u"\u65E5\u65E5\u65E5lllll");
476 TextDirection direction = TextDirection::kLtr;
477 HarfBuzzShaper shaper(string);
478 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
479
480 // Shape each run and merge them using CopyRange. Width() should match.
481 scoped_refptr<ShapeResult> result1 = shaper.Shape(&font, direction, 0, 3);
482 scoped_refptr<ShapeResult> result2 =
483 shaper.Shape(&font, direction, 3, string.length());
484
485 scoped_refptr<ShapeResult> composite_result =
486 ShapeResult::Create(&font, 0, 0, direction);
487 result1->CopyRange(0, 3, composite_result.get());
488 result2->CopyRange(3, string.length(), composite_result.get());
489
490 EXPECT_EQ(result->Width(), composite_result->Width());
491 }
492
TEST_F(HarfBuzzShaperTest,ShapeVerticalUprightIdeograph)493 TEST_F(HarfBuzzShaperTest, ShapeVerticalUprightIdeograph) {
494 font_description.SetOrientation(FontOrientation::kVerticalUpright);
495 font = Font(font_description);
496
497 // This string should create one ideograph run.
498 String string(u"\u65E5\u65E6\u65E0\u65D3\u65D0");
499 TextDirection direction = TextDirection::kLtr;
500 HarfBuzzShaper shaper(string);
501 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
502
503 // Shape each run and merge them using CopyRange. Width() should match.
504 scoped_refptr<ShapeResult> result1 = shaper.Shape(&font, direction, 0, 3);
505 scoped_refptr<ShapeResult> result2 =
506 shaper.Shape(&font, direction, 3, string.length());
507
508 scoped_refptr<ShapeResult> composite_result =
509 ShapeResult::Create(&font, 0, 0, direction);
510 result1->CopyRange(0, 3, composite_result.get());
511 result2->CopyRange(3, string.length(), composite_result.get());
512
513 // Rounding of x and width may be off by ~0.1 on Mac.
514 float tolerance = 0.1f;
515 EXPECT_NEAR(result->Width(), composite_result->Width(), tolerance);
516 }
517
TEST_F(HarfBuzzShaperTest,RangeShapeSmallCaps)518 TEST_F(HarfBuzzShaperTest, RangeShapeSmallCaps) {
519 // Test passes if no assertion is hit of the ones below, but also the newly
520 // introduced one in HarfBuzzShaper::ShapeSegment: DCHECK_GT(shape_end,
521 // shape_start) is not hit.
522 FontDescription font_description;
523 font_description.SetVariantCaps(FontDescription::kSmallCaps);
524 font_description.SetComputedSize(12.0);
525 Font font(font_description);
526
527 // Shaping index 2 to 3 means that case splitting for small caps splits before
528 // character index 2 since the initial 'a' needs to be uppercased, but the
529 // space character does not need to be uppercased. This triggered
530 // crbug.com/817271.
531 String string(u"a aa");
532 HarfBuzzShaper shaper(string);
533 scoped_refptr<ShapeResult> result =
534 shaper.Shape(&font, TextDirection::kLtr, 2, 3);
535 EXPECT_EQ(1u, result->NumCharacters());
536
537 string = u"aa a";
538 HarfBuzzShaper shaper_two(string);
539 result = shaper_two.Shape(&font, TextDirection::kLtr, 3, 4);
540 EXPECT_EQ(1u, result->NumCharacters());
541
542 string = u"a aa";
543 HarfBuzzShaper shaper_three(string);
544 result = shaper_three.Shape(&font, TextDirection::kLtr, 1, 2);
545 EXPECT_EQ(1u, result->NumCharacters());
546
547 string = u"aa aa aa aa aa aa aa aa aa aa";
548 HarfBuzzShaper shaper_four(string);
549 result = shaper_four.Shape(&font, TextDirection::kLtr, 21, 23);
550 EXPECT_EQ(2u, result->NumCharacters());
551
552 string = u"aa aa aa aa aa aa aa aa aa aa";
553 HarfBuzzShaper shaper_five(string);
554 result = shaper_five.Shape(&font, TextDirection::kLtr, 27, 29);
555 EXPECT_EQ(2u, result->NumCharacters());
556 }
557
TEST_F(HarfBuzzShaperTest,ShapeVerticalMixed)558 TEST_F(HarfBuzzShaperTest, ShapeVerticalMixed) {
559 font_description.SetOrientation(FontOrientation::kVerticalMixed);
560 font = Font(font_description);
561
562 // This string should create 2 runs, ideographic in upright and Latin in
563 // rotated horizontal.
564 String string(u"\u65E5\u65E5\u65E5lllll");
565 TextDirection direction = TextDirection::kLtr;
566 HarfBuzzShaper shaper(string);
567 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
568
569 // Shape each run and merge them using CopyRange. Width() should match.
570 scoped_refptr<ShapeResult> result1 = shaper.Shape(&font, direction, 0, 3);
571 scoped_refptr<ShapeResult> result2 =
572 shaper.Shape(&font, direction, 3, string.length());
573
574 scoped_refptr<ShapeResult> composite_result =
575 ShapeResult::Create(&font, 0, 0, direction);
576 result1->CopyRange(0, 3, composite_result.get());
577 result2->CopyRange(3, string.length(), composite_result.get());
578
579 EXPECT_EQ(result->Width(), composite_result->Width());
580 }
581
582 class ShapeStringTest : public HarfBuzzShaperTest,
583 public testing::WithParamInterface<const char16_t*> {};
584
585 INSTANTIATE_TEST_SUITE_P(HarfBuzzShaperTest,
586 ShapeStringTest,
587 testing::Values(
588 // U+FFF0 is not assigned as of Unicode 10.0.
589 u"\uFFF0",
590 u"\uFFF0Hello",
591 // U+00AD SOFT HYPHEN often does not have glyphs.
592 u"\u00AD"));
593
TEST_P(ShapeStringTest,MissingGlyph)594 TEST_P(ShapeStringTest, MissingGlyph) {
595 String string(GetParam());
596 HarfBuzzShaper shaper(string);
597 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
598 EXPECT_EQ(0u, result->StartIndex());
599 EXPECT_EQ(string.length(), result->EndIndex());
600 }
601
602 // Test splitting runs by kMaxCharacterIndex using a simple string that has code
603 // point:glyph:cluster are all 1:1.
TEST_P(ShapeParameterTest,MaxGlyphsSimple)604 TEST_P(ShapeParameterTest, MaxGlyphsSimple) {
605 const unsigned length = HarfBuzzRunGlyphData::kMaxCharacterIndex + 2;
606 String string = CreateStringOf('X', length);
607 HarfBuzzShaper shaper(string);
608 scoped_refptr<ShapeResult> result = ShapeWithParameter(&shaper);
609 EXPECT_EQ(length, result->NumCharacters());
610 EXPECT_EQ(length, result->NumGlyphs());
611 Vector<ShapeResultRunData> runs = ShapeResultRunData::Get(result);
612 if (IsRtl(GetParam()))
613 runs.Reverse();
614 EXPECT_THAT(
615 runs, testing::ElementsAre(
616 ShapeResultRunData{0, length - 1, length - 1, HB_SCRIPT_LATIN},
617 ShapeResultRunData{length - 1, 1, 1, HB_SCRIPT_LATIN}));
618 }
619
620 // 'X' + U+0300 COMBINING GRAVE ACCENT is a cluster, but most fonts do not have
621 // a pre-composed glyph for it, so code points and glyphs are 1:1. Because the
622 // length is "+1" and the last character is combining, this string does not hit
623 // kMaxCharacterIndex but hits kMaxGlyphs.
TEST_P(ShapeParameterTest,MaxGlyphsClusterLatin)624 TEST_P(ShapeParameterTest, MaxGlyphsClusterLatin) {
625 const unsigned length = HarfBuzzRunGlyphData::kMaxGlyphs + 1;
626 String string = CreateStringOf('X', length);
627 string.replace(1, 1, u"\u0300");
628 string.replace(length - 2, 2, u"Z\u0300");
629 HarfBuzzShaper shaper(string);
630 scoped_refptr<ShapeResult> result = ShapeWithParameter(&shaper);
631 EXPECT_EQ(length, result->NumCharacters());
632 EXPECT_EQ(length, result->NumGlyphs());
633 Vector<ShapeResultRunData> runs = ShapeResultRunData::Get(result);
634 if (IsRtl(GetParam()))
635 runs.Reverse();
636 EXPECT_THAT(
637 runs, testing::ElementsAre(
638 ShapeResultRunData{0, length - 2, length - 2, HB_SCRIPT_LATIN},
639 ShapeResultRunData{length - 2, 2u, 2u, HB_SCRIPT_LATIN}));
640 }
641
642 // Same as MaxGlyphsClusterLatin, but by making the length "+2", this string
643 // hits kMaxCharacterIndex.
TEST_P(ShapeParameterTest,MaxGlyphsClusterLatin2)644 TEST_P(ShapeParameterTest, MaxGlyphsClusterLatin2) {
645 const unsigned length = HarfBuzzRunGlyphData::kMaxGlyphs + 2;
646 String string = CreateStringOf('X', length);
647 string.replace(1, 1, u"\u0300");
648 string.replace(length - 2, 2, u"Z\u0300");
649 HarfBuzzShaper shaper(string);
650 scoped_refptr<ShapeResult> result = ShapeWithParameter(&shaper);
651 EXPECT_EQ(length, result->NumCharacters());
652 EXPECT_EQ(length, result->NumGlyphs());
653 Vector<ShapeResultRunData> runs = ShapeResultRunData::Get(result);
654 if (IsRtl(GetParam()))
655 runs.Reverse();
656 EXPECT_THAT(
657 runs, testing::ElementsAre(
658 ShapeResultRunData{0, length - 2, length - 2, HB_SCRIPT_LATIN},
659 ShapeResultRunData{length - 2, 2u, 2u, HB_SCRIPT_LATIN}));
660 }
661
TEST_P(ShapeParameterTest,MaxGlyphsClusterDevanagari)662 TEST_P(ShapeParameterTest, MaxGlyphsClusterDevanagari) {
663 const unsigned length = HarfBuzzRunGlyphData::kMaxCharacterIndex + 2;
664 String string = CreateStringOf(0x930, length);
665 string.replace(0, 3, u"\u0930\u093F\u0902");
666 string.replace(length - 3, 3, u"\u0930\u093F\u0902");
667 HarfBuzzShaper shaper(string);
668 scoped_refptr<ShapeResult> result = ShapeWithParameter(&shaper);
669 EXPECT_EQ(length, result->NumCharacters());
670 #if defined(OS_LINUX) || defined(OS_FUCHSIA)
671 // Linux and Fuchsia use Lohit Devanagari. When using that font the shaper
672 // returns 32767 glyphs instead of 32769.
673 // TODO(crbug.com/933551): Add Noto Sans Devanagari to
674 // //third_party/test_fonts and use it here.
675 if (result->NumGlyphs() != length)
676 return;
677 #endif
678 EXPECT_EQ(length, result->NumGlyphs());
679 Vector<ShapeResultRunData> runs = ShapeResultRunData::Get(result);
680 if (IsRtl(GetParam()))
681 runs.Reverse();
682 EXPECT_THAT(
683 runs,
684 testing::ElementsAre(
685 ShapeResultRunData{0, length - 3, length - 3, HB_SCRIPT_DEVANAGARI},
686 ShapeResultRunData{length - 3, 3u, 3u, HB_SCRIPT_DEVANAGARI}));
687 }
688
TEST_P(ShapeParameterTest,ZeroWidthSpace)689 TEST_P(ShapeParameterTest, ZeroWidthSpace) {
690 UChar string[] = {kZeroWidthSpaceCharacter,
691 kZeroWidthSpaceCharacter,
692 0x0627,
693 0x0631,
694 0x062F,
695 0x0648,
696 kZeroWidthSpaceCharacter,
697 kZeroWidthSpaceCharacter};
698 const unsigned length = base::size(string);
699 HarfBuzzShaper shaper(String(string, length));
700 scoped_refptr<ShapeResult> result = ShapeWithParameter(&shaper);
701 EXPECT_EQ(0u, result->StartIndex());
702 EXPECT_EQ(length, result->EndIndex());
703 #if DCHECK_IS_ON()
704 result->CheckConsistency();
705 #endif
706 }
707
TEST_F(HarfBuzzShaperTest,NegativeLetterSpacing)708 TEST_F(HarfBuzzShaperTest, NegativeLetterSpacing) {
709 String string(u"Hello");
710 HarfBuzzShaper shaper(string);
711 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
712 float width = result->Width();
713
714 ShapeResultSpacing<String> spacing(string);
715 FontDescription font_description;
716 font_description.SetLetterSpacing(-5);
717 spacing.SetSpacing(font_description);
718 result->ApplySpacing(spacing);
719
720 EXPECT_EQ(5 * 5, width - result->Width());
721 }
722
TEST_F(HarfBuzzShaperTest,NegativeLetterSpacingTo0)723 TEST_F(HarfBuzzShaperTest, NegativeLetterSpacingTo0) {
724 String string(u"00000");
725 HarfBuzzShaper shaper(string);
726 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
727 float char_width = result->Width() / string.length();
728
729 ShapeResultSpacing<String> spacing(string);
730 FontDescription font_description;
731 font_description.SetLetterSpacing(-char_width);
732 spacing.SetSpacing(font_description);
733 result->ApplySpacing(spacing);
734
735 // EXPECT_EQ(0.0f, result->Width());
736 }
737
TEST_F(HarfBuzzShaperTest,NegativeLetterSpacingToNegative)738 TEST_F(HarfBuzzShaperTest, NegativeLetterSpacingToNegative) {
739 String string(u"00000");
740 HarfBuzzShaper shaper(string);
741 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
742 float char_width = result->Width() / string.length();
743
744 ShapeResultSpacing<String> spacing(string);
745 FontDescription font_description;
746 font_description.SetLetterSpacing(-2 * char_width);
747 spacing.SetSpacing(font_description);
748 result->ApplySpacing(spacing);
749
750 // CSS does not allow negative width, it should be clampled to 0.
751 // EXPECT_EQ(0.0f, result->Width());
752 }
753
754 static struct GlyphDataRangeTestData {
755 const char16_t* text;
756 TextDirection direction;
757 unsigned run_index;
758 unsigned start_offset;
759 unsigned end_offset;
760 unsigned start_glyph;
761 unsigned end_glyph;
762 } glyph_data_range_test_data[] = {
763 // Hebrew, taken from fast/text/selection/hebrew-selection.html
764 // The two code points form a grapheme cluster, which produces two glyphs.
765 // Character index array should be [0, 0].
766 {u"\u05E9\u05B0", TextDirection::kRtl, 0, 0, 1, 0, 2},
767 // ZWJ tests taken from fast/text/international/zerowidthjoiner.html
768 // Character index array should be [6, 3, 3, 3, 0, 0, 0].
769 {u"\u0639\u200D\u200D\u0639\u200D\u200D\u0639", TextDirection::kRtl, 0, 0,
770 1, 4, 7},
771 {u"\u0639\u200D\u200D\u0639\u200D\u200D\u0639", TextDirection::kRtl, 0, 2,
772 5, 1, 4},
773 {u"\u0639\u200D\u200D\u0639\u200D\u200D\u0639", TextDirection::kRtl, 0, 4,
774 7, 0, 1},
775 };
776
operator <<(std::ostream & ostream,const GlyphDataRangeTestData & data)777 std::ostream& operator<<(std::ostream& ostream,
778 const GlyphDataRangeTestData& data) {
779 return ostream << data.text;
780 }
781
782 class GlyphDataRangeTest
783 : public HarfBuzzShaperTest,
784 public testing::WithParamInterface<GlyphDataRangeTestData> {};
785
786 INSTANTIATE_TEST_SUITE_P(HarfBuzzShaperTest,
787 GlyphDataRangeTest,
788 testing::ValuesIn(glyph_data_range_test_data));
789
TEST_P(GlyphDataRangeTest,Data)790 TEST_P(GlyphDataRangeTest, Data) {
791 auto data = GetParam();
792 String string(data.text);
793 HarfBuzzShaper shaper(string);
794 scoped_refptr<ShapeResult> result = shaper.Shape(&font, data.direction);
795
796 const auto& run = TestInfo(result)->RunInfoForTesting(data.run_index);
797 auto glyphs = run.FindGlyphDataRange(data.start_offset, data.end_offset);
798 unsigned start_glyph = std::distance(run.glyph_data_.begin(), glyphs.begin);
799 EXPECT_EQ(data.start_glyph, start_glyph);
800 unsigned end_glyph = std::distance(run.glyph_data_.begin(), glyphs.end);
801 EXPECT_EQ(data.end_glyph, end_glyph);
802 }
803
804 static struct OffsetForPositionTestData {
805 float position;
806 unsigned offset_ltr;
807 unsigned offset_rtl;
808 unsigned hit_test_ltr;
809 unsigned hit_test_rtl;
810 unsigned fit_ltr_ltr;
811 unsigned fit_ltr_rtl;
812 unsigned fit_rtl_ltr;
813 unsigned fit_rtl_rtl;
814 } offset_for_position_fixed_pitch_test_data[] = {
815 // The left edge.
816 {-1, 0, 5, 0, 5, 0, 0, 5, 5},
817 {0, 0, 5, 0, 5, 0, 0, 5, 5},
818 // Hit test should round to the nearest glyph at the middle of a glyph.
819 {4, 0, 4, 0, 5, 0, 1, 5, 4},
820 {6, 0, 4, 1, 4, 0, 1, 5, 4},
821 // Glyph boundary between the 1st and the 2nd glyph.
822 // Avoid testing "10.0" to avoid rounding differences on Windows.
823 {9.9, 0, 4, 1, 4, 0, 1, 5, 4},
824 {10.1, 1, 3, 1, 4, 1, 2, 4, 3},
825 // Run boundary is at position 20. The 1st run has 2 characters.
826 {14, 1, 3, 1, 4, 1, 2, 4, 3},
827 {16, 1, 3, 2, 3, 1, 2, 4, 3},
828 {20.1, 2, 2, 2, 3, 2, 3, 3, 2},
829 {24, 2, 2, 2, 3, 2, 3, 3, 2},
830 {26, 2, 2, 3, 2, 2, 3, 3, 2},
831 // The end of the ShapeResult. The result has 5 characters.
832 {44, 4, 0, 4, 1, 4, 5, 1, 0},
833 {46, 4, 0, 5, 0, 4, 5, 1, 0},
834 {50, 5, 0, 5, 0, 5, 5, 0, 0},
835 // Beyond the right edge of the ShapeResult.
836 {51, 5, 0, 5, 0, 5, 5, 0, 0},
837 };
838
operator <<(std::ostream & ostream,const OffsetForPositionTestData & data)839 std::ostream& operator<<(std::ostream& ostream,
840 const OffsetForPositionTestData& data) {
841 return ostream << data.position;
842 }
843
844 class OffsetForPositionTest
845 : public HarfBuzzShaperTest,
846 public testing::WithParamInterface<OffsetForPositionTestData> {};
847
848 INSTANTIATE_TEST_SUITE_P(
849 HarfBuzzShaperTest,
850 OffsetForPositionTest,
851 testing::ValuesIn(offset_for_position_fixed_pitch_test_data));
852
TEST_P(OffsetForPositionTest,Data)853 TEST_P(OffsetForPositionTest, Data) {
854 auto data = GetParam();
855 String string(u"01234");
856 HarfBuzzShaper shaper(string);
857 Font ahem = CreateAhem(10);
858 scoped_refptr<ShapeResult> result =
859 SplitRun(shaper.Shape(&ahem, TextDirection::kLtr), 2);
860 EXPECT_EQ(data.offset_ltr,
861 result->OffsetForPosition(data.position, DontBreakGlyphs));
862 EXPECT_EQ(data.hit_test_ltr, result->CaretOffsetForHitTest(
863 data.position, string, DontBreakGlyphs));
864 EXPECT_EQ(data.fit_ltr_ltr,
865 result->OffsetToFit(data.position, TextDirection::kLtr));
866 EXPECT_EQ(data.fit_ltr_rtl,
867 result->OffsetToFit(data.position, TextDirection::kRtl));
868
869 result = SplitRun(shaper.Shape(&ahem, TextDirection::kRtl), 3);
870 EXPECT_EQ(data.offset_rtl,
871 result->OffsetForPosition(data.position, DontBreakGlyphs));
872 EXPECT_EQ(data.hit_test_rtl, result->CaretOffsetForHitTest(
873 data.position, string, DontBreakGlyphs));
874 EXPECT_EQ(data.fit_rtl_ltr,
875 result->OffsetToFit(data.position, TextDirection::kLtr));
876 EXPECT_EQ(data.fit_rtl_rtl,
877 result->OffsetToFit(data.position, TextDirection::kRtl));
878 }
879
TEST_F(HarfBuzzShaperTest,PositionForOffsetLatin)880 TEST_F(HarfBuzzShaperTest, PositionForOffsetLatin) {
881 String string = To16Bit("Hello World!", 12);
882 TextDirection direction = TextDirection::kLtr;
883
884 HarfBuzzShaper shaper(string);
885 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
886 scoped_refptr<ShapeResult> first =
887 shaper.Shape(&font, direction, 0, 5); // Hello
888 scoped_refptr<ShapeResult> second =
889 shaper.Shape(&font, direction, 6, 11); // World
890
891 EXPECT_EQ(0.0f, result->PositionForOffset(0));
892 ASSERT_NEAR(first->Width(), result->PositionForOffset(5), 1);
893 ASSERT_NEAR(second->Width(),
894 result->PositionForOffset(11) - result->PositionForOffset(6), 1);
895 ASSERT_NEAR(result->Width(), result->PositionForOffset(12), 0.1);
896 }
897
TEST_F(HarfBuzzShaperTest,PositionForOffsetArabic)898 TEST_F(HarfBuzzShaperTest, PositionForOffsetArabic) {
899 UChar arabic_string[] = {0x628, 0x64A, 0x629};
900 TextDirection direction = TextDirection::kRtl;
901
902 HarfBuzzShaper shaper(String(arabic_string, 3));
903 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
904
905 EXPECT_EQ(0.0f, result->PositionForOffset(3));
906 ASSERT_NEAR(result->Width(), result->PositionForOffset(0), 0.1);
907 }
908
TEST_F(HarfBuzzShaperTest,EmojiZWJSequence)909 TEST_F(HarfBuzzShaperTest, EmojiZWJSequence) {
910 UChar emoji_zwj_sequence[] = {0x270C, 0x200D, 0xD83C, 0xDFFF,
911 0x270C, 0x200D, 0xD83C, 0xDFFC};
912 TextDirection direction = TextDirection::kLtr;
913
914 HarfBuzzShaper shaper(
915 String(emoji_zwj_sequence, base::size(emoji_zwj_sequence)));
916 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
917 }
918
919 // A Value-Parameterized Test class to test OffsetForPosition() with
920 // |include_partial_glyphs| parameter.
921 class IncludePartialGlyphsTest
922 : public HarfBuzzShaperTest,
923 public ::testing::WithParamInterface<IncludePartialGlyphsOption> {};
924
925 INSTANTIATE_TEST_SUITE_P(
926 HarfBuzzShaperTest,
927 IncludePartialGlyphsTest,
928 ::testing::Values(IncludePartialGlyphsOption::OnlyFullGlyphs,
929 IncludePartialGlyphsOption::IncludePartialGlyphs));
930
TEST_P(IncludePartialGlyphsTest,OffsetForPositionMatchesPositionForOffsetLatin)931 TEST_P(IncludePartialGlyphsTest,
932 OffsetForPositionMatchesPositionForOffsetLatin) {
933 String string = To16Bit("Hello World!", 12);
934 TextDirection direction = TextDirection::kLtr;
935
936 HarfBuzzShaper shaper(string);
937 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
938
939 IncludePartialGlyphsOption partial = GetParam();
940 EXPECT_EQ(0u, result->OffsetForPosition(result->PositionForOffset(0), string,
941 partial, DontBreakGlyphs));
942 EXPECT_EQ(1u, result->OffsetForPosition(result->PositionForOffset(1), string,
943 partial, DontBreakGlyphs));
944 EXPECT_EQ(2u, result->OffsetForPosition(result->PositionForOffset(2), string,
945 partial, DontBreakGlyphs));
946 EXPECT_EQ(3u, result->OffsetForPosition(result->PositionForOffset(3), string,
947 partial, DontBreakGlyphs));
948 EXPECT_EQ(4u, result->OffsetForPosition(result->PositionForOffset(4), string,
949 partial, DontBreakGlyphs));
950 EXPECT_EQ(5u, result->OffsetForPosition(result->PositionForOffset(5), string,
951 partial, DontBreakGlyphs));
952 EXPECT_EQ(6u, result->OffsetForPosition(result->PositionForOffset(6), string,
953 partial, DontBreakGlyphs));
954 EXPECT_EQ(7u, result->OffsetForPosition(result->PositionForOffset(7), string,
955 partial, DontBreakGlyphs));
956 EXPECT_EQ(8u, result->OffsetForPosition(result->PositionForOffset(8), string,
957 partial, DontBreakGlyphs));
958 EXPECT_EQ(9u, result->OffsetForPosition(result->PositionForOffset(9), string,
959 partial, DontBreakGlyphs));
960 EXPECT_EQ(10u, result->OffsetForPosition(result->PositionForOffset(10),
961 string, partial, DontBreakGlyphs));
962 EXPECT_EQ(11u, result->OffsetForPosition(result->PositionForOffset(11),
963 string, partial, DontBreakGlyphs));
964 EXPECT_EQ(12u, result->OffsetForPosition(result->PositionForOffset(12),
965 string, partial, DontBreakGlyphs));
966 }
967
TEST_P(IncludePartialGlyphsTest,OffsetForPositionMatchesPositionForOffsetArabic)968 TEST_P(IncludePartialGlyphsTest,
969 OffsetForPositionMatchesPositionForOffsetArabic) {
970 UChar arabic_string[] = {0x628, 0x64A, 0x629};
971 String string(arabic_string, 3);
972 TextDirection direction = TextDirection::kRtl;
973
974 HarfBuzzShaper shaper(string);
975 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
976
977 IncludePartialGlyphsOption partial = GetParam();
978 EXPECT_EQ(0u, result->OffsetForPosition(result->PositionForOffset(0), string,
979 partial, DontBreakGlyphs));
980 EXPECT_EQ(1u, result->OffsetForPosition(result->PositionForOffset(1), string,
981 partial, DontBreakGlyphs));
982 EXPECT_EQ(2u, result->OffsetForPosition(result->PositionForOffset(2), string,
983 partial, DontBreakGlyphs));
984 EXPECT_EQ(3u, result->OffsetForPosition(result->PositionForOffset(3), string,
985 partial, DontBreakGlyphs));
986 }
987
TEST_P(IncludePartialGlyphsTest,OffsetForPositionMatchesPositionForOffsetMixed)988 TEST_P(IncludePartialGlyphsTest,
989 OffsetForPositionMatchesPositionForOffsetMixed) {
990 UChar mixed_string[] = {0x628, 0x64A, 0x629, 0xE20, 0x65E5, 0x62};
991 String string(mixed_string, 6);
992 HarfBuzzShaper shaper(string);
993 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
994
995 IncludePartialGlyphsOption partial = GetParam();
996 EXPECT_EQ(0u, result->OffsetForPosition(result->PositionForOffset(0), string,
997 partial, DontBreakGlyphs));
998 EXPECT_EQ(1u, result->OffsetForPosition(result->PositionForOffset(1), string,
999 partial, DontBreakGlyphs));
1000 EXPECT_EQ(2u, result->OffsetForPosition(result->PositionForOffset(2), string,
1001 partial, DontBreakGlyphs));
1002 EXPECT_EQ(3u, result->OffsetForPosition(result->PositionForOffset(3), string,
1003 partial, DontBreakGlyphs));
1004 EXPECT_EQ(4u, result->OffsetForPosition(result->PositionForOffset(4), string,
1005 partial, DontBreakGlyphs));
1006 EXPECT_EQ(5u, result->OffsetForPosition(result->PositionForOffset(5), string,
1007 partial, DontBreakGlyphs));
1008 EXPECT_EQ(6u, result->OffsetForPosition(result->PositionForOffset(6), string,
1009 partial, DontBreakGlyphs));
1010 }
1011
TEST_F(HarfBuzzShaperTest,CachedOffsetPositionMappingForOffsetLatin)1012 TEST_F(HarfBuzzShaperTest, CachedOffsetPositionMappingForOffsetLatin) {
1013 String string = To16Bit("Hello World!", 12);
1014 TextDirection direction = TextDirection::kLtr;
1015
1016 HarfBuzzShaper shaper(string);
1017 scoped_refptr<ShapeResult> sr = shaper.Shape(&font, direction);
1018 sr->EnsurePositionData();
1019
1020 EXPECT_EQ(0u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(0)));
1021 EXPECT_EQ(1u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(1)));
1022 EXPECT_EQ(2u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(2)));
1023 EXPECT_EQ(3u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(3)));
1024 EXPECT_EQ(4u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(4)));
1025 EXPECT_EQ(5u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(5)));
1026 EXPECT_EQ(6u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(6)));
1027 EXPECT_EQ(7u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(7)));
1028 EXPECT_EQ(8u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(8)));
1029 EXPECT_EQ(9u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(9)));
1030 EXPECT_EQ(10u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(10)));
1031 EXPECT_EQ(11u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(11)));
1032 EXPECT_EQ(12u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(12)));
1033 }
1034
TEST_F(HarfBuzzShaperTest,CachedOffsetPositionMappingArabic)1035 TEST_F(HarfBuzzShaperTest, CachedOffsetPositionMappingArabic) {
1036 UChar arabic_string[] = {0x628, 0x64A, 0x629};
1037 TextDirection direction = TextDirection::kRtl;
1038
1039 HarfBuzzShaper shaper(String(arabic_string, 3));
1040 scoped_refptr<ShapeResult> sr = shaper.Shape(&font, direction);
1041 sr->EnsurePositionData();
1042
1043 EXPECT_EQ(0u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(0)));
1044 EXPECT_EQ(1u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(1)));
1045 EXPECT_EQ(2u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(2)));
1046 EXPECT_EQ(3u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(3)));
1047 }
1048
TEST_F(HarfBuzzShaperTest,CachedOffsetPositionMappingMixed)1049 TEST_F(HarfBuzzShaperTest, CachedOffsetPositionMappingMixed) {
1050 UChar mixed_string[] = {0x628, 0x64A, 0x629, 0xE20, 0x65E5, 0x62};
1051 HarfBuzzShaper shaper(String(mixed_string, 6));
1052 scoped_refptr<ShapeResult> sr = shaper.Shape(&font, TextDirection::kLtr);
1053 sr->EnsurePositionData();
1054
1055 EXPECT_EQ(0u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(0)));
1056 EXPECT_EQ(1u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(1)));
1057 EXPECT_EQ(2u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(2)));
1058 EXPECT_EQ(3u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(3)));
1059 EXPECT_EQ(4u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(4)));
1060 EXPECT_EQ(5u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(5)));
1061 EXPECT_EQ(6u, sr->CachedOffsetForPosition(sr->CachedPositionForOffset(6)));
1062 }
1063
TEST_F(HarfBuzzShaperTest,PositionForOffsetMultiGlyphClusterLtr)1064 TEST_F(HarfBuzzShaperTest, PositionForOffsetMultiGlyphClusterLtr) {
1065 // In this Hindi text, each code unit produces a glyph, and the first 3 glyphs
1066 // form a grapheme cluster, and the last 2 glyphs form another.
1067 String string(u"\u0930\u093F\u0902\u0926\u0940");
1068 TextDirection direction = TextDirection::kLtr;
1069 HarfBuzzShaper shaper(string);
1070 scoped_refptr<ShapeResult> sr = shaper.Shape(&font, direction);
1071 sr->EnsurePositionData();
1072
1073 // The first 3 code units should be at position 0.
1074 EXPECT_EQ(0, sr->CachedPositionForOffset(0));
1075 EXPECT_EQ(0, sr->CachedPositionForOffset(1));
1076 EXPECT_EQ(0, sr->CachedPositionForOffset(2));
1077 // The last 2 code units should be > 0, and the same position.
1078 EXPECT_GT(sr->CachedPositionForOffset(3), 0);
1079 EXPECT_EQ(sr->CachedPositionForOffset(3), sr->CachedPositionForOffset(4));
1080 }
1081
TEST_F(HarfBuzzShaperTest,PositionForOffsetMultiGlyphClusterRtl)1082 TEST_F(HarfBuzzShaperTest, PositionForOffsetMultiGlyphClusterRtl) {
1083 // In this Hindi text, each code unit produces a glyph, and the first 3 glyphs
1084 // form a grapheme cluster, and the last 2 glyphs form another.
1085 String string(u"\u0930\u093F\u0902\u0926\u0940");
1086 TextDirection direction = TextDirection::kRtl;
1087 HarfBuzzShaper shaper(string);
1088 scoped_refptr<ShapeResult> sr = shaper.Shape(&font, direction);
1089 sr->EnsurePositionData();
1090
1091 // The first 3 code units should be at position 0, but since this is RTL, the
1092 // position is the right edgef of the character, and thus > 0.
1093 float pos0 = sr->CachedPositionForOffset(0);
1094 EXPECT_GT(pos0, 0);
1095 EXPECT_EQ(pos0, sr->CachedPositionForOffset(1));
1096 EXPECT_EQ(pos0, sr->CachedPositionForOffset(2));
1097 // The last 2 code units should be > 0, and the same position.
1098 float pos3 = sr->CachedPositionForOffset(3);
1099 EXPECT_GT(pos3, 0);
1100 EXPECT_LT(pos3, pos0);
1101 EXPECT_EQ(pos3, sr->CachedPositionForOffset(4));
1102 }
1103
TEST_F(HarfBuzzShaperTest,PositionForOffsetMissingGlyph)1104 TEST_F(HarfBuzzShaperTest, PositionForOffsetMissingGlyph) {
1105 String string(u"\u0633\u0644\u0627\u0645");
1106 HarfBuzzShaper shaper(string);
1107 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kRtl);
1108 // Because the offset 1 and 2 should form a ligature, SubRange(2, 4) creates a
1109 // ShapeResult that does not have its first glyph.
1110 result = result->SubRange(2, 4);
1111 result->PositionForOffset(0);
1112 // Pass if |PositionForOffset| does not crash.
1113 }
1114
1115 static struct ShapeResultCopyRangeTestData {
1116 const char16_t* string;
1117 TextDirection direction;
1118 unsigned break_point;
1119 } shape_result_copy_range_test_data[] = {
1120 {u"ABC", TextDirection::kLtr, 1},
1121 {u"\u0648\u0644\u064A", TextDirection::kRtl, 1},
1122 // These strings creates 3 runs. Split it in the middle of 2nd run.
1123 {u"\u65E5Hello\u65E5\u65E5", TextDirection::kLtr, 3},
1124 {u"\u0648\u0644\u064A AB \u0628\u062A", TextDirection::kRtl, 5}};
1125
operator <<(std::ostream & ostream,const ShapeResultCopyRangeTestData & data)1126 std::ostream& operator<<(std::ostream& ostream,
1127 const ShapeResultCopyRangeTestData& data) {
1128 return ostream << String(data.string) << " @ " << data.break_point << ", "
1129 << data.direction;
1130 }
1131
1132 class ShapeResultCopyRangeTest
1133 : public HarfBuzzShaperTest,
1134 public testing::WithParamInterface<ShapeResultCopyRangeTestData> {};
1135
1136 INSTANTIATE_TEST_SUITE_P(HarfBuzzShaperTest,
1137 ShapeResultCopyRangeTest,
1138 testing::ValuesIn(shape_result_copy_range_test_data));
1139
1140 // Split a ShapeResult and combine them should match to the original result.
TEST_P(ShapeResultCopyRangeTest,Split)1141 TEST_P(ShapeResultCopyRangeTest, Split) {
1142 const auto& test_data = GetParam();
1143 String string(test_data.string);
1144 TextDirection direction = test_data.direction;
1145
1146 HarfBuzzShaper shaper(string);
1147 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1148
1149 // Split the result.
1150 scoped_refptr<ShapeResult> result1 =
1151 ShapeResult::Create(&font, 0, 0, direction);
1152 result->CopyRange(0, test_data.break_point, result1.get());
1153 EXPECT_EQ(test_data.break_point, result1->NumCharacters());
1154 EXPECT_EQ(0u, result1->StartIndex());
1155 EXPECT_EQ(test_data.break_point, result1->EndIndex());
1156
1157 scoped_refptr<ShapeResult> result2 =
1158 ShapeResult::Create(&font, 0, 0, direction);
1159 result->CopyRange(test_data.break_point, string.length(), result2.get());
1160 EXPECT_EQ(string.length() - test_data.break_point, result2->NumCharacters());
1161 EXPECT_EQ(test_data.break_point, result2->StartIndex());
1162 EXPECT_EQ(string.length(), result2->EndIndex());
1163
1164 // Combine them.
1165 scoped_refptr<ShapeResult> composite_result =
1166 ShapeResult::Create(&font, 0, 0, direction);
1167 result1->CopyRange(0, test_data.break_point, composite_result.get());
1168 result2->CopyRange(0, string.length(), composite_result.get());
1169 EXPECT_EQ(string.length(), composite_result->NumCharacters());
1170
1171 // Test character indexes match.
1172 Vector<unsigned> expected_character_indexes =
1173 TestInfo(result)->CharacterIndexesForTesting();
1174 Vector<unsigned> composite_character_indexes =
1175 TestInfo(result)->CharacterIndexesForTesting();
1176 EXPECT_EQ(expected_character_indexes, composite_character_indexes);
1177 }
1178
1179 // Shape ranges and combine them shold match to the result of shaping the whole
1180 // string.
TEST_P(ShapeResultCopyRangeTest,ShapeRange)1181 TEST_P(ShapeResultCopyRangeTest, ShapeRange) {
1182 const auto& test_data = GetParam();
1183 String string(test_data.string);
1184 TextDirection direction = test_data.direction;
1185
1186 HarfBuzzShaper shaper(string);
1187 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1188
1189 // Shape each range.
1190 scoped_refptr<ShapeResult> result1 =
1191 shaper.Shape(&font, direction, 0, test_data.break_point);
1192 EXPECT_EQ(test_data.break_point, result1->NumCharacters());
1193 scoped_refptr<ShapeResult> result2 =
1194 shaper.Shape(&font, direction, test_data.break_point, string.length());
1195 EXPECT_EQ(string.length() - test_data.break_point, result2->NumCharacters());
1196
1197 // Combine them.
1198 scoped_refptr<ShapeResult> composite_result =
1199 ShapeResult::Create(&font, 0, 0, direction);
1200 result1->CopyRange(0, test_data.break_point, composite_result.get());
1201 result2->CopyRange(0, string.length(), composite_result.get());
1202 EXPECT_EQ(string.length(), composite_result->NumCharacters());
1203
1204 // Test character indexes match.
1205 Vector<unsigned> expected_character_indexes =
1206 TestInfo(result)->CharacterIndexesForTesting();
1207 Vector<unsigned> composite_character_indexes =
1208 TestInfo(result)->CharacterIndexesForTesting();
1209 EXPECT_EQ(expected_character_indexes, composite_character_indexes);
1210 }
1211
TEST_F(HarfBuzzShaperTest,ShapeResultCopyRangeIntoLatin)1212 TEST_F(HarfBuzzShaperTest, ShapeResultCopyRangeIntoLatin) {
1213 String string = To16Bit("Testing ShapeResult::createSubRun", 33);
1214 TextDirection direction = TextDirection::kLtr;
1215
1216 HarfBuzzShaper shaper(string);
1217 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1218
1219 scoped_refptr<ShapeResult> composite_result =
1220 ShapeResult::Create(&font, 0, 0, direction);
1221 result->CopyRange(0, 10, composite_result.get());
1222 result->CopyRange(10, 20, composite_result.get());
1223 result->CopyRange(20, 30, composite_result.get());
1224 result->CopyRange(30, 33, composite_result.get());
1225
1226 EXPECT_EQ(result->NumCharacters(), composite_result->NumCharacters());
1227 EXPECT_EQ(result->SnappedWidth(), composite_result->SnappedWidth());
1228
1229 // Rounding of width may be off by ~0.1 on Mac.
1230 float tolerance = 0.1f;
1231 EXPECT_NEAR(result->Width(), composite_result->Width(), tolerance);
1232
1233 EXPECT_EQ(result->SnappedStartPositionForOffset(0),
1234 composite_result->SnappedStartPositionForOffset(0));
1235 EXPECT_EQ(result->SnappedStartPositionForOffset(15),
1236 composite_result->SnappedStartPositionForOffset(15));
1237 EXPECT_EQ(result->SnappedStartPositionForOffset(30),
1238 composite_result->SnappedStartPositionForOffset(30));
1239 EXPECT_EQ(result->SnappedStartPositionForOffset(33),
1240 composite_result->SnappedStartPositionForOffset(33));
1241 }
1242
TEST_F(HarfBuzzShaperTest,ShapeResultCopyRangeIntoArabicThaiHanLatin)1243 TEST_F(HarfBuzzShaperTest, ShapeResultCopyRangeIntoArabicThaiHanLatin) {
1244 UChar mixed_string[] = {0x628, 0x20, 0x64A, 0x629, 0x20, 0xE20, 0x65E5, 0x62};
1245 TextDirection direction = TextDirection::kLtr;
1246
1247 HarfBuzzShaper shaper(String(mixed_string, 8));
1248 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1249
1250 scoped_refptr<ShapeResult> composite_result =
1251 ShapeResult::Create(&font, 0, 0, direction);
1252 result->CopyRange(0, 4, composite_result.get());
1253 result->CopyRange(4, 6, composite_result.get());
1254 result->CopyRange(6, 8, composite_result.get());
1255
1256 EXPECT_EQ(result->NumCharacters(), composite_result->NumCharacters());
1257 EXPECT_EQ(result->SnappedWidth(), composite_result->SnappedWidth());
1258 EXPECT_EQ(result->SnappedStartPositionForOffset(0),
1259 composite_result->SnappedStartPositionForOffset(0));
1260 EXPECT_EQ(result->SnappedStartPositionForOffset(1),
1261 composite_result->SnappedStartPositionForOffset(1));
1262 EXPECT_EQ(result->SnappedStartPositionForOffset(2),
1263 composite_result->SnappedStartPositionForOffset(2));
1264 EXPECT_EQ(result->SnappedStartPositionForOffset(3),
1265 composite_result->SnappedStartPositionForOffset(3));
1266 EXPECT_EQ(result->SnappedStartPositionForOffset(4),
1267 composite_result->SnappedStartPositionForOffset(4));
1268 EXPECT_EQ(result->SnappedStartPositionForOffset(5),
1269 composite_result->SnappedStartPositionForOffset(5));
1270 EXPECT_EQ(result->SnappedStartPositionForOffset(6),
1271 composite_result->SnappedStartPositionForOffset(6));
1272 EXPECT_EQ(result->SnappedStartPositionForOffset(7),
1273 composite_result->SnappedStartPositionForOffset(7));
1274 EXPECT_EQ(result->SnappedStartPositionForOffset(8),
1275 composite_result->SnappedStartPositionForOffset(8));
1276 }
1277
TEST_P(ShapeParameterTest,ShapeResultCopyRangeAcrossRuns)1278 TEST_P(ShapeParameterTest, ShapeResultCopyRangeAcrossRuns) {
1279 // Create 3 runs:
1280 // [0]: 1 character.
1281 // [1]: 5 characters.
1282 // [2]: 2 character.
1283 String mixed_string(u"\u65E5Hello\u65E5\u65E5");
1284 TextDirection direction = GetParam();
1285 HarfBuzzShaper shaper(mixed_string);
1286 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1287
1288 // CopyRange(5, 7) should copy 1 character from [1] and 1 from [2].
1289 scoped_refptr<ShapeResult> target =
1290 ShapeResult::Create(&font, 0, 0, direction);
1291 result->CopyRange(5, 7, target.get());
1292 EXPECT_EQ(2u, target->NumCharacters());
1293 }
1294
TEST_P(ShapeParameterTest,ShapeResultCopyRangeContextMultiRuns)1295 TEST_P(ShapeParameterTest, ShapeResultCopyRangeContextMultiRuns) {
1296 // Create 2 runs:
1297 // [0]: 5 characters.
1298 // [1]: 4 character.
1299 String mixed_string(u"Hello\u65E5\u65E5\u65E5\u65E5");
1300 TextDirection direction = GetParam();
1301 HarfBuzzShaper shaper(mixed_string);
1302 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1303
1304 scoped_refptr<ShapeResult> sub2to4 = result->SubRange(2, 4);
1305 EXPECT_EQ(2u, sub2to4->NumCharacters());
1306 scoped_refptr<ShapeResult> sub5to9 = result->SubRange(5, 9);
1307 EXPECT_EQ(4u, sub5to9->NumCharacters());
1308 }
1309
TEST_F(HarfBuzzShaperTest,ShapeResultCopyRangeSegmentGlyphBoundingBox)1310 TEST_F(HarfBuzzShaperTest, ShapeResultCopyRangeSegmentGlyphBoundingBox) {
1311 String string(u"THello worldL");
1312 TextDirection direction = TextDirection::kLtr;
1313
1314 HarfBuzzShaper shaper(string);
1315 scoped_refptr<ShapeResult> result1 = shaper.Shape(&font, direction, 0, 6);
1316 scoped_refptr<ShapeResult> result2 =
1317 shaper.Shape(&font, direction, 6, string.length());
1318
1319 scoped_refptr<ShapeResult> composite_result =
1320 ShapeResult::Create(&font, 0, 0, direction);
1321 result1->CopyRange(0, 6, composite_result.get());
1322 result2->CopyRange(6, string.length(), composite_result.get());
1323
1324 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1325 EXPECT_EQ(result->Width(), composite_result->Width());
1326 }
1327
TEST_F(HarfBuzzShaperTest,SubRange)1328 TEST_F(HarfBuzzShaperTest, SubRange) {
1329 String string(u"Hello world");
1330 TextDirection direction = TextDirection::kRtl;
1331 HarfBuzzShaper shaper(string);
1332 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1333
1334 scoped_refptr<ShapeResult> sub_range = result->SubRange(4, 7);
1335 DCHECK_EQ(4u, sub_range->StartIndex());
1336 DCHECK_EQ(7u, sub_range->EndIndex());
1337 DCHECK_EQ(3u, sub_range->NumCharacters());
1338 DCHECK_EQ(result->Direction(), sub_range->Direction());
1339 }
1340
TEST_F(HarfBuzzShaperTest,SafeToBreakLatinCommonLigatures)1341 TEST_F(HarfBuzzShaperTest, SafeToBreakLatinCommonLigatures) {
1342 FontDescription::VariantLigatures ligatures;
1343 ligatures.common = FontDescription::kEnabledLigaturesState;
1344
1345 // MEgalopolis Extra has a lot of ligatures which this test relies on.
1346 Font testFont = blink::test::CreateTestFont(
1347 "MEgalopolis",
1348 blink::test::PlatformTestDataPath(
1349 "third_party/MEgalopolis/MEgalopolisExtra.woff"),
1350 16, &ligatures);
1351
1352 String string = To16Bit("ffi ff", 6);
1353 HarfBuzzShaper shaper(string);
1354 scoped_refptr<ShapeResult> result =
1355 shaper.Shape(&testFont, TextDirection::kLtr);
1356
1357 EXPECT_EQ(0u, result->NextSafeToBreakOffset(0)); // At start of string.
1358 EXPECT_EQ(3u, result->NextSafeToBreakOffset(1)); // At end of "ffi" ligature.
1359 EXPECT_EQ(3u, result->NextSafeToBreakOffset(2)); // At end of "ffi" ligature.
1360 EXPECT_EQ(3u, result->NextSafeToBreakOffset(3)); // At end of "ffi" ligature.
1361 EXPECT_EQ(4u, result->NextSafeToBreakOffset(4)); // After space.
1362 EXPECT_EQ(6u, result->NextSafeToBreakOffset(5)); // At end of "ff" ligature.
1363 EXPECT_EQ(6u, result->NextSafeToBreakOffset(6)); // At end of "ff" ligature.
1364
1365 // Verify safe to break information in copied results to ensure that both
1366 // copying and multi-run break information works.
1367 scoped_refptr<ShapeResult> copied_result =
1368 ShapeResult::Create(&testFont, 0, 0, TextDirection::kLtr);
1369 result->CopyRange(0, 3, copied_result.get());
1370 result->CopyRange(3, string.length(), copied_result.get());
1371
1372 EXPECT_EQ(0u, copied_result->NextSafeToBreakOffset(0));
1373 EXPECT_EQ(3u, copied_result->NextSafeToBreakOffset(1));
1374 EXPECT_EQ(3u, copied_result->NextSafeToBreakOffset(2));
1375 EXPECT_EQ(3u, copied_result->NextSafeToBreakOffset(3));
1376 EXPECT_EQ(4u, copied_result->NextSafeToBreakOffset(4));
1377 EXPECT_EQ(6u, copied_result->NextSafeToBreakOffset(5));
1378 EXPECT_EQ(6u, copied_result->NextSafeToBreakOffset(6));
1379 }
1380
TEST_F(HarfBuzzShaperTest,SafeToBreakPreviousLatinCommonLigatures)1381 TEST_F(HarfBuzzShaperTest, SafeToBreakPreviousLatinCommonLigatures) {
1382 FontDescription::VariantLigatures ligatures;
1383 ligatures.common = FontDescription::kEnabledLigaturesState;
1384
1385 // MEgalopolis Extra has a lot of ligatures which this test relies on.
1386 Font testFont = blink::test::CreateTestFont(
1387 "MEgalopolis",
1388 blink::test::PlatformTestDataPath(
1389 "third_party/MEgalopolis/MEgalopolisExtra.woff"),
1390 16, &ligatures);
1391
1392 String string = To16Bit("ffi ff", 6);
1393 HarfBuzzShaper shaper(string);
1394 scoped_refptr<ShapeResult> result =
1395 shaper.Shape(&testFont, TextDirection::kLtr);
1396
1397 EXPECT_EQ(6u, result->PreviousSafeToBreakOffset(6)); // At end of "ff" liga.
1398 EXPECT_EQ(4u, result->PreviousSafeToBreakOffset(5)); // At end of "ff" liga.
1399 EXPECT_EQ(4u, result->PreviousSafeToBreakOffset(4)); // After space.
1400 EXPECT_EQ(3u, result->PreviousSafeToBreakOffset(3)); // At end of "ffi" liga.
1401 EXPECT_EQ(0u, result->PreviousSafeToBreakOffset(2)); // At start of string.
1402 EXPECT_EQ(0u, result->PreviousSafeToBreakOffset(1)); // At start of string.
1403 EXPECT_EQ(0u, result->PreviousSafeToBreakOffset(0)); // At start of string.
1404
1405 // Verify safe to break information in copied results to ensure that both
1406 // copying and multi-run break information works.
1407 scoped_refptr<ShapeResult> copied_result =
1408 ShapeResult::Create(&testFont, 0, 0, TextDirection::kLtr);
1409 result->CopyRange(0, 3, copied_result.get());
1410 result->CopyRange(3, string.length(), copied_result.get());
1411
1412 EXPECT_EQ(6u, copied_result->PreviousSafeToBreakOffset(6));
1413 EXPECT_EQ(4u, copied_result->PreviousSafeToBreakOffset(5));
1414 EXPECT_EQ(4u, copied_result->PreviousSafeToBreakOffset(4));
1415 EXPECT_EQ(3u, copied_result->PreviousSafeToBreakOffset(3));
1416 EXPECT_EQ(0u, copied_result->PreviousSafeToBreakOffset(2));
1417 EXPECT_EQ(0u, copied_result->PreviousSafeToBreakOffset(1));
1418 EXPECT_EQ(0u, copied_result->PreviousSafeToBreakOffset(0));
1419 }
1420
TEST_F(HarfBuzzShaperTest,SafeToBreakLatinDiscretionaryLigatures)1421 TEST_F(HarfBuzzShaperTest, SafeToBreakLatinDiscretionaryLigatures) {
1422 FontDescription::VariantLigatures ligatures;
1423 ligatures.common = FontDescription::kEnabledLigaturesState;
1424 ligatures.discretionary = FontDescription::kEnabledLigaturesState;
1425
1426 // MEgalopolis Extra has a lot of ligatures which this test relies on.
1427 Font testFont = blink::test::CreateTestFont(
1428 "MEgalopolis",
1429 blink::test::PlatformTestDataPath(
1430 "third_party/MEgalopolis/MEgalopolisExtra.woff"),
1431 16, &ligatures);
1432
1433 // $ ./hb-shape --shaper=ot --features="dlig=1,kern" --show-flags
1434 // MEgalopolisExtra.ttf "RADDAYoVaDD"
1435 // [R_A=0+1150|D=2+729|D=3+699|A=4+608#1|Y=5+608#1|o=6+696#1|V=7+652#1|a=8+657#1|D=9+729|D=10+729]
1436 // RA Ligature, unkerned D D, D A kerns, A Y kerns, Y o kerns, o V kerns, V a
1437 // kerns, no kerning with D.
1438 String test_word(u"RADDAYoVaDD");
1439 unsigned safe_to_break_positions[] = {2, 3, 9, 10};
1440 HarfBuzzShaper shaper(test_word);
1441 scoped_refptr<ShapeResult> result =
1442 shaper.Shape(&testFont, TextDirection::kLtr);
1443
1444 unsigned compare_safe_to_break_position = 0;
1445 for (unsigned i = 1; i < test_word.length() - 1; ++i) {
1446 EXPECT_EQ(safe_to_break_positions[compare_safe_to_break_position],
1447 result->NextSafeToBreakOffset(i));
1448 if (i == safe_to_break_positions[compare_safe_to_break_position])
1449 compare_safe_to_break_position++;
1450 }
1451
1452 // Add zero-width spaces at some of the safe to break offsets.
1453 String inserted_zero_width_spaces(u"RA\u200BD\u200BDAYoVa\u200BD\u200BD");
1454 HarfBuzzShaper refShaper(inserted_zero_width_spaces);
1455 scoped_refptr<ShapeResult> referenceResult =
1456 refShaper.Shape(&testFont, TextDirection::kLtr);
1457
1458 // Results should be identical if it truly is safe to break at the designated
1459 // safe-to-break offsets because otherwise, the zero-width spaces would have
1460 // altered the text spacing, for example by breaking apart ligatures or
1461 // kerning pairs.
1462 EXPECT_EQ(result->SnappedWidth(), referenceResult->SnappedWidth());
1463
1464 // Zero-width spaces were inserted, so we need to account for that by
1465 // offseting the index that we compare against.
1466 unsigned inserts_offset = 0;
1467 for (unsigned i = 0; i < test_word.length(); ++i) {
1468 if (i == safe_to_break_positions[inserts_offset])
1469 inserts_offset++;
1470 EXPECT_EQ(
1471 result->SnappedStartPositionForOffset(i),
1472 referenceResult->SnappedStartPositionForOffset(i + inserts_offset));
1473 }
1474 }
1475
1476 // TODO(crbug.com/870712): This test fails due to font fallback differences on
1477 // Android and Fuchsia.
1478 #if defined(OS_ANDROID) || defined(OS_FUCHSIA)
1479 #define MAYBE_SafeToBreakArabicCommonLigatures \
1480 DISABLED_SafeToBreakArabicCommonLigatures
1481 #else
1482 #define MAYBE_SafeToBreakArabicCommonLigatures SafeToBreakArabicCommonLigatures
1483 #endif
TEST_F(HarfBuzzShaperTest,MAYBE_SafeToBreakArabicCommonLigatures)1484 TEST_F(HarfBuzzShaperTest, MAYBE_SafeToBreakArabicCommonLigatures) {
1485 FontDescription::VariantLigatures ligatures;
1486 ligatures.common = FontDescription::kEnabledLigaturesState;
1487
1488 // كسر الاختبار
1489 String string(
1490 u"\u0643\u0633\u0631\u0020\u0627\u0644\u0627\u062E\u062A\u0628\u0627"
1491 u"\u0631");
1492 HarfBuzzShaper shaper(string);
1493 scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kRtl);
1494
1495 Vector<unsigned> safe_to_break_positions;
1496
1497 #if defined(OS_MACOSX)
1498 safe_to_break_positions = {0, 2, 3, 4, 11};
1499 #else
1500 safe_to_break_positions = {0, 3, 4, 5, 7, 11};
1501 #endif
1502 unsigned compare_safe_to_break_position = 0;
1503 for (unsigned i = 0; i < string.length() - 1; ++i) {
1504 EXPECT_EQ(safe_to_break_positions[compare_safe_to_break_position],
1505 result->NextSafeToBreakOffset(i));
1506 if (i == safe_to_break_positions[compare_safe_to_break_position])
1507 compare_safe_to_break_position++;
1508 }
1509 }
1510
1511 // TODO(layout-dev): Expand RTL test coverage and add tests for mixed
1512 // directionality strings.
1513
1514 // Test when some characters are missing in |runs_|.
TEST_P(ShapeParameterTest,SafeToBreakMissingRun)1515 TEST_P(ShapeParameterTest, SafeToBreakMissingRun) {
1516 TextDirection direction = GetParam();
1517 scoped_refptr<ShapeResult> result = CreateMissingRunResult(direction);
1518 #if DCHECK_IS_ON()
1519 result->CheckConsistency();
1520 #endif
1521
1522 EXPECT_EQ(2u, result->StartIndex());
1523 EXPECT_EQ(10u, result->EndIndex());
1524
1525 EXPECT_EQ(2u, result->NextSafeToBreakOffset(2));
1526 EXPECT_EQ(3u, result->NextSafeToBreakOffset(3));
1527 EXPECT_EQ(4u, result->NextSafeToBreakOffset(4));
1528 EXPECT_EQ(6u, result->NextSafeToBreakOffset(5));
1529 EXPECT_EQ(6u, result->NextSafeToBreakOffset(6));
1530 EXPECT_EQ(8u, result->NextSafeToBreakOffset(7));
1531 EXPECT_EQ(8u, result->NextSafeToBreakOffset(8));
1532 EXPECT_EQ(10u, result->NextSafeToBreakOffset(9));
1533
1534 EXPECT_EQ(2u, result->PreviousSafeToBreakOffset(2));
1535 EXPECT_EQ(3u, result->PreviousSafeToBreakOffset(3));
1536 EXPECT_EQ(4u, result->PreviousSafeToBreakOffset(4));
1537 EXPECT_EQ(4u, result->PreviousSafeToBreakOffset(5));
1538 EXPECT_EQ(6u, result->PreviousSafeToBreakOffset(6));
1539 EXPECT_EQ(6u, result->PreviousSafeToBreakOffset(7));
1540 EXPECT_EQ(8u, result->PreviousSafeToBreakOffset(8));
1541 EXPECT_EQ(8u, result->PreviousSafeToBreakOffset(9));
1542 }
1543
TEST_P(ShapeParameterTest,CopyRangeMissingRun)1544 TEST_P(ShapeParameterTest, CopyRangeMissingRun) {
1545 TextDirection direction = GetParam();
1546 scoped_refptr<ShapeResult> result = CreateMissingRunResult(direction);
1547
1548 // 6 and 7 are missing but NumCharacters() should be 4.
1549 scoped_refptr<ShapeResult> sub = result->SubRange(5, 9);
1550 EXPECT_EQ(sub->StartIndex(), 5u);
1551 EXPECT_EQ(sub->EndIndex(), 9u);
1552 EXPECT_EQ(sub->NumCharacters(), 4u);
1553
1554 // The end is missing.
1555 sub = result->SubRange(5, 7);
1556 EXPECT_EQ(sub->StartIndex(), 5u);
1557 EXPECT_EQ(sub->EndIndex(), 7u);
1558 EXPECT_EQ(sub->NumCharacters(), 2u);
1559
1560 // The start is missing.
1561 sub = result->SubRange(7, 9);
1562 EXPECT_EQ(sub->StartIndex(), 7u);
1563 EXPECT_EQ(sub->EndIndex(), 9u);
1564 EXPECT_EQ(sub->NumCharacters(), 2u);
1565 }
1566
TEST_P(ShapeParameterTest,CopyRangeNoRuns)1567 TEST_P(ShapeParameterTest, CopyRangeNoRuns) {
1568 TextDirection direction = GetParam();
1569 scoped_refptr<ShapeResult> result =
1570 ShapeResult::Create(&font, 0, 2, direction);
1571
1572 scoped_refptr<ShapeResult> sub0 = result->SubRange(0, 1);
1573 EXPECT_EQ(sub0->StartIndex(), 0u);
1574 EXPECT_EQ(sub0->EndIndex(), 1u);
1575 EXPECT_EQ(sub0->NumCharacters(), 1u);
1576
1577 scoped_refptr<ShapeResult> sub1 = result->SubRange(1, 2);
1578 EXPECT_EQ(sub1->StartIndex(), 1u);
1579 EXPECT_EQ(sub1->EndIndex(), 2u);
1580 EXPECT_EQ(sub1->NumCharacters(), 1u);
1581
1582 Vector<scoped_refptr<ShapeResult>> range_results;
1583 Vector<ShapeResult::ShapeRange> ranges;
1584 range_results.push_back(ShapeResult::CreateEmpty(*result));
1585 ranges.push_back(ShapeResult::ShapeRange{0, 1, range_results[0].get()});
1586 result->CopyRanges(ranges.data(), ranges.size());
1587 for (unsigned i = 0; i < ranges.size(); i++) {
1588 const ShapeResult::ShapeRange& range = ranges[i];
1589 const ShapeResult& result = *range_results[i];
1590 EXPECT_EQ(result.StartIndex(), range.start);
1591 EXPECT_EQ(result.EndIndex(), range.end);
1592 EXPECT_EQ(result.NumCharacters(), range.end - range.start);
1593 }
1594 }
1595
TEST_P(ShapeParameterTest,ShapeResultViewMissingRun)1596 TEST_P(ShapeParameterTest, ShapeResultViewMissingRun) {
1597 TextDirection direction = GetParam();
1598 scoped_refptr<ShapeResult> result = CreateMissingRunResult(direction);
1599
1600 // 6 and 7 are missing but NumCharacters() should be 4.
1601 scoped_refptr<ShapeResultView> view =
1602 ShapeResultView::Create(result.get(), 5, 9);
1603 EXPECT_EQ(view->StartIndex(), 5u);
1604 EXPECT_EQ(view->EndIndex(), 9u);
1605 EXPECT_EQ(view->NumCharacters(), 4u);
1606
1607 // The end is missing.
1608 view = ShapeResultView::Create(result.get(), 5, 7);
1609 EXPECT_EQ(view->StartIndex(), 5u);
1610 EXPECT_EQ(view->EndIndex(), 7u);
1611 EXPECT_EQ(view->NumCharacters(), 2u);
1612
1613 // The start is missing.
1614 view = ShapeResultView::Create(result.get(), 7, 9);
1615 EXPECT_EQ(view->StartIndex(), 7u);
1616 EXPECT_EQ(view->EndIndex(), 9u);
1617 EXPECT_EQ(view->NumCharacters(), 2u);
1618 }
1619
1620 // Call this to ensure your test string has some kerning going on.
KerningIsHappening(const FontDescription & font_description,TextDirection direction,const String & str)1621 static bool KerningIsHappening(const FontDescription& font_description,
1622 TextDirection direction,
1623 const String& str) {
1624 FontDescription no_kern = font_description;
1625 no_kern.SetKerning(FontDescription::kNoneKerning);
1626
1627 FontDescription kern = font_description;
1628 kern.SetKerning(FontDescription::kAutoKerning);
1629
1630 Font font_no_kern(no_kern);
1631 Font font_kern(kern);
1632
1633 HarfBuzzShaper shaper(str);
1634
1635 scoped_refptr<ShapeResult> result_no_kern =
1636 shaper.Shape(&font_no_kern, direction);
1637 scoped_refptr<ShapeResult> result_kern = shaper.Shape(&font_kern, direction);
1638
1639 for (unsigned i = 0; i < str.length(); i++) {
1640 if (result_no_kern->PositionForOffset(i) !=
1641 result_kern->PositionForOffset(i))
1642 return true;
1643 }
1644 return false;
1645 }
1646
TEST_F(HarfBuzzShaperTest,KerningIsHappeningWorks)1647 TEST_F(HarfBuzzShaperTest, KerningIsHappeningWorks) {
1648 EXPECT_TRUE(
1649 KerningIsHappening(font_description, TextDirection::kLtr, u"AVOID"));
1650 EXPECT_FALSE(
1651 KerningIsHappening(font_description, TextDirection::kLtr, u"NOID"));
1652
1653 // We won't kern vertically with the default font.
1654 font_description.SetOrientation(FontOrientation::kVerticalUpright);
1655
1656 EXPECT_FALSE(
1657 KerningIsHappening(font_description, TextDirection::kLtr, u"AVOID"));
1658 EXPECT_FALSE(
1659 KerningIsHappening(font_description, TextDirection::kLtr, u"NOID"));
1660 }
1661
TEST_F(HarfBuzzShaperTest,ShapeHorizontalWithoutSubpixelPositionWithoutKerningIsRounded)1662 TEST_F(HarfBuzzShaperTest,
1663 ShapeHorizontalWithoutSubpixelPositionWithoutKerningIsRounded) {
1664 ScopedSubpixelOverride subpixel_override(false);
1665
1666 String string(u"NOID");
1667 TextDirection direction = TextDirection::kLtr;
1668 ASSERT_FALSE(KerningIsHappening(font_description, direction, string));
1669
1670 HarfBuzzShaper shaper(string);
1671 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1672
1673 for (unsigned i = 0; i < string.length(); i++) {
1674 float position = result->PositionForOffset(i);
1675 EXPECT_EQ(round(position), position)
1676 << "Position not rounded at offset " << i;
1677 }
1678 }
1679
TEST_F(HarfBuzzShaperTest,ShapeHorizontalWithSubpixelPositionWithoutKerningIsNotRounded)1680 TEST_F(HarfBuzzShaperTest,
1681 ShapeHorizontalWithSubpixelPositionWithoutKerningIsNotRounded) {
1682 ScopedSubpixelOverride subpixel_override(true);
1683
1684 String string(u"NOID");
1685 TextDirection direction = TextDirection::kLtr;
1686 ASSERT_FALSE(KerningIsHappening(font_description, direction, string));
1687
1688 HarfBuzzShaper shaper(string);
1689 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1690
1691 for (unsigned i = 0; i < string.length(); i++) {
1692 float position = result->PositionForOffset(i);
1693 if (round(position) != position)
1694 return;
1695 }
1696
1697 EXPECT_TRUE(false) << "No unrounded positions found";
1698 }
1699
TEST_F(HarfBuzzShaperTest,ShapeHorizontalWithoutSubpixelPositionWithKerningIsRounded)1700 TEST_F(HarfBuzzShaperTest,
1701 ShapeHorizontalWithoutSubpixelPositionWithKerningIsRounded) {
1702 ScopedSubpixelOverride subpixel_override(false);
1703
1704 String string(u"AVOID");
1705 TextDirection direction = TextDirection::kLtr;
1706 ASSERT_TRUE(KerningIsHappening(font_description, direction, string));
1707
1708 HarfBuzzShaper shaper(string);
1709 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1710
1711 for (unsigned i = 0; i < string.length(); i++) {
1712 float position = result->PositionForOffset(i);
1713 EXPECT_EQ(round(position), position)
1714 << "Position not rounded at offset " << i;
1715 }
1716 }
1717
TEST_F(HarfBuzzShaperTest,ShapeHorizontalWithSubpixelPositionWithKerningIsNotRounded)1718 TEST_F(HarfBuzzShaperTest,
1719 ShapeHorizontalWithSubpixelPositionWithKerningIsNotRounded) {
1720 ScopedSubpixelOverride subpixel_override(true);
1721
1722 String string(u"AVOID");
1723 TextDirection direction = TextDirection::kLtr;
1724 ASSERT_TRUE(KerningIsHappening(font_description, direction, string));
1725
1726 HarfBuzzShaper shaper(string);
1727 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1728
1729 for (unsigned i = 0; i < string.length(); i++) {
1730 float position = result->PositionForOffset(i);
1731 if (round(position) != position)
1732 return;
1733 }
1734
1735 EXPECT_TRUE(false) << "No unrounded positions found";
1736 }
1737
TEST_F(HarfBuzzShaperTest,ShapeVerticalWithoutSubpixelPositionIsRounded)1738 TEST_F(HarfBuzzShaperTest, ShapeVerticalWithoutSubpixelPositionIsRounded) {
1739 ScopedSubpixelOverride subpixel_override(false);
1740
1741 font_description.SetOrientation(FontOrientation::kVerticalUpright);
1742 font = Font(font_description);
1743
1744 String string(u"\u65E5\u65E5\u65E5");
1745 TextDirection direction = TextDirection::kLtr;
1746
1747 HarfBuzzShaper shaper(string);
1748 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1749
1750 for (unsigned i = 0; i < string.length(); i++) {
1751 float position = result->PositionForOffset(i);
1752 EXPECT_EQ(round(position), position)
1753 << "Position not rounded at offset " << i;
1754 }
1755 }
1756
TEST_F(HarfBuzzShaperTest,ShapeVerticalWithSubpixelPositionIsRounded)1757 TEST_F(HarfBuzzShaperTest, ShapeVerticalWithSubpixelPositionIsRounded) {
1758 ScopedSubpixelOverride subpixel_override(true);
1759
1760 font_description.SetOrientation(FontOrientation::kVerticalUpright);
1761 font = Font(font_description);
1762
1763 String string(u"\u65E5\u65E5\u65E5");
1764 TextDirection direction = TextDirection::kLtr;
1765
1766 HarfBuzzShaper shaper(string);
1767 scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
1768
1769 // Vertical text is never subpixel positioned.
1770 for (unsigned i = 0; i < string.length(); i++) {
1771 float position = result->PositionForOffset(i);
1772 EXPECT_EQ(round(position), position)
1773 << "Position not rounded at offset " << i;
1774 }
1775 }
1776
1777 } // namespace blink
1778