blob: 62fbee4b800528cd2c1dbd192193b1f0707c09cb [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.h"
#include <unicode/uscript.h>
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/fonts/font.h"
#include "third_party/blink/renderer/platform/fonts/font_cache.h"
#include "third_party/blink/renderer/platform/fonts/font_test_utilities.h"
#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h"
namespace blink {
class ShapeResultViewTest : public testing::Test {
protected:
void SetUp() override {
font_description.SetComputedSize(12.0);
font = Font(font_description);
}
void TearDown() override {}
FontCachePurgePreventer font_cache_purge_preventer;
FontDescription font_description;
Font font;
};
TEST_F(ShapeResultViewTest, LatinSingleView) {
String string =
To16Bit("Test run with multiple words and breaking opportunities.", 56);
TextDirection direction = TextDirection::kLtr;
HarfBuzzShaper shaper(string);
scoped_refptr<const ShapeResult> result = shaper.Shape(&font, direction);
Vector<ShapeResultTestGlyphInfo> glyphs;
result->ForEachGlyph(0, AddGlyphInfo, static_cast<void*>(&glyphs));
// Test view at the start of the result: "Test run with multiple"
ShapeResultView::Segment segment = {result.get(), 0, 22};
auto first4 = ShapeResultView::Create(&segment, 1);
EXPECT_EQ(first4->StartIndex(), 0u);
EXPECT_EQ(first4->NumCharacters(), 22u);
EXPECT_EQ(first4->NumGlyphs(), 22u);
Vector<ShapeResultTestGlyphInfo> first4_glyphs;
first4->ForEachGlyph(0, AddGlyphInfo, static_cast<void*>(&first4_glyphs));
EXPECT_EQ(first4_glyphs.size(), 22u);
EXPECT_TRUE(CompareResultGlyphs(first4_glyphs, glyphs, 0u, 22u));
// Test view in the middle of the result: "multiple words and breaking"
segment = {result.get(), 14, 41};
auto middle4 = ShapeResultView::Create(&segment, 1);
EXPECT_EQ(middle4->StartIndex(), 14u);
EXPECT_EQ(middle4->NumCharacters(), 27u);
EXPECT_EQ(middle4->NumGlyphs(), 27u);
Vector<ShapeResultTestGlyphInfo> middle4_glyphs;
middle4->ForEachGlyph(0, AddGlyphInfo, static_cast<void*>(&middle4_glyphs));
EXPECT_EQ(middle4_glyphs.size(), 27u);
EXPECT_TRUE(CompareResultGlyphs(middle4_glyphs, glyphs, 14u, 27u));
// Test view at the end of the result: "breaking opportunities."
segment = {result.get(), 33, 56};
auto last2 = ShapeResultView::Create(&segment, 1);
EXPECT_EQ(last2->StartIndex(), 33u);
EXPECT_EQ(last2->NumCharacters(), 23u);
EXPECT_EQ(last2->NumGlyphs(), 23u);
Vector<ShapeResultTestGlyphInfo> last2_glyphs;
last2->ForEachGlyph(0, AddGlyphInfo, static_cast<void*>(&last2_glyphs));
EXPECT_EQ(last2_glyphs.size(), 23u);
EXPECT_TRUE(CompareResultGlyphs(last2_glyphs, glyphs, 33u, 23u));
}
TEST_F(ShapeResultViewTest, ArabicSingleView) {
String string = To16Bit("عربى نص", 7);
TextDirection direction = TextDirection::kRtl;
HarfBuzzShaper shaper(string);
scoped_refptr<const ShapeResult> result = shaper.Shape(&font, direction);
Vector<ShapeResultTestGlyphInfo> glyphs;
result->ForEachGlyph(0, AddGlyphInfo, static_cast<void*>(&glyphs));
// Test view at the start of the result: "عربى"
ShapeResultView::Segment segment = {result.get(), 0, 4};
auto first_word = ShapeResultView::Create(&segment, 1);
Vector<ShapeResultTestGlyphInfo> first_glyphs;
first_word->ForEachGlyph(0, AddGlyphInfo, static_cast<void*>(&first_glyphs));
EXPECT_EQ(first_word->StartIndex(), 0u);
EXPECT_EQ(first_word->NumCharacters(), 4u);
EXPECT_EQ(first_word->NumGlyphs(), 4u);
EXPECT_EQ(first_glyphs.size(), 4u);
String first_reference_string = To16Bit("عربى", 4);
HarfBuzzShaper first_reference_shaper(first_reference_string);
scoped_refptr<const ShapeResult> first_wortd_reference =
first_reference_shaper.Shape(&font, direction);
Vector<ShapeResultTestGlyphInfo> first_reference_glyphs;
first_wortd_reference->ForEachGlyph(
0, AddGlyphInfo, static_cast<void*>(&first_reference_glyphs));
EXPECT_EQ(first_reference_glyphs.size(), 4u);
EXPECT_TRUE(
CompareResultGlyphs(first_glyphs, first_reference_glyphs, 0u, 4u));
EXPECT_TRUE(CompareResultGlyphs(first_glyphs, glyphs, 3u, 7u));
// Test view at the end of the result: "نص"
segment = {result.get(), 4, 7};
auto last_word = ShapeResultView::Create(&segment, 1);
Vector<ShapeResultTestGlyphInfo> last_glyphs;
last_word->ForEachGlyph(0, AddGlyphInfo, static_cast<void*>(&last_glyphs));
EXPECT_EQ(last_word->StartIndex(), 4u);
EXPECT_EQ(last_word->NumCharacters(), 3u);
EXPECT_EQ(last_word->NumGlyphs(), 3u);
EXPECT_EQ(last_glyphs.size(), 3u);
}
TEST_F(ShapeResultViewTest, PreviousSafeToBreak) {
String string =
u"\u0028\u05D1\u0029\u0020\u05D4\u05D1\u05DC\u0020\u05D0\u05DE\u05E8"
u"\u0020\u05E2\u05DC\u0020"
u"\u05D3\u05D1\u05E8\u05D9\u0020\u05D4\u05D1\u05DC\u05D9\u0020\u05D4"
u"\u05E2\u05D5\u05DC\u05DD\u002C"
u"\u0020\u05D5\u05E1\u05DE\u05DA\u0020\u05D4\u05B2\u05D1\u05B5\u05DC"
u"\u0020\u05D0\u05DC\u0020\u05D4"
u"\u05D1\u05DC\u05D9\u05DD\u0020\u05D5\u05D0\u05DD\u0020\u05DC\u05D0"
u"\u0020\u05D9\u05DE\u05E6\u05D0"
u"\u0020\u05DE\u05D4\u05E9\u05DE\u05D5\u05EA\u0020\u05E9\u05D4\u05DD"
u"\u0020\u05E2\u05DC\u0020\u05DE"
u"\u05E9\u05E7\u05DC\u0020\u05D0\u05E8\u05E5\u0020\u05E9\u05D9\u05E9"
u"\u05EA\u05E0\u05D4\u0020\u05D7"
u"\u05D5\u05E5\u0020\u05DE\u05B5\u05D7\u05B2\u05D3\u05B7\u05E8\u0020"
u"\u05DE\u05B4\u05E9\u05B0\u05C1"
u"\u05DB\u05B8\u05D1\u05B0\u05DA\u05B8\u0020\u0028\u05E9\u05DE\u05D5"
u"\u05EA\u0020\u05D6\u05F3\u003A"
u"\u05DB\u05F4\u05D7\u0029";
TextDirection direction = TextDirection::kRtl;
HarfBuzzShaper shaper(string);
const RunSegmenter::RunSegmenterRange range = {
51, 131, USCRIPT_HEBREW, blink::OrientationIterator::kOrientationKeep,
blink::FontFallbackPriority::kText};
scoped_refptr<ShapeResult> shape_result =
shaper.Shape(&font, direction, 51, 131, range);
unsigned start_offset = 59;
unsigned end_offset = 118;
scoped_refptr<const ShapeResultView> result_view =
ShapeResultView::Create(shape_result.get(), start_offset, end_offset);
scoped_refptr<ShapeResult> result = result_view->CreateShapeResult();
unsigned offset = end_offset;
do {
unsigned safe = result_view->PreviousSafeToBreakOffset(offset);
unsigned cached_safe = result->CachedPreviousSafeToBreakOffset(offset);
EXPECT_EQ(safe, cached_safe);
} while (--offset > start_offset);
}
TEST_F(ShapeResultViewTest, LatinMultiRun) {
TextDirection direction = TextDirection::kLtr;
HarfBuzzShaper shaper_a(To16Bit("hello", 5));
HarfBuzzShaper shaper_b(To16Bit(" w", 2));
HarfBuzzShaper shaper_c(To16Bit("orld", 4));
HarfBuzzShaper shaper_d(To16Bit("!", 1));
// Combine four separate results into a single one to ensure we have a result
// with multiple runs: "hello world!"
scoped_refptr<ShapeResult> result =
ShapeResult::Create(&font, 0, 0, direction);
shaper_a.Shape(&font, direction)->CopyRange(0u, 5u, result.get());
shaper_b.Shape(&font, direction)->CopyRange(0u, 2u, result.get());
shaper_c.Shape(&font, direction)->CopyRange(0u, 4u, result.get());
shaper_d.Shape(&font, direction)->CopyRange(0u, 1u, result.get());
Vector<ShapeResultTestGlyphInfo> result_glyphs;
result->ForEachGlyph(0, AddGlyphInfo, static_cast<void*>(&result_glyphs));
// Create composite view out of multiple segments where at least some of the
// segments have multiple runs: "hello wood wold!"
ShapeResultView::Segment segments[5] = {
{result.get(), 0, 8}, // "hello wo"
{result.get(), 7, 8}, // "o"
{result.get(), 10, 11}, // "d"
{result.get(), 5, 8}, // " wo"
{result.get(), 9, 12}, // "ld!"
};
auto composite_view = ShapeResultView::Create(&segments[0], 5);
Vector<ShapeResultTestGlyphInfo> view_glyphs;
composite_view->ForEachGlyph(0, AddGlyphInfo,
static_cast<void*>(&view_glyphs));
EXPECT_EQ(composite_view->StartIndex(), 0u);
EXPECT_EQ(composite_view->NumCharacters(), 16u);
EXPECT_EQ(composite_view->NumGlyphs(), 16u);
EXPECT_EQ(view_glyphs.size(), 16u);
HarfBuzzShaper shaper2(To16Bit("hello world!", 12));
scoped_refptr<const ShapeResult> result2 = shaper2.Shape(&font, direction);
Vector<ShapeResultTestGlyphInfo> glyphs2;
result2->ForEachGlyph(0, AddGlyphInfo, static_cast<void*>(&glyphs2));
EXPECT_TRUE(CompareResultGlyphs(result_glyphs, glyphs2, 0u, 12u));
HarfBuzzShaper reference_shaper(To16Bit("hello wood wold!", 16));
scoped_refptr<const ShapeResult> reference_result =
reference_shaper.Shape(&font, direction);
Vector<ShapeResultTestGlyphInfo> reference_glyphs;
reference_result->ForEachGlyph(0, AddGlyphInfo,
static_cast<void*>(&reference_glyphs));
scoped_refptr<ShapeResult> composite_copy =
ShapeResult::Create(&font, 0, 0, direction);
result->CopyRange(0, 8, composite_copy.get());
result->CopyRange(7, 8, composite_copy.get());
result->CopyRange(10, 11, composite_copy.get());
result->CopyRange(5, 8, composite_copy.get());
result->CopyRange(9, 12, composite_copy.get());
Vector<ShapeResultTestGlyphInfo> composite_copy_glyphs;
composite_copy->ForEachGlyph(0, AddGlyphInfo,
static_cast<void*>(&composite_copy_glyphs));
EXPECT_TRUE(CompareResultGlyphs(view_glyphs, reference_glyphs, 0u, 16u));
EXPECT_TRUE(
CompareResultGlyphs(composite_copy_glyphs, reference_glyphs, 0u, 16u));
EXPECT_EQ(composite_view->Width(), composite_copy->Width());
}
TEST_F(ShapeResultViewTest, LatinCompositeView) {
String string =
To16Bit("Test run with multiple words and breaking opportunities.", 56);
TextDirection direction = TextDirection::kLtr;
HarfBuzzShaper shaper(string);
scoped_refptr<const ShapeResult> result = shaper.Shape(&font, direction);
Vector<ShapeResultTestGlyphInfo> glyphs;
result->ForEachGlyph(0, AddGlyphInfo, static_cast<void*>(&glyphs));
String reference_string = To16Bit("multiple breaking opportunities Test", 36);
HarfBuzzShaper reference_shaper(reference_string);
scoped_refptr<const ShapeResult> reference_result =
reference_shaper.Shape(&font, direction);
Vector<ShapeResultTestGlyphInfo> reference_glyphs;
// Match the character index logic of ShapeResult::CopyRange where the the
// character index of the first result is preserved and all subsequent ones
// are adjusted to be sequential.
// TODO(layout-dev): Arguably both should be updated to renumber the first
// result as well but some callers depend on the existing behavior.
scoped_refptr<ShapeResult> composite_copy =
ShapeResult::Create(&font, 0, 0, direction);
result->CopyRange(14, 23, composite_copy.get());
result->CopyRange(33, 55, composite_copy.get());
result->CopyRange(4, 5, composite_copy.get());
result->CopyRange(0, 4, composite_copy.get());
EXPECT_EQ(composite_copy->NumCharacters(), reference_result->NumCharacters());
EXPECT_EQ(composite_copy->NumGlyphs(), reference_result->NumGlyphs());
composite_copy->ForEachGlyph(0, AddGlyphInfo,
static_cast<void*>(&reference_glyphs));
// Create composite view out of multiple segments:
ShapeResultView::Segment segments[4] = {
{result.get(), 14, 23}, // "multiple "
{result.get(), 33, 55}, // "breaking opportunities"
{result.get(), 4, 5}, // " "
{result.get(), 0, 4} // "Test"
};
auto composite_view = ShapeResultView::Create(&segments[0], 4);
EXPECT_EQ(composite_view->StartIndex(), composite_copy->StartIndex());
EXPECT_EQ(composite_view->NumCharacters(), reference_result->NumCharacters());
EXPECT_EQ(composite_view->NumGlyphs(), reference_result->NumGlyphs());
Vector<ShapeResultTestGlyphInfo> composite_glyphs;
composite_view->ForEachGlyph(0, AddGlyphInfo,
static_cast<void*>(&composite_glyphs));
EXPECT_EQ(composite_glyphs.size(), 36u);
EXPECT_TRUE(CompareResultGlyphs(composite_glyphs, reference_glyphs, 0u, 22u));
EXPECT_EQ(composite_view->Width(), composite_copy->Width());
}
TEST_F(ShapeResultViewTest, MixedScriptsCompositeView) {
String string_a = To16Bit("Test with multiple 字体 ", 22);
String string_b = To16Bit("and 本書.", 7);
TextDirection direction = TextDirection::kLtr;
HarfBuzzShaper shaper_a(string_a);
scoped_refptr<const ShapeResult> result_a = shaper_a.Shape(&font, direction);
HarfBuzzShaper shaper_b(string_b);
scoped_refptr<const ShapeResult> result_b = shaper_b.Shape(&font, direction);
String reference_string = To16Bit("Test with multiple 字体 and 本書.", 29);
HarfBuzzShaper reference_shaper(reference_string);
scoped_refptr<const ShapeResult> reference_result =
reference_shaper.Shape(&font, direction);
// Create a copy using CopyRange and compare with that to ensure that the same
// fonts are used for both the composite and the reference. The combined
// reference_result data might use different fonts, resulting in different
// glyph ids and metrics.
scoped_refptr<ShapeResult> composite_copy =
ShapeResult::Create(&font, 0, 0, direction);
result_a->CopyRange(0, 22, composite_copy.get());
result_b->CopyRange(0, 7, composite_copy.get());
EXPECT_EQ(composite_copy->NumCharacters(), reference_result->NumCharacters());
EXPECT_EQ(composite_copy->NumGlyphs(), reference_result->NumGlyphs());
Vector<ShapeResultTestGlyphInfo> reference_glyphs;
composite_copy->ForEachGlyph(0, AddGlyphInfo,
static_cast<void*>(&reference_glyphs));
ShapeResultView::Segment segments[4] = {{result_a.get(), 0, 22},
{result_b.get(), 0, 7}};
auto composite_view = ShapeResultView::Create(&segments[0], 2);
EXPECT_EQ(composite_view->StartIndex(), 0u);
EXPECT_EQ(composite_view->NumCharacters(), reference_result->NumCharacters());
EXPECT_EQ(composite_view->NumGlyphs(), reference_result->NumGlyphs());
Vector<ShapeResultTestGlyphInfo> composite_glyphs;
composite_view->ForEachGlyph(0, AddGlyphInfo,
static_cast<void*>(&composite_glyphs));
EXPECT_TRUE(CompareResultGlyphs(composite_glyphs, reference_glyphs, 0u,
reference_glyphs.size()));
EXPECT_EQ(composite_view->Width(), composite_copy->Width());
}
TEST_F(ShapeResultViewTest, TrimEndOfView) {
String string = To16Bit("12345678901234567890", 20);
TextDirection direction = TextDirection::kLtr;
HarfBuzzShaper shaper(string);
scoped_refptr<const ShapeResult> result = shaper.Shape(&font, direction);
// Create a view from 5 to 20.
scoped_refptr<const ShapeResultView> view1 =
ShapeResultView::Create(result.get(), 5, 20);
EXPECT_EQ(view1->NumCharacters(), 15u);
EXPECT_EQ(view1->NumGlyphs(), 15u);
// Trim the last character from the view.
scoped_refptr<const ShapeResultView> view2 =
ShapeResultView::Create(view1.get(), 5, 19);
EXPECT_EQ(view2->NumCharacters(), 14u);
EXPECT_EQ(view2->NumGlyphs(), 14u);
}
} // namespace blink