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