blob: 6ab2024741d580612b4e95a3a084d506a76ebf54 [file] [log] [blame]
// Copyright (c) 2014 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/caching_word_shaper.h"
#include <memory>
#include "base/stl_util.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/fonts/font_cache.h"
#include "third_party/blink/renderer/platform/fonts/shaping/caching_word_shape_iterator.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h"
namespace blink {
class CachingWordShaperTest : public testing::Test {
protected:
void SetUp() override {
font_description.SetComputedSize(12.0);
font_description.SetLocale(LayoutLocale::Get("en"));
ASSERT_EQ(USCRIPT_LATIN, font_description.GetScript());
font_description.SetGenericFamily(FontDescription::kStandardFamily);
font = Font(font_description);
ASSERT_TRUE(font.CanShapeWordByWord());
fallback_fonts = nullptr;
cache = std::make_unique<ShapeCache>();
}
base::test::TaskEnvironment task_environment_;
FontCachePurgePreventer font_cache_purge_preventer;
FontDescription font_description;
Font font;
std::unique_ptr<ShapeCache> cache;
HashSet<const SimpleFontData*>* fallback_fonts;
unsigned start_index = 0;
unsigned num_glyphs = 0;
hb_script_t script = HB_SCRIPT_INVALID;
};
static inline const ShapeResultTestInfo* TestInfo(
scoped_refptr<const ShapeResult>& result) {
return static_cast<const ShapeResultTestInfo*>(result.get());
}
TEST_F(CachingWordShaperTest, LatinLeftToRightByWord) {
TextRun text_run(reinterpret_cast<const LChar*>("ABC DEF."), 8);
scoped_refptr<const ShapeResult> result;
CachingWordShapeIterator iterator(cache.get(), text_run, &font);
ASSERT_TRUE(iterator.Next(&result));
ASSERT_TRUE(
TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script));
EXPECT_EQ(0u, start_index);
EXPECT_EQ(3u, num_glyphs);
EXPECT_EQ(HB_SCRIPT_LATIN, script);
ASSERT_TRUE(iterator.Next(&result));
ASSERT_TRUE(
TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script));
EXPECT_EQ(0u, start_index);
EXPECT_EQ(1u, num_glyphs);
EXPECT_EQ(HB_SCRIPT_COMMON, script);
ASSERT_TRUE(iterator.Next(&result));
ASSERT_TRUE(
TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script));
EXPECT_EQ(0u, start_index);
EXPECT_EQ(4u, num_glyphs);
EXPECT_EQ(HB_SCRIPT_LATIN, script);
ASSERT_FALSE(iterator.Next(&result));
}
TEST_F(CachingWordShaperTest, CommonAccentLeftToRightByWord) {
const UChar kStr[] = {0x2F, 0x301, 0x2E, 0x20, 0x2E, 0x0};
TextRun text_run(kStr, 5);
unsigned offset = 0;
scoped_refptr<const ShapeResult> result;
CachingWordShapeIterator iterator(cache.get(), text_run, &font);
ASSERT_TRUE(iterator.Next(&result));
ASSERT_TRUE(
TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script));
EXPECT_EQ(0u, offset + start_index);
EXPECT_EQ(3u, num_glyphs);
EXPECT_EQ(HB_SCRIPT_COMMON, script);
offset += result->NumCharacters();
ASSERT_TRUE(iterator.Next(&result));
ASSERT_TRUE(
TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script));
EXPECT_EQ(3u, offset + start_index);
EXPECT_EQ(1u, num_glyphs);
EXPECT_EQ(HB_SCRIPT_COMMON, script);
offset += result->NumCharacters();
ASSERT_TRUE(iterator.Next(&result));
ASSERT_TRUE(
TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script));
EXPECT_EQ(4u, offset + start_index);
EXPECT_EQ(1u, num_glyphs);
EXPECT_EQ(HB_SCRIPT_COMMON, script);
offset += result->NumCharacters();
ASSERT_EQ(5u, offset);
ASSERT_FALSE(iterator.Next(&result));
}
TEST_F(CachingWordShaperTest, SegmentCJKByCharacter) {
const UChar kStr[] = {0x56FD, 0x56FD, // CJK Unified Ideograph
'a', 'b',
0x56FD, // CJK Unified Ideograph
'x', 'y', 'z',
0x3042, // HIRAGANA LETTER A
0x56FD, // CJK Unified Ideograph
0x0};
TextRun text_run(kStr, 10);
scoped_refptr<const ShapeResult> word_result;
CachingWordShapeIterator iterator(cache.get(), text_run, &font);
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(1u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(1u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(2u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(1u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(3u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(1u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(1u, word_result->NumCharacters());
ASSERT_FALSE(iterator.Next(&word_result));
}
TEST_F(CachingWordShaperTest, SegmentCJKAndCommon) {
const UChar kStr[] = {'a', 'b',
0xFF08, // FULLWIDTH LEFT PARENTHESIS (script=common)
0x56FD, // CJK Unified Ideograph
0x56FD, // CJK Unified Ideograph
0x56FD, // CJK Unified Ideograph
0x3002, // IDEOGRAPHIC FULL STOP (script=common)
0x0};
TextRun text_run(kStr, 7);
scoped_refptr<const ShapeResult> word_result;
CachingWordShapeIterator iterator(cache.get(), text_run, &font);
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(2u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(2u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(1u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(2u, word_result->NumCharacters());
ASSERT_FALSE(iterator.Next(&word_result));
}
TEST_F(CachingWordShaperTest, SegmentCJKAndInherit) {
const UChar kStr[] = {
0x304B, // HIRAGANA LETTER KA
0x304B, // HIRAGANA LETTER KA
0x3009, // COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK
0x304B, // HIRAGANA LETTER KA
0x0};
TextRun text_run(kStr, 4);
scoped_refptr<const ShapeResult> word_result;
CachingWordShapeIterator iterator(cache.get(), text_run, &font);
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(1u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(2u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(1u, word_result->NumCharacters());
ASSERT_FALSE(iterator.Next(&word_result));
}
TEST_F(CachingWordShaperTest, SegmentCJKAndNonCJKCommon) {
const UChar kStr[] = {0x56FD, // CJK Unified Ideograph
' ', 0x0};
TextRun text_run(kStr, 2);
scoped_refptr<const ShapeResult> word_result;
CachingWordShapeIterator iterator(cache.get(), text_run, &font);
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(1u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(1u, word_result->NumCharacters());
ASSERT_FALSE(iterator.Next(&word_result));
}
TEST_F(CachingWordShaperTest, SegmentEmojiSequences) {
std::vector<std::string> test_strings = {
// A family followed by a couple with heart emoji sequence,
// the latter including a variation selector.
u8"\U0001f468\u200D\U0001f469\u200D\U0001f467\u200D\U0001f466\U0001f469"
u8"\u200D\u2764\uFE0F\u200D\U0001f48b\u200D\U0001f468",
// Pirate flag
u8"\U0001F3F4\u200D\u2620\uFE0F",
// Pilot, judge sequence
u8"\U0001f468\U0001f3fb\u200D\u2696\uFE0F\U0001f468\U0001f3fb\u200D\u2708"
u8"\uFE0F",
// Woman, Kiss, Man sequence
u8"\U0001f469\u200D\u2764\uFE0F\u200D\U0001f48b\u200D\U0001f468",
// Signs of horns with skin tone modifier
u8"\U0001f918\U0001f3fb",
// Man, dark skin tone, red hair
u8"\U0001f468\U0001f3ff\u200D\U0001f9b0"};
for (auto test_string : test_strings) {
String emoji_string = String::FromUTF8(test_string);
TextRun text_run(emoji_string);
scoped_refptr<const ShapeResult> word_result;
CachingWordShapeIterator iterator(cache.get(), text_run, &font);
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(emoji_string.length(), word_result->NumCharacters())
<< " Length mismatch for sequence: " << test_string;
ASSERT_FALSE(iterator.Next(&word_result));
}
}
TEST_F(CachingWordShaperTest, SegmentEmojiExtraZWJPrefix) {
// A ZWJ, followed by a family and a heart-kiss sequence.
const UChar kStr[] = {0x200D, 0xD83D, 0xDC68, 0x200D, 0xD83D, 0xDC69,
0x200D, 0xD83D, 0xDC67, 0x200D, 0xD83D, 0xDC66,
0xD83D, 0xDC69, 0x200D, 0x2764, 0xFE0F, 0x200D,
0xD83D, 0xDC8B, 0x200D, 0xD83D, 0xDC68, 0x0};
TextRun text_run(kStr, 23);
scoped_refptr<const ShapeResult> word_result;
CachingWordShapeIterator iterator(cache.get(), text_run, &font);
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(1u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(22u, word_result->NumCharacters());
ASSERT_FALSE(iterator.Next(&word_result));
}
TEST_F(CachingWordShaperTest, SegmentEmojiSubdivisionFlags) {
// Subdivision flags for Wales, Scotland, England.
const UChar kStr[] = {0xD83C, 0xDFF4, 0xDB40, 0xDC67, 0xDB40, 0xDC62, 0xDB40,
0xDC77, 0xDB40, 0xDC6C, 0xDB40, 0xDC73, 0xDB40, 0xDC7F,
0xD83C, 0xDFF4, 0xDB40, 0xDC67, 0xDB40, 0xDC62, 0xDB40,
0xDC73, 0xDB40, 0xDC63, 0xDB40, 0xDC74, 0xDB40, 0xDC7F,
0xD83C, 0xDFF4, 0xDB40, 0xDC67, 0xDB40, 0xDC62, 0xDB40,
0xDC65, 0xDB40, 0xDC6E, 0xDB40, 0xDC67, 0xDB40, 0xDC7F};
TextRun text_run(kStr, base::size(kStr));
scoped_refptr<const ShapeResult> word_result;
CachingWordShapeIterator iterator(cache.get(), text_run, &font);
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(42u, word_result->NumCharacters());
ASSERT_FALSE(iterator.Next(&word_result));
}
TEST_F(CachingWordShaperTest, SegmentCJKCommon) {
const UChar kStr[] = {0xFF08, // FULLWIDTH LEFT PARENTHESIS (script=common)
0xFF08, // FULLWIDTH LEFT PARENTHESIS (script=common)
0xFF08, // FULLWIDTH LEFT PARENTHESIS (script=common)
0x0};
TextRun text_run(kStr, 3);
scoped_refptr<const ShapeResult> word_result;
CachingWordShapeIterator iterator(cache.get(), text_run, &font);
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(3u, word_result->NumCharacters());
ASSERT_FALSE(iterator.Next(&word_result));
}
TEST_F(CachingWordShaperTest, SegmentCJKCommonAndNonCJK) {
const UChar kStr[] = {0xFF08, // FULLWIDTH LEFT PARENTHESIS (script=common)
'a', 'b', 0x0};
TextRun text_run(kStr, 3);
scoped_refptr<const ShapeResult> word_result;
CachingWordShapeIterator iterator(cache.get(), text_run, &font);
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(1u, word_result->NumCharacters());
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(2u, word_result->NumCharacters());
ASSERT_FALSE(iterator.Next(&word_result));
}
TEST_F(CachingWordShaperTest, SegmentCJKSmallFormVariants) {
const UChar kStr[] = {0x5916, // CJK UNIFIED IDEOGRPAH
0xFE50, // SMALL COMMA
0x0};
TextRun text_run(kStr, 2);
scoped_refptr<const ShapeResult> word_result;
CachingWordShapeIterator iterator(cache.get(), text_run, &font);
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(2u, word_result->NumCharacters());
ASSERT_FALSE(iterator.Next(&word_result));
}
TEST_F(CachingWordShaperTest, SegmentHangulToneMark) {
const UChar kStr[] = {0xC740, // HANGUL SYLLABLE EUN
0x302E, // HANGUL SINGLE DOT TONE MARK
0x0};
TextRun text_run(kStr, 2);
scoped_refptr<const ShapeResult> word_result;
CachingWordShapeIterator iterator(cache.get(), text_run, &font);
ASSERT_TRUE(iterator.Next(&word_result));
EXPECT_EQ(2u, word_result->NumCharacters());
ASSERT_FALSE(iterator.Next(&word_result));
}
TEST_F(CachingWordShaperTest, TextOrientationFallbackShouldNotInFallbackList) {
const UChar kStr[] = {
'A', // code point for verticalRightOrientationFontData()
// Ideally we'd like to test uprightOrientationFontData() too
// using code point such as U+3042, but it'd fallback to system
// fonts as the glyph is missing.
0x0};
TextRun text_run(kStr, 1);
font_description.SetOrientation(FontOrientation::kVerticalMixed);
Font vertical_mixed_font = Font(font_description);
ASSERT_TRUE(vertical_mixed_font.CanShapeWordByWord());
CachingWordShaper shaper(vertical_mixed_font);
FloatRect glyph_bounds;
HashSet<const SimpleFontData*> fallback_fonts;
ASSERT_GT(shaper.Width(text_run, &fallback_fonts, &glyph_bounds), 0);
EXPECT_EQ(0u, fallback_fonts.size());
}
TEST_F(CachingWordShaperTest, GlyphBoundsWithSpaces) {
CachingWordShaper shaper(font);
TextRun periods(reinterpret_cast<const LChar*>(".........."), 10);
FloatRect periods_glyph_bounds;
float periods_width = shaper.Width(periods, nullptr, &periods_glyph_bounds);
TextRun periods_and_spaces(
reinterpret_cast<const LChar*>(". . . . . . . . . ."), 19);
FloatRect periods_and_spaces_glyph_bounds;
float periods_and_spaces_width = shaper.Width(
periods_and_spaces, nullptr, &periods_and_spaces_glyph_bounds);
// The total width of periods and spaces should be longer than the width of
// periods alone.
ASSERT_GT(periods_and_spaces_width, periods_width);
// The glyph bounds of periods and spaces should be longer than the glyph
// bounds of periods alone.
ASSERT_GT(periods_and_spaces_glyph_bounds.Width(),
periods_glyph_bounds.Width());
}
} // namespace blink