summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/platform/fonts/shaping
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/fonts/shaping')
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shape_iterator.cc33
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shape_iterator.h218
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper.cc136
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper.h75
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper_test.cc422
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/case_mapping_harf_buzz_buffer_filler.cc94
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/case_mapping_harf_buzz_buffer_filler.h42
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_face.cc448
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_face.h87
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_font_cache.h151
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.cc947
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h109
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper_fuzzer.cc52
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper_test.cc1258
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/run_segmenter.cc108
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/run_segmenter.h67
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/run_segmenter_test.cc251
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_cache.cc42
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_cache.h252
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc953
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result.h212
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.cc420
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.h170
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer_test.cc356
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.cc235
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h77
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h229
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.cc195
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h80
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.cc66
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h71
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc390
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.h116
-rw-r--r--chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker_test.cc344
34 files changed, 8706 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shape_iterator.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shape_iterator.cc
new file mode 100644
index 00000000000..0c56ee41fa9
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shape_iterator.cc
@@ -0,0 +1,33 @@
+// Copyright 2016 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_shape_iterator.h"
+
+#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h"
+
+namespace blink {
+
+scoped_refptr<const ShapeResult> CachingWordShapeIterator::ShapeWordWithoutSpacing(
+ const TextRun& word_run,
+ const Font* font) {
+ ShapeCacheEntry* cache_entry = shape_cache_->Add(word_run, ShapeCacheEntry());
+ if (cache_entry && cache_entry->shape_result_)
+ return cache_entry->shape_result_;
+
+ unsigned word_length = 0;
+ std::unique_ptr<UChar[]> word_text = word_run.NormalizedUTF16(&word_length);
+
+ HarfBuzzShaper shaper(word_text.get(), word_length);
+ scoped_refptr<const ShapeResult> shape_result =
+ shaper.Shape(font, word_run.Direction());
+ if (!shape_result)
+ return nullptr;
+
+ if (cache_entry)
+ cache_entry->shape_result_ = shape_result;
+
+ return shape_result;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shape_iterator.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shape_iterator.h
new file mode 100644
index 00000000000..1fb7b8d921e
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shape_iterator.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2015 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_CACHING_WORD_SHAPE_ITERATOR_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_CACHING_WORD_SHAPE_ITERATOR_H_
+
+#include "third_party/blink/renderer/platform/fonts/font.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/caching_word_shape_iterator.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_cache.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h"
+#include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/text/character_names.h"
+
+namespace blink {
+
+class PLATFORM_EXPORT CachingWordShapeIterator final {
+ STACK_ALLOCATED();
+ WTF_MAKE_NONCOPYABLE(CachingWordShapeIterator);
+
+ public:
+ CachingWordShapeIterator(ShapeCache* cache,
+ const TextRun& run,
+ const Font* font)
+ : shape_cache_(cache),
+ text_run_(run),
+ font_(font),
+ spacing_(run),
+ width_so_far_(0),
+ start_index_(0) {
+ DCHECK(font);
+
+ // Shaping word by word is faster as each word is cached. If we cannot
+ // use the cache or if the font doesn't support word by word shaping
+ // fall back on shaping the entire run.
+ shape_by_word_ = font_->CanShapeWordByWord();
+
+ // SVG sets SpacingDisabled because it handles spacing by themselves.
+ if (!run.SpacingDisabled())
+ spacing_.SetSpacingAndExpansion(font->GetFontDescription());
+ }
+
+ bool Next(scoped_refptr<const ShapeResult>* word_result) {
+ if (UNLIKELY(text_run_.AllowTabs()))
+ return NextForAllowTabs(word_result);
+
+ if (!shape_by_word_) {
+ if (start_index_)
+ return false;
+ *word_result = ShapeWord(text_run_, font_);
+ start_index_ = 1;
+ return word_result->get();
+ }
+
+ return NextWord(word_result);
+ }
+
+ private:
+ scoped_refptr<const ShapeResult> ShapeWordWithoutSpacing(const TextRun&,
+ const Font*);
+
+ scoped_refptr<const ShapeResult> ShapeWord(const TextRun& word_run,
+ const Font* font) {
+ if (LIKELY(!spacing_.HasSpacing()))
+ return ShapeWordWithoutSpacing(word_run, font);
+
+ scoped_refptr<const ShapeResult> result = ShapeWordWithoutSpacing(word_run, font);
+ return result->ApplySpacingToCopy(spacing_, word_run);
+ }
+
+ bool NextWord(scoped_refptr<const ShapeResult>* word_result) {
+ return ShapeToEndIndex(word_result, NextWordEndIndex());
+ }
+
+ static bool IsWordDelimiter(UChar ch) {
+ return ch == kSpaceCharacter || ch == kTabulationCharacter;
+ }
+
+ unsigned NextWordEndIndex() const {
+ const unsigned length = text_run_.length();
+ if (start_index_ >= length)
+ return 0;
+
+ if (start_index_ + 1u == length || IsWordDelimiter(text_run_[start_index_]))
+ return start_index_ + 1;
+
+ // 8Bit words end at isWordDelimiter().
+ if (text_run_.Is8Bit()) {
+ for (unsigned i = start_index_ + 1;; i++) {
+ if (i == length || IsWordDelimiter(text_run_[i]))
+ return i;
+ }
+ }
+
+ // Non-CJK/Emoji words end at isWordDelimiter() or CJK/Emoji characters.
+ unsigned end = start_index_;
+ UChar32 ch = text_run_.CodepointAtAndNext(end);
+ if (!Character::IsCJKIdeographOrSymbol(ch)) {
+ for (unsigned next_end = end; end < length; end = next_end) {
+ ch = text_run_.CodepointAtAndNext(next_end);
+ if (IsWordDelimiter(ch) || Character::IsCJKIdeographOrSymbolBase(ch))
+ return end;
+ }
+ return length;
+ }
+
+ // For CJK/Emoji words, delimit every character because these scripts do
+ // not delimit words by spaces, and delimiting only at isWordDelimiter()
+ // worsen the cache efficiency.
+ bool has_any_script = !Character::IsCommonOrInheritedScript(ch);
+ for (unsigned next_end = end; end < length; end = next_end) {
+ ch = text_run_.CodepointAtAndNext(next_end);
+ // ZWJ and modifier check in order not to split those Emoji sequences.
+ if (U_GET_GC_MASK(ch) & (U_GC_M_MASK | U_GC_LM_MASK | U_GC_SK_MASK) ||
+ ch == kZeroWidthJoinerCharacter || Character::IsModifier(ch) ||
+ Character::IsEmojiFlagSequenceTag(ch))
+ continue;
+ // Avoid delimiting COMMON/INHERITED alone, which makes harder to
+ // identify the script.
+ if (Character::IsCJKIdeographOrSymbol(ch)) {
+ if (Character::IsCommonOrInheritedScript(ch))
+ continue;
+ if (!has_any_script) {
+ has_any_script = true;
+ continue;
+ }
+ }
+ return end;
+ }
+ return length;
+ }
+
+ bool ShapeToEndIndex(scoped_refptr<const ShapeResult>* result, unsigned end_index) {
+ if (!end_index || end_index <= start_index_)
+ return false;
+
+ const unsigned length = text_run_.length();
+ if (!start_index_ && end_index == length) {
+ *result = ShapeWord(text_run_, font_);
+ } else {
+ DCHECK_LE(end_index, length);
+ TextRun sub_run =
+ text_run_.SubRun(start_index_, end_index - start_index_);
+ *result = ShapeWord(sub_run, font_);
+ }
+ start_index_ = end_index;
+ return result->get();
+ }
+
+ unsigned EndIndexUntil(UChar ch) const {
+ unsigned length = text_run_.length();
+ DCHECK_LT(start_index_, length);
+ for (unsigned i = start_index_ + 1;; i++) {
+ if (i == length || text_run_[i] == ch)
+ return i;
+ }
+ }
+
+ bool NextForAllowTabs(scoped_refptr<const ShapeResult>* word_result) {
+ unsigned length = text_run_.length();
+ if (start_index_ >= length)
+ return false;
+
+ if (UNLIKELY(text_run_[start_index_] == kTabulationCharacter)) {
+ for (unsigned i = start_index_ + 1;; i++) {
+ if (i == length || text_run_[i] != kTabulationCharacter) {
+ *word_result = ShapeResult::CreateForTabulationCharacters(
+ font_, text_run_, width_so_far_, i - start_index_);
+ start_index_ = i;
+ break;
+ }
+ }
+ } else if (!shape_by_word_) {
+ if (!ShapeToEndIndex(word_result, EndIndexUntil(kTabulationCharacter)))
+ return false;
+ } else {
+ if (!NextWord(word_result))
+ return false;
+ }
+ DCHECK(*word_result);
+ width_so_far_ += (*word_result)->Width();
+ return true;
+ }
+
+ ShapeCache* shape_cache_;
+ const TextRun& text_run_;
+ const Font* font_;
+ ShapeResultSpacing<TextRun> spacing_;
+ float width_so_far_; // Used only when allowTabs()
+ unsigned start_index_ : 31;
+ unsigned shape_by_word_ : 1;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_CACHING_WORD_SHAPE_ITERATOR_H_
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper.cc
new file mode 100644
index 00000000000..45e40d9d62b
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper.cc
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2015 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper.h"
+
+#include "third_party/blink/renderer/platform/fonts/character_range.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/caching_word_shape_iterator.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_cache.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h"
+#include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
+#include "third_party/blink/renderer/platform/wtf/text/character_names.h"
+
+namespace blink {
+
+ShapeCache* CachingWordShaper::GetShapeCache() const {
+ return font_.font_fallback_list_->GetShapeCache(font_.font_description_);
+}
+
+float CachingWordShaper::Width(const TextRun& run,
+ HashSet<const SimpleFontData*>* fallback_fonts,
+ FloatRect* glyph_bounds) {
+ float width = 0;
+ scoped_refptr<const ShapeResult> word_result;
+ CachingWordShapeIterator iterator(GetShapeCache(), run, &font_);
+ while (iterator.Next(&word_result)) {
+ if (word_result) {
+ if (glyph_bounds) {
+ FloatRect adjusted_bounds = word_result->Bounds();
+ // Translate glyph bounds to the current glyph position which
+ // is the total width before this glyph.
+ adjusted_bounds.SetX(adjusted_bounds.X() + width);
+ glyph_bounds->Unite(adjusted_bounds);
+ }
+ width += word_result->Width();
+ if (fallback_fonts)
+ word_result->FallbackFonts(fallback_fonts);
+ }
+ }
+
+ return width;
+}
+
+static inline float ShapeResultsForRun(ShapeCache* shape_cache,
+ const Font* font,
+ const TextRun& run,
+ ShapeResultBuffer* results_buffer) {
+ CachingWordShapeIterator iterator(shape_cache, run, font);
+ scoped_refptr<const ShapeResult> word_result;
+ float total_width = 0;
+ while (iterator.Next(&word_result)) {
+ if (word_result) {
+ total_width += word_result->Width();
+ results_buffer->AppendResult(std::move(word_result));
+ }
+ }
+ return total_width;
+}
+
+int CachingWordShaper::OffsetForPosition(const TextRun& run,
+ float target_x,
+ bool include_partial_glyphs) {
+ ShapeResultBuffer buffer;
+ ShapeResultsForRun(GetShapeCache(), &font_, run, &buffer);
+
+ return buffer.OffsetForPosition(run, target_x, include_partial_glyphs);
+}
+
+void CachingWordShaper::FillResultBuffer(const TextRunPaintInfo& run_info,
+ ShapeResultBuffer* buffer) {
+ DCHECK(buffer);
+ ShapeResultsForRun(GetShapeCache(), &font_, run_info.run, buffer);
+}
+
+CharacterRange CachingWordShaper::GetCharacterRange(const TextRun& run,
+ unsigned from,
+ unsigned to) {
+ ShapeResultBuffer buffer;
+ float total_width = ShapeResultsForRun(GetShapeCache(), &font_, run, &buffer);
+
+ return buffer.GetCharacterRange(run.Direction(), total_width, from, to);
+}
+
+Vector<CharacterRange> CachingWordShaper::IndividualCharacterRanges(
+ const TextRun& run) {
+ ShapeResultBuffer buffer;
+ float total_width = ShapeResultsForRun(GetShapeCache(), &font_, run, &buffer);
+
+ auto ranges = buffer.IndividualCharacterRanges(run.Direction(), total_width);
+ // The shaper can fail to return glyph metrics for all characters (see
+ // crbug.com/613915 and crbug.com/615661) so add empty ranges to ensure all
+ // characters have an associated range.
+ while (ranges.size() < static_cast<unsigned>(run.length()))
+ ranges.push_back(CharacterRange(0, 0, 0, 0));
+ return ranges;
+}
+
+Vector<ShapeResult::RunFontData> CachingWordShaper::GetRunFontData(
+ const TextRun& run) const {
+ ShapeResultBuffer buffer;
+ ShapeResultsForRun(GetShapeCache(), &font_, run, &buffer);
+
+ return buffer.GetRunFontData();
+}
+
+GlyphData CachingWordShaper::EmphasisMarkGlyphData(
+ const TextRun& emphasis_mark_run) const {
+ ShapeResultBuffer buffer;
+ ShapeResultsForRun(GetShapeCache(), &font_, emphasis_mark_run, &buffer);
+
+ return buffer.EmphasisMarkGlyphData(font_.font_description_);
+}
+
+}; // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper.h
new file mode 100644
index 00000000000..0b6a215fb45
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2015 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_CACHING_WORD_SHAPER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_CACHING_WORD_SHAPER_H_
+
+#include "base/memory/scoped_refptr.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h"
+#include "third_party/blink/renderer/platform/geometry/float_rect.h"
+#include "third_party/blink/renderer/platform/text/text_run.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+struct CharacterRange;
+class Font;
+class ShapeCache;
+class SimpleFontData;
+struct GlyphData;
+
+class PLATFORM_EXPORT CachingWordShaper final {
+ STACK_ALLOCATED();
+ WTF_MAKE_NONCOPYABLE(CachingWordShaper);
+
+ public:
+ explicit CachingWordShaper(const Font& font) : font_(font) {}
+ ~CachingWordShaper() = default;
+
+ float Width(const TextRun&,
+ HashSet<const SimpleFontData*>* fallback_fonts,
+ FloatRect* glyph_bounds);
+ int OffsetForPosition(const TextRun&,
+ float target_x,
+ bool include_partial_glyphs);
+
+ void FillResultBuffer(const TextRunPaintInfo&, ShapeResultBuffer*);
+ CharacterRange GetCharacterRange(const TextRun&, unsigned from, unsigned to);
+ Vector<CharacterRange> IndividualCharacterRanges(const TextRun&);
+
+ Vector<ShapeResult::RunFontData> GetRunFontData(const TextRun&) const;
+
+ GlyphData EmphasisMarkGlyphData(const TextRun&) const;
+
+ private:
+ ShapeCache* GetShapeCache() const;
+
+ const Font& font_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_CACHING_WORD_SHAPER_H_
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper_test.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper_test.cc
new file mode 100644
index 00000000000..2183a6ccd12
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper_test.cc
@@ -0,0 +1,422 @@
+// 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 "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);
+ font.Update(nullptr);
+ ASSERT_TRUE(font.CanShapeWordByWord());
+ fallback_fonts = nullptr;
+ cache = std::make_unique<ShapeCache>();
+ }
+
+ 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, SegmentEmojiZWJCommon) {
+ // A family followed by a couple with heart emoji sequence,
+ // the latter including a variation selector.
+ const UChar kStr[] = {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, 22);
+
+ scoped_refptr<const ShapeResult> word_result;
+ CachingWordShapeIterator iterator(cache.get(), text_run, &font);
+
+ ASSERT_TRUE(iterator.Next(&word_result));
+ EXPECT_EQ(22u, word_result->NumCharacters());
+
+ ASSERT_FALSE(iterator.Next(&word_result));
+}
+
+TEST_F(CachingWordShaperTest, SegmentEmojiPilotJudgeSequence) {
+ // A family followed by a couple with heart emoji sequence,
+ // the latter including a variation selector.
+ const UChar kStr[] = {0xD83D, 0xDC68, 0xD83C, 0xDFFB, 0x200D, 0x2696, 0xFE0F,
+ 0xD83D, 0xDC68, 0xD83C, 0xDFFB, 0x200D, 0x2708, 0xFE0F};
+ TextRun text_run(kStr, ARRAY_SIZE(kStr));
+
+ scoped_refptr<const ShapeResult> word_result;
+ CachingWordShapeIterator iterator(cache.get(), text_run, &font);
+
+ ASSERT_TRUE(iterator.Next(&word_result));
+ EXPECT_EQ(ARRAY_SIZE(kStr), word_result->NumCharacters());
+
+ ASSERT_FALSE(iterator.Next(&word_result));
+}
+
+TEST_F(CachingWordShaperTest, SegmentEmojiHeartZWJSequence) {
+ // A ZWJ, followed by two family ZWJ Sequences.
+ const UChar kStr[] = {0xD83D, 0xDC69, 0x200D, 0x2764, 0xFE0F, 0x200D,
+ 0xD83D, 0xDC8B, 0x200D, 0xD83D, 0xDC68, 0x0};
+ TextRun text_run(kStr, 11);
+
+ scoped_refptr<const ShapeResult> word_result;
+ CachingWordShapeIterator iterator(cache.get(), text_run, &font);
+
+ ASSERT_TRUE(iterator.Next(&word_result));
+ EXPECT_EQ(11u, word_result->NumCharacters());
+
+ ASSERT_FALSE(iterator.Next(&word_result));
+}
+
+TEST_F(CachingWordShaperTest, SegmentEmojiSignsOfHornsModifier) {
+ // A Sign of the Horns emoji, followed by a fitzpatrick modifer
+ const UChar kStr[] = {0xD83E, 0xDD18, 0xD83C, 0xDFFB, 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(4u, word_result->NumCharacters());
+
+ 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, ARRAY_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);
+ vertical_mixed_font.Update(nullptr);
+ 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
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/case_mapping_harf_buzz_buffer_filler.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/case_mapping_harf_buzz_buffer_filler.cc
new file mode 100644
index 00000000000..54ce1d432b1
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/case_mapping_harf_buzz_buffer_filler.cc
@@ -0,0 +1,94 @@
+// Copyright 2016 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/case_mapping_harf_buzz_buffer_filler.h"
+
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+static const uint16_t* ToUint16(const UChar* src) {
+ // FIXME: This relies on undefined behavior however it works on the
+ // current versions of all compilers we care about and avoids making
+ // a copy of the string.
+ static_assert(sizeof(UChar) == sizeof(uint16_t),
+ "UChar should be the same size as uint16_t");
+ return reinterpret_cast<const uint16_t*>(src);
+}
+
+CaseMappingHarfBuzzBufferFiller::CaseMappingHarfBuzzBufferFiller(
+ CaseMapIntend case_map_intend,
+ AtomicString locale,
+ hb_buffer_t* harf_buzz_buffer,
+ const UChar* buffer,
+ unsigned buffer_length,
+ unsigned start_index,
+ unsigned num_characters)
+ : harf_buzz_buffer_(harf_buzz_buffer) {
+ if (case_map_intend == CaseMapIntend::kKeepSameCase) {
+ hb_buffer_add_utf16(harf_buzz_buffer_, ToUint16(buffer), buffer_length,
+ start_index, num_characters);
+ } else {
+ String case_mapped_text;
+ if (case_map_intend == CaseMapIntend::kUpperCase) {
+ case_mapped_text = String(buffer, buffer_length).UpperUnicode(locale);
+ } else {
+ case_mapped_text = String(buffer, buffer_length).LowerUnicode(locale);
+ }
+
+ if (case_mapped_text.length() != buffer_length) {
+ FillSlowCase(case_map_intend, locale, buffer, buffer_length, start_index,
+ num_characters);
+ return;
+ }
+
+ DCHECK_EQ(case_mapped_text.length(), buffer_length);
+ DCHECK(!case_mapped_text.Is8Bit());
+ hb_buffer_add_utf16(harf_buzz_buffer_,
+ ToUint16(case_mapped_text.Characters16()),
+ buffer_length, start_index, num_characters);
+ }
+}
+
+// TODO(drott): crbug.com/623940 Fix lack of context sensitive case mapping
+// here.
+void CaseMappingHarfBuzzBufferFiller::FillSlowCase(
+ CaseMapIntend case_map_intend,
+ AtomicString locale,
+ const UChar* buffer,
+ unsigned buffer_length,
+ unsigned start_index,
+ unsigned num_characters) {
+ // Record pre-context.
+ hb_buffer_add_utf16(harf_buzz_buffer_, ToUint16(buffer), buffer_length,
+ start_index, 0);
+
+ for (unsigned char_index = start_index;
+ char_index < start_index + num_characters;) {
+ unsigned new_char_index = char_index;
+ U16_FWD_1(buffer, new_char_index, num_characters);
+ String char_by_char(&buffer[char_index], new_char_index - char_index);
+ String case_mapped_char;
+ if (case_map_intend == CaseMapIntend::kUpperCase)
+ case_mapped_char = char_by_char.UpperUnicode(locale);
+ else
+ case_mapped_char = char_by_char.LowerUnicode(locale);
+
+ for (unsigned j = 0; j < case_mapped_char.length();) {
+ UChar32 codepoint = 0;
+ U16_NEXT(case_mapped_char.Characters16(), j, case_mapped_char.length(),
+ codepoint);
+ // Add all characters of the case mapping result at the same cluster
+ // position.
+ hb_buffer_add(harf_buzz_buffer_, codepoint, char_index);
+ }
+ char_index = new_char_index;
+ }
+
+ // Record post-context
+ hb_buffer_add_utf16(harf_buzz_buffer_, ToUint16(buffer), buffer_length,
+ start_index + num_characters, 0);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/case_mapping_harf_buzz_buffer_filler.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/case_mapping_harf_buzz_buffer_filler.h
new file mode 100644
index 00000000000..ef32b1e61ea
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/case_mapping_harf_buzz_buffer_filler.h
@@ -0,0 +1,42 @@
+// Copyright 2015 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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_CASE_MAPPING_HARF_BUZZ_BUFFER_FILLER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_CASE_MAPPING_HARF_BUZZ_BUFFER_FILLER_H_
+
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
+#include "third_party/blink/renderer/platform/wtf/text/unicode.h"
+
+#include <hb.h>
+
+namespace blink {
+
+enum class CaseMapIntend { kKeepSameCase, kUpperCase, kLowerCase };
+
+class CaseMappingHarfBuzzBufferFiller {
+ STACK_ALLOCATED();
+
+ public:
+ CaseMappingHarfBuzzBufferFiller(CaseMapIntend,
+ AtomicString locale,
+ hb_buffer_t* harf_buzz_buffer,
+ const UChar* buffer,
+ unsigned buffer_length,
+ unsigned start_index,
+ unsigned num_characters);
+
+ private:
+ void FillSlowCase(CaseMapIntend,
+ AtomicString locale,
+ const UChar* buffer,
+ unsigned buffer_length,
+ unsigned start_index,
+ unsigned num_characters);
+ hb_buffer_t* harf_buzz_buffer_;
+};
+
+} // namespace blink
+
+#endif
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_face.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_face.cc
new file mode 100644
index 00000000000..74aa4743f32
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_face.cc
@@ -0,0 +1,448 @@
+/*
+ * Copyright (c) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_face.h"
+
+#include <memory>
+
+#include "build/build_config.h"
+#include "third_party/blink/renderer/platform/fonts/font_cache.h"
+#include "third_party/blink/renderer/platform/fonts/font_global_context.h"
+#include "third_party/blink/renderer/platform/fonts/font_platform_data.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_font_cache.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h"
+#include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
+#include "third_party/blink/renderer/platform/fonts/skia/skia_text_metrics.h"
+#include "third_party/blink/renderer/platform/fonts/unicode_range_set.h"
+#include "third_party/blink/renderer/platform/histogram.h"
+#include "third_party/blink/renderer/platform/resolution_units.h"
+#include "third_party/blink/renderer/platform/wtf/hash_map.h"
+#include "third_party/blink/renderer/platform/wtf/math_extras.h"
+
+#include <hb-ot.h>
+#include <hb.h>
+#if defined(OS_MACOSX)
+#include <hb-coretext.h>
+#endif
+
+#include <SkPaint.h>
+#include <SkPath.h>
+#include <SkPoint.h>
+#include <SkRect.h>
+#include <SkStream.h>
+#include <SkTypeface.h>
+
+namespace blink {
+
+void HbFontDeleter::operator()(hb_font_t* font) {
+ if (font)
+ hb_font_destroy(font);
+}
+
+void HbFaceDeleter::operator()(hb_face_t* face) {
+ if (face)
+ hb_face_destroy(face);
+}
+
+struct HbSetDeleter {
+ void operator()(hb_set_t* set) {
+ if (set)
+ hb_set_destroy(set);
+ }
+};
+
+using HbSetUniquePtr = std::unique_ptr<hb_set_t, HbSetDeleter>;
+
+static scoped_refptr<HbFontCacheEntry> CreateHbFontCacheEntry(hb_face_t*);
+
+HarfBuzzFace::HarfBuzzFace(FontPlatformData* platform_data, uint64_t unique_id)
+ : platform_data_(platform_data), unique_id_(unique_id) {
+ HarfBuzzFontCache::AddResult result =
+ FontGlobalContext::GetHarfBuzzFontCache().insert(unique_id_, nullptr);
+ if (result.is_new_entry) {
+ HbFaceUniquePtr face(CreateFace());
+ result.stored_value->value = CreateHbFontCacheEntry(face.get());
+ }
+ result.stored_value->value->AddRef();
+ unscaled_font_ = result.stored_value->value->HbFont();
+ harf_buzz_font_data_ = result.stored_value->value->HbFontData();
+}
+
+HarfBuzzFace::~HarfBuzzFace() {
+ HarfBuzzFontCache::iterator result =
+ FontGlobalContext::GetHarfBuzzFontCache().find(unique_id_);
+ SECURITY_DCHECK(result != FontGlobalContext::GetHarfBuzzFontCache().end());
+ DCHECK(!result.Get()->value->HasOneRef());
+ result.Get()->value->Release();
+ if (result.Get()->value->HasOneRef())
+ FontGlobalContext::GetHarfBuzzFontCache().erase(unique_id_);
+}
+
+static hb_bool_t HarfBuzzGetGlyph(hb_font_t* hb_font,
+ void* font_data,
+ hb_codepoint_t unicode,
+ hb_codepoint_t variation_selector,
+ hb_codepoint_t* glyph,
+ void* user_data) {
+ HarfBuzzFontData* hb_font_data =
+ reinterpret_cast<HarfBuzzFontData*>(font_data);
+
+ CHECK(hb_font_data);
+ if (hb_font_data->range_set_ && !hb_font_data->range_set_->Contains(unicode))
+ return false;
+
+ return hb_font_get_glyph(hb_font_get_parent(hb_font), unicode,
+ variation_selector, glyph);
+}
+
+static hb_position_t HarfBuzzGetGlyphHorizontalAdvance(hb_font_t* hb_font,
+ void* font_data,
+ hb_codepoint_t glyph,
+ void* user_data) {
+ HarfBuzzFontData* hb_font_data =
+ reinterpret_cast<HarfBuzzFontData*>(font_data);
+ hb_position_t advance = 0;
+
+ SkiaTextMetrics(&hb_font_data->paint_)
+ .GetGlyphWidthForHarfBuzz(glyph, &advance);
+ return advance;
+}
+
+static hb_bool_t HarfBuzzGetGlyphVerticalOrigin(hb_font_t* hb_font,
+ void* font_data,
+ hb_codepoint_t glyph,
+ hb_position_t* x,
+ hb_position_t* y,
+ void* user_data) {
+ HarfBuzzFontData* hb_font_data =
+ reinterpret_cast<HarfBuzzFontData*>(font_data);
+ scoped_refptr<OpenTypeVerticalData> vertical_data =
+ hb_font_data->VerticalData();
+ if (!vertical_data)
+ return false;
+
+ float result[] = {0, 0};
+ Glyph the_glyph = glyph;
+ vertical_data->GetVerticalTranslationsForGlyphs(hb_font_data->paint_,
+ &the_glyph, 1, result);
+ *x = SkiaTextMetrics::SkiaScalarToHarfBuzzPosition(-result[0]);
+ *y = SkiaTextMetrics::SkiaScalarToHarfBuzzPosition(-result[1]);
+ return true;
+}
+
+static hb_position_t HarfBuzzGetGlyphVerticalAdvance(hb_font_t* hb_font,
+ void* font_data,
+ hb_codepoint_t glyph,
+ void* user_data) {
+ HarfBuzzFontData* hb_font_data =
+ reinterpret_cast<HarfBuzzFontData*>(font_data);
+ scoped_refptr<OpenTypeVerticalData> vertical_data =
+ hb_font_data->VerticalData();
+ if (!vertical_data) {
+ return SkiaTextMetrics::SkiaScalarToHarfBuzzPosition(
+ hb_font_data->height_fallback_);
+ }
+
+ Glyph the_glyph = glyph;
+ float advance_height = -vertical_data->AdvanceHeight(the_glyph);
+ return SkiaTextMetrics::SkiaScalarToHarfBuzzPosition(
+ SkFloatToScalar(advance_height));
+}
+
+static hb_position_t HarfBuzzGetGlyphHorizontalKerning(
+ hb_font_t*,
+ void* font_data,
+ hb_codepoint_t left_glyph,
+ hb_codepoint_t right_glyph,
+ void*) {
+ HarfBuzzFontData* hb_font_data =
+ reinterpret_cast<HarfBuzzFontData*>(font_data);
+ if (hb_font_data->paint_.isVerticalText()) {
+ // We don't support cross-stream kerning
+ return 0;
+ }
+
+ SkTypeface* typeface = hb_font_data->paint_.getTypeface();
+
+ const uint16_t glyphs[2] = {static_cast<uint16_t>(left_glyph),
+ static_cast<uint16_t>(right_glyph)};
+ int32_t kerning_adjustments[1] = {0};
+
+ if (typeface->getKerningPairAdjustments(glyphs, 2, kerning_adjustments)) {
+ SkScalar upm = SkIntToScalar(typeface->getUnitsPerEm());
+ SkScalar size = hb_font_data->paint_.getTextSize();
+ return SkiaTextMetrics::SkiaScalarToHarfBuzzPosition(
+ SkIntToScalar(kerning_adjustments[0]) * size / upm);
+ }
+
+ return 0;
+}
+
+static hb_bool_t HarfBuzzGetGlyphExtents(hb_font_t* hb_font,
+ void* font_data,
+ hb_codepoint_t glyph,
+ hb_glyph_extents_t* extents,
+ void* user_data) {
+ HarfBuzzFontData* hb_font_data =
+ reinterpret_cast<HarfBuzzFontData*>(font_data);
+
+ SkiaTextMetrics(&hb_font_data->paint_)
+ .GetGlyphExtentsForHarfBuzz(glyph, extents);
+ return true;
+}
+
+static inline bool TableHasSpace(hb_face_t* face,
+ hb_set_t* glyphs,
+ hb_tag_t tag,
+ hb_codepoint_t space) {
+ unsigned count = hb_ot_layout_table_get_lookup_count(face, tag);
+ for (unsigned i = 0; i < count; i++) {
+ hb_ot_layout_lookup_collect_glyphs(face, tag, i, glyphs, glyphs, glyphs,
+ nullptr);
+ if (hb_set_has(glyphs, space))
+ return true;
+ }
+ return false;
+}
+
+static bool GetSpaceGlyph(hb_font_t* font, hb_codepoint_t& space) {
+ return hb_font_get_nominal_glyph(font, kSpaceCharacter, &space);
+}
+
+bool HarfBuzzFace::HasSpaceInLigaturesOrKerning(TypesettingFeatures features) {
+ const hb_codepoint_t kInvalidCodepoint = static_cast<hb_codepoint_t>(-1);
+ hb_codepoint_t space = kInvalidCodepoint;
+
+ HbSetUniquePtr glyphs(hb_set_create());
+
+ // Check whether computing is needed and compute for gpos/gsub.
+ if (features & kKerning &&
+ harf_buzz_font_data_->space_in_gpos_ ==
+ HarfBuzzFontData::SpaceGlyphInOpenTypeTables::Unknown) {
+ if (space == kInvalidCodepoint && !GetSpaceGlyph(unscaled_font_, space))
+ return false;
+ // Compute for gpos.
+ hb_face_t* face = hb_font_get_face(unscaled_font_);
+ DCHECK(face);
+ harf_buzz_font_data_->space_in_gpos_ =
+ hb_ot_layout_has_positioning(face) &&
+ TableHasSpace(face, glyphs.get(), HB_OT_TAG_GPOS, space)
+ ? HarfBuzzFontData::SpaceGlyphInOpenTypeTables::Present
+ : HarfBuzzFontData::SpaceGlyphInOpenTypeTables::NotPresent;
+ }
+
+ hb_set_clear(glyphs.get());
+
+ if (features & kLigatures &&
+ harf_buzz_font_data_->space_in_gsub_ ==
+ HarfBuzzFontData::SpaceGlyphInOpenTypeTables::Unknown) {
+ if (space == kInvalidCodepoint && !GetSpaceGlyph(unscaled_font_, space))
+ return false;
+ // Compute for gpos.
+ hb_face_t* face = hb_font_get_face(unscaled_font_);
+ DCHECK(face);
+ harf_buzz_font_data_->space_in_gsub_ =
+ hb_ot_layout_has_substitution(face) &&
+ TableHasSpace(face, glyphs.get(), HB_OT_TAG_GSUB, space)
+ ? HarfBuzzFontData::SpaceGlyphInOpenTypeTables::Present
+ : HarfBuzzFontData::SpaceGlyphInOpenTypeTables::NotPresent;
+ }
+
+ return (features & kKerning &&
+ harf_buzz_font_data_->space_in_gpos_ ==
+ HarfBuzzFontData::SpaceGlyphInOpenTypeTables::Present) ||
+ (features & kLigatures &&
+ harf_buzz_font_data_->space_in_gsub_ ==
+ HarfBuzzFontData::SpaceGlyphInOpenTypeTables::Present);
+}
+
+unsigned HarfBuzzFace::UnitsPerEmFromHeadTable() {
+ hb_face_t* face = hb_font_get_face(unscaled_font_);
+ return hb_face_get_upem(face);
+}
+
+bool HarfBuzzFace::ShouldSubpixelPosition() {
+ return harf_buzz_font_data_->paint_.isSubpixelText();
+}
+
+static hb_font_funcs_t* HarfBuzzSkiaGetFontFuncs() {
+ hb_font_funcs_t* funcs = FontGlobalContext::GetHarfBuzzFontFuncs();
+
+ // We don't set callback functions which we can't support.
+ // HarfBuzz will use the fallback implementation if they aren't set.
+ if (!funcs) {
+ funcs = hb_font_funcs_create();
+ hb_font_funcs_set_glyph_func(funcs, HarfBuzzGetGlyph, nullptr, nullptr);
+ hb_font_funcs_set_glyph_h_advance_func(
+ funcs, HarfBuzzGetGlyphHorizontalAdvance, nullptr, nullptr);
+ hb_font_funcs_set_glyph_h_kerning_func(
+ funcs, HarfBuzzGetGlyphHorizontalKerning, nullptr, nullptr);
+ hb_font_funcs_set_glyph_v_advance_func(
+ funcs, HarfBuzzGetGlyphVerticalAdvance, nullptr, nullptr);
+ hb_font_funcs_set_glyph_v_origin_func(funcs, HarfBuzzGetGlyphVerticalOrigin,
+ nullptr, nullptr);
+ hb_font_funcs_set_glyph_extents_func(funcs, HarfBuzzGetGlyphExtents,
+ nullptr, nullptr);
+ hb_font_funcs_make_immutable(funcs);
+ FontGlobalContext::SetHarfBuzzFontFuncs(funcs);
+ }
+ DCHECK(funcs);
+ return funcs;
+}
+
+static hb_blob_t* HarfBuzzSkiaGetTable(hb_face_t* face,
+ hb_tag_t tag,
+ void* user_data) {
+ SkTypeface* typeface = reinterpret_cast<SkTypeface*>(user_data);
+
+ const size_t table_size = typeface->getTableSize(tag);
+ if (!table_size) {
+ return nullptr;
+ }
+
+ char* buffer = reinterpret_cast<char*>(WTF::Partitions::FastMalloc(
+ table_size, WTF_HEAP_PROFILER_TYPE_NAME(HarfBuzzFontData)));
+ if (!buffer)
+ return nullptr;
+ size_t actual_size = typeface->getTableData(tag, 0, table_size, buffer);
+ if (table_size != actual_size) {
+ WTF::Partitions::FastFree(buffer);
+ return nullptr;
+ }
+ return hb_blob_create(const_cast<char*>(buffer), table_size,
+ HB_MEMORY_MODE_WRITABLE, buffer,
+ WTF::Partitions::FastFree);
+}
+
+static void DeleteTypefaceStream(void* stream_asset_ptr) {
+ SkStreamAsset* stream_asset =
+ reinterpret_cast<SkStreamAsset*>(stream_asset_ptr);
+ delete stream_asset;
+}
+
+hb_face_t* HarfBuzzFace::CreateFace() {
+#if defined(OS_MACOSX)
+ // hb_face_t needs to be instantiated using the CoreText constructor for
+ // compatibility with AAT font, in which case HarfBuzz' CoreText backend is
+ // used. If we encounter a FreeType backed SkTypeface, for variable fonts on
+ // Mac OS < 10.12, follow the regular OpenType-only codepath below.
+ if (platform_data_->CgFont()) {
+ hb_face_t* face = hb_coretext_face_create(platform_data_->CgFont());
+ DCHECK(face);
+ return face;
+ }
+#endif
+ hb_face_t* face = nullptr;
+
+ DEFINE_THREAD_SAFE_STATIC_LOCAL(BooleanHistogram, zero_copy_success_histogram,
+ ("Blink.Fonts.HarfBuzzFaceZeroCopyAccess"));
+ SkTypeface* typeface = platform_data_->Typeface();
+ CHECK(typeface);
+ int ttc_index = 0;
+ SkStreamAsset* typeface_stream = typeface->openStream(&ttc_index);
+ if (typeface_stream && typeface_stream->getMemoryBase()) {
+ std::unique_ptr<hb_blob_t, void (*)(hb_blob_t*)> face_blob(
+ hb_blob_create(
+ reinterpret_cast<const char*>(typeface_stream->getMemoryBase()),
+ typeface_stream->getLength(), HB_MEMORY_MODE_READONLY,
+ typeface_stream, DeleteTypefaceStream),
+ hb_blob_destroy);
+ face = hb_face_create(face_blob.get(), ttc_index);
+ }
+
+ // Fallback to table copies if there is no in-memory access.
+ if (!face) {
+ face = hb_face_create_for_tables(HarfBuzzSkiaGetTable,
+ platform_data_->Typeface(), nullptr);
+ zero_copy_success_histogram.Count(false);
+ } else {
+ zero_copy_success_histogram.Count(true);
+ }
+
+ DCHECK(face);
+ return face;
+}
+
+scoped_refptr<HbFontCacheEntry> CreateHbFontCacheEntry(hb_face_t* face) {
+ HbFontUniquePtr ot_font(hb_font_create(face));
+ hb_ot_font_set_funcs(ot_font.get());
+ // Creating a sub font means that non-available functions
+ // are found from the parent.
+ hb_font_t* unscaled_font = hb_font_create_sub_font(ot_font.get());
+ scoped_refptr<HbFontCacheEntry> cache_entry =
+ HbFontCacheEntry::Create(unscaled_font);
+ hb_font_set_funcs(unscaled_font, HarfBuzzSkiaGetFontFuncs(),
+ cache_entry->HbFontData(), nullptr);
+ return cache_entry;
+}
+
+static_assert(
+ std::is_same<decltype(SkFontArguments::VariationPosition::Coordinate::axis),
+ decltype(hb_variation_t::tag)>::value &&
+ std::is_same<
+ decltype(SkFontArguments::VariationPosition::Coordinate::value),
+ decltype(hb_variation_t::value)>::value &&
+ sizeof(SkFontArguments::VariationPosition::Coordinate) ==
+ sizeof(hb_variation_t),
+ "Skia and HarfBuzz Variation parameter types must match in structure and "
+ "size.");
+
+hb_font_t* HarfBuzzFace::GetScaledFont(
+ scoped_refptr<UnicodeRangeSet> range_set,
+ VerticalLayoutCallbacks vertical_layout) const {
+ PaintFont paint_font;
+ platform_data_->SetupPaintFont(&paint_font);
+ paint_font.SetTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ harf_buzz_font_data_->range_set_ = std::move(range_set);
+ harf_buzz_font_data_->UpdateFallbackMetricsAndScale(
+ *platform_data_, paint_font.ToSkPaint(), vertical_layout);
+
+ int scale =
+ SkiaTextMetrics::SkiaScalarToHarfBuzzPosition(platform_data_->size());
+ hb_font_set_scale(unscaled_font_, scale, scale);
+ hb_font_set_ptem(unscaled_font_, platform_data_->size() / kCssPixelsPerPoint);
+
+ SkTypeface* typeface = harf_buzz_font_data_->paint_.getTypeface();
+ int axis_count = typeface->getVariationDesignPosition(nullptr, 0);
+ if (axis_count > 0) {
+ Vector<SkFontArguments::VariationPosition::Coordinate> axis_values;
+ axis_values.resize(axis_count);
+ if (typeface->getVariationDesignPosition(axis_values.data(),
+ axis_values.size()) > 0) {
+ hb_font_set_variations(
+ unscaled_font_, reinterpret_cast<hb_variation_t*>(axis_values.data()),
+ axis_values.size());
+ }
+ }
+
+ return unscaled_font_;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_face.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_face.h
new file mode 100644
index 00000000000..35d81feda56
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_face.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_HARF_BUZZ_FACE_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_HARF_BUZZ_FACE_H_
+
+#include "base/memory/scoped_refptr.h"
+#include "third_party/blink/renderer/platform/fonts/typesetting_features.h"
+#include "third_party/blink/renderer/platform/fonts/unicode_range_set.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/hash_map.h"
+#include "third_party/blink/renderer/platform/wtf/noncopyable.h"
+#include "third_party/blink/renderer/platform/wtf/ref_counted.h"
+#include "third_party/blink/renderer/platform/wtf/text/character_names.h"
+
+#include <hb.h>
+
+namespace blink {
+
+class FontPlatformData;
+struct HarfBuzzFontData;
+
+class HarfBuzzFace : public RefCounted<HarfBuzzFace> {
+ WTF_MAKE_NONCOPYABLE(HarfBuzzFace);
+
+ public:
+ static scoped_refptr<HarfBuzzFace> Create(FontPlatformData* platform_data,
+ uint64_t unique_id) {
+ return base::AdoptRef(new HarfBuzzFace(platform_data, unique_id));
+ }
+ ~HarfBuzzFace();
+
+ enum VerticalLayoutCallbacks { PrepareForVerticalLayout, NoVerticalLayout };
+
+ // In order to support the restricting effect of unicode-range optionally a
+ // range restriction can be passed in, which will restrict which glyphs we
+ // return in the harfBuzzGetGlyph function.
+ hb_font_t* GetScaledFont(scoped_refptr<UnicodeRangeSet>,
+ VerticalLayoutCallbacks) const;
+
+ bool HasSpaceInLigaturesOrKerning(TypesettingFeatures);
+ unsigned UnitsPerEmFromHeadTable();
+
+ bool ShouldSubpixelPosition();
+
+ private:
+ HarfBuzzFace(FontPlatformData*, uint64_t);
+
+ hb_face_t* CreateFace();
+ void PrepareHarfBuzzFontData();
+
+ FontPlatformData* platform_data_;
+ uint64_t unique_id_;
+ hb_font_t* unscaled_font_;
+ HarfBuzzFontData* harf_buzz_font_data_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_HARF_BUZZ_FACE_H_
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_font_cache.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_font_cache.h
new file mode 100644
index 00000000000..a25f2532a92
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_font_cache.h
@@ -0,0 +1,151 @@
+// Copyright 2017 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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_HARF_BUZZ_FONT_CACHE_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_HARF_BUZZ_FONT_CACHE_H_
+
+#include <memory>
+#include "third_party/blink/renderer/platform/fonts/font_metrics.h"
+#include "third_party/blink/renderer/platform/fonts/opentype/open_type_vertical_data.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_face.h"
+#include "third_party/blink/renderer/platform/fonts/unicode_range_set.h"
+#include "third_party/blink/renderer/platform/wtf/assertions.h"
+
+struct hb_font_t;
+struct hb_face_t;
+
+namespace blink {
+
+struct HbFontDeleter {
+ void operator()(hb_font_t* font);
+};
+
+using HbFontUniquePtr = std::unique_ptr<hb_font_t, HbFontDeleter>;
+
+struct HbFaceDeleter {
+ void operator()(hb_face_t* face);
+};
+
+using HbFaceUniquePtr = std::unique_ptr<hb_face_t, HbFaceDeleter>;
+
+const unsigned kInvalidFallbackMetricsValue = static_cast<unsigned>(-1);
+
+// struct to carry user-pointer data for hb_font_t callback
+// functions/operations, that require information related to a font scaled to a
+// particular size.
+struct HarfBuzzFontData {
+ USING_FAST_MALLOC(HarfBuzzFontData);
+ WTF_MAKE_NONCOPYABLE(HarfBuzzFontData);
+
+ public:
+ HarfBuzzFontData()
+ : paint_(),
+ space_in_gpos_(SpaceGlyphInOpenTypeTables::Unknown),
+ space_in_gsub_(SpaceGlyphInOpenTypeTables::Unknown),
+ vertical_data_(nullptr),
+ range_set_(nullptr) {}
+
+ // The vertical origin and vertical advance functions in HarfBuzzFace require
+ // the ascent and height metrics as fallback in case no specific vertical
+ // layout information is found from the font.
+ void UpdateFallbackMetricsAndScale(
+ const FontPlatformData& platform_data,
+ const SkPaint& paint,
+ HarfBuzzFace::VerticalLayoutCallbacks vertical_layout) {
+ float ascent = 0;
+ float descent = 0;
+ unsigned dummy_ascent_inflation = 0;
+ unsigned dummy_descent_inflation = 0;
+
+ paint_ = paint;
+
+ if (UNLIKELY(vertical_layout == HarfBuzzFace::PrepareForVerticalLayout)) {
+ FontMetrics::AscentDescentWithHacks(
+ ascent, descent, dummy_ascent_inflation, dummy_descent_inflation,
+ platform_data, paint);
+ ascent_fallback_ = ascent;
+ // Simulate the rounding that FontMetrics does so far for returning the
+ // integer Height()
+ height_fallback_ = lroundf(ascent) + lroundf(descent);
+
+ int units_per_em =
+ platform_data.GetHarfBuzzFace()->UnitsPerEmFromHeadTable();
+ if (!units_per_em) {
+ DLOG(ERROR)
+ << "Units per EM is 0 for font used in vertical writing mode.";
+ }
+ size_per_unit_ = platform_data.size() / (units_per_em ? units_per_em : 1);
+ } else {
+ ascent_fallback_ = kInvalidFallbackMetricsValue;
+ height_fallback_ = kInvalidFallbackMetricsValue;
+ size_per_unit_ = kInvalidFallbackMetricsValue;
+ }
+ }
+
+ scoped_refptr<OpenTypeVerticalData> VerticalData() {
+ if (!vertical_data_) {
+ DCHECK_NE(ascent_fallback_, kInvalidFallbackMetricsValue);
+ DCHECK_NE(height_fallback_, kInvalidFallbackMetricsValue);
+ DCHECK_NE(size_per_unit_, kInvalidFallbackMetricsValue);
+
+ vertical_data_ =
+ OpenTypeVerticalData::CreateUnscaled(paint_.refTypeface());
+ }
+ vertical_data_->SetScaleAndFallbackMetrics(size_per_unit_, ascent_fallback_,
+ height_fallback_);
+ return vertical_data_;
+ }
+
+ SkPaint paint_;
+
+ // Capture these scaled fallback metrics from FontPlatformData so that a
+ // OpenTypeVerticalData object can be constructed from them when needed.
+ float size_per_unit_;
+ float ascent_fallback_;
+ float height_fallback_;
+
+ enum class SpaceGlyphInOpenTypeTables { Unknown, Present, NotPresent };
+
+ SpaceGlyphInOpenTypeTables space_in_gpos_;
+ SpaceGlyphInOpenTypeTables space_in_gsub_;
+
+ scoped_refptr<OpenTypeVerticalData> vertical_data_;
+ scoped_refptr<UnicodeRangeSet> range_set_;
+};
+
+// Though we have FontCache class, which provides the cache mechanism for
+// WebKit's font objects, we also need additional caching layer for HarfBuzz to
+// reduce the number of hb_font_t objects created. Without it, we would create
+// an hb_font_t object for every FontPlatformData object. But insted, we only
+// need one for each unique SkTypeface.
+// FIXME, crbug.com/609099: We should fix the FontCache to only keep one
+// FontPlatformData object independent of size, then consider using this here.
+class HbFontCacheEntry : public RefCounted<HbFontCacheEntry> {
+ public:
+ static scoped_refptr<HbFontCacheEntry> Create(hb_font_t* hb_font) {
+ DCHECK(hb_font);
+ return base::AdoptRef(new HbFontCacheEntry(hb_font));
+ }
+
+ hb_font_t* HbFont() { return hb_font_.get(); }
+ HarfBuzzFontData* HbFontData() { return hb_font_data_.get(); }
+
+ private:
+ explicit HbFontCacheEntry(hb_font_t* font)
+ : hb_font_(HbFontUniquePtr(font)),
+ hb_font_data_(std::make_unique<HarfBuzzFontData>()){};
+
+ HbFontUniquePtr hb_font_;
+ std::unique_ptr<HarfBuzzFontData> hb_font_data_;
+};
+
+typedef HashMap<uint64_t,
+ scoped_refptr<HbFontCacheEntry>,
+ WTF::IntHash<uint64_t>,
+ WTF::UnsignedWithZeroKeyHashTraits<uint64_t>>
+ HarfBuzzFontCache;
+
+} // namespace blink
+
+#endif
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.cc
new file mode 100644
index 00000000000..bf0a73e1184
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.cc
@@ -0,0 +1,947 @@
+/*
+ * Copyright (c) 2012 Google Inc. All rights reserved.
+ * Copyright (C) 2013 BlackBerry Limited. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h"
+
+#include <hb.h>
+#include <unicode/uchar.h>
+#include <unicode/uscript.h>
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "third_party/blink/renderer/platform/fonts/font.h"
+#include "third_party/blink/renderer/platform/fonts/font_description.h"
+#include "third_party/blink/renderer/platform/fonts/font_fallback_iterator.h"
+#include "third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/case_mapping_harf_buzz_buffer_filler.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_face.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h"
+#include "third_party/blink/renderer/platform/fonts/small_caps_iterator.h"
+#include "third_party/blink/renderer/platform/fonts/utf16_text_iterator.h"
+#include "third_party/blink/renderer/platform/wtf/compiler.h"
+#include "third_party/blink/renderer/platform/wtf/deque.h"
+#include "third_party/blink/renderer/platform/wtf/math_extras.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+#include "third_party/blink/renderer/platform/wtf/text/unicode.h"
+
+namespace blink {
+
+namespace {
+
+#if DCHECK_IS_ON()
+// Check if the ShapeResult has the specified range.
+// |text| and |font| are only for logging.
+void CheckShapeResultRange(const ShapeResult* result,
+ unsigned start,
+ unsigned end,
+ const UChar* text,
+ const Font* font) {
+ DCHECK_LE(start, end);
+ unsigned length = end - start;
+ if (length == result->NumCharacters() &&
+ (!length || (start == result->StartIndexForResult() &&
+ end == result->EndIndexForResult())))
+ return;
+
+ // Log font-family/size as specified.
+ StringBuilder log;
+ log.Append("Font='");
+ const FontDescription& font_description = font->GetFontDescription();
+ for (const FontFamily* family = &font_description.Family();;) {
+ log.Append(family->Family());
+ family = family->Next();
+ if (!family)
+ break;
+ log.Append(", ");
+ }
+ log.Append(String::Format("', %f", font_description.ComputedSize()));
+
+ // Log the primary font with its family name in the font file.
+ const SimpleFontData* font_data = font->PrimaryFont();
+ if (font_data) {
+ const SkTypeface* typeface = font_data->PlatformData().Typeface();
+ SkString family_name;
+ typeface->getFamilyName(&family_name);
+ log.Append(", primary=");
+ log.Append(family_name.c_str());
+ }
+
+ // Log the text to shape.
+ log.Append(String::Format(": %u-%u -> %u-%u:", start, end,
+ result->StartIndexForResult(),
+ result->EndIndexForResult()));
+ for (unsigned i = start; i < end; ++i)
+ log.Append(String::Format(" %02X", text[i]));
+
+ log.Append(", result=");
+ result->ToString(&log);
+
+ NOTREACHED() << log.ToString();
+}
+#endif
+
+} // namespace
+
+enum ReshapeQueueItemAction { kReshapeQueueNextFont, kReshapeQueueRange };
+
+struct ReshapeQueueItem {
+ DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
+ ReshapeQueueItemAction action_;
+ unsigned start_index_;
+ unsigned num_characters_;
+ ReshapeQueueItem(ReshapeQueueItemAction action, unsigned start, unsigned num)
+ : action_(action), start_index_(start), num_characters_(num){};
+};
+
+template <typename T>
+class HarfBuzzScopedPtr {
+ STACK_ALLOCATED();
+ WTF_MAKE_NONCOPYABLE(HarfBuzzScopedPtr);
+
+ public:
+ typedef void (*DestroyFunction)(T*);
+
+ HarfBuzzScopedPtr(T* ptr, DestroyFunction destroy)
+ : ptr_(ptr), destroy_(destroy) {
+ DCHECK(destroy_);
+ }
+ ~HarfBuzzScopedPtr() {
+ if (ptr_)
+ (*destroy_)(ptr_);
+ }
+
+ T* Get() { return ptr_; }
+ void Set(T* ptr) { ptr_ = ptr; }
+
+ private:
+ T* ptr_;
+ DestroyFunction destroy_;
+};
+
+HarfBuzzShaper::HarfBuzzShaper(const UChar* text, unsigned length)
+ : text_(text), text_length_(length) {}
+
+using FeaturesVector = Vector<hb_feature_t, 6>;
+struct RangeData {
+ hb_buffer_t* buffer;
+ const Font* font;
+ TextDirection text_direction;
+ unsigned start;
+ unsigned end;
+ FeaturesVector font_features;
+ Deque<ReshapeQueueItem> reshape_queue;
+
+ hb_direction_t HarfBuzzDirection(CanvasRotationInVertical canvas_rotation) {
+ FontOrientation orientation = font->GetFontDescription().Orientation();
+ hb_direction_t direction =
+ IsVerticalAnyUpright(orientation) &&
+ (canvas_rotation ==
+ CanvasRotationInVertical::kRotateCanvasUpright)
+ ? HB_DIRECTION_TTB
+ : HB_DIRECTION_LTR;
+ return text_direction == TextDirection::kRtl
+ ? HB_DIRECTION_REVERSE(direction)
+ : direction;
+ }
+};
+
+struct BufferSlice {
+ unsigned start_character_index;
+ unsigned num_characters;
+ unsigned start_glyph_index;
+ unsigned num_glyphs;
+};
+
+namespace {
+
+// A port of hb_icu_script_to_script because harfbuzz on CrOS is built
+// without hb-icu. See http://crbug.com/356929
+static inline hb_script_t ICUScriptToHBScript(UScriptCode script) {
+ if (UNLIKELY(script == USCRIPT_INVALID_CODE))
+ return HB_SCRIPT_INVALID;
+
+ return hb_script_from_string(uscript_getShortName(script), -1);
+}
+
+void RoundHarfBuzzPosition(hb_position_t* value) {
+ if ((*value) & 0xFFFF) {
+ // There is a non-zero fractional part in the 16.16 value.
+ *value = static_cast<hb_position_t>(
+ round(static_cast<float>(*value) / (1 << 16)))
+ << 16;
+ }
+}
+
+void RoundHarfBuzzBufferPositions(hb_buffer_t* buffer) {
+ unsigned int len;
+ hb_glyph_position_t* glyph_positions =
+ hb_buffer_get_glyph_positions(buffer, &len);
+ for (unsigned int i = 0; i < len; i++) {
+ hb_glyph_position_t* pos = &glyph_positions[i];
+ RoundHarfBuzzPosition(&pos->x_offset);
+ RoundHarfBuzzPosition(&pos->y_offset);
+ RoundHarfBuzzPosition(&pos->x_advance);
+ RoundHarfBuzzPosition(&pos->y_advance);
+ }
+}
+
+inline bool ShapeRange(hb_buffer_t* buffer,
+ hb_feature_t* font_features,
+ unsigned font_features_size,
+ const SimpleFontData* current_font,
+ scoped_refptr<UnicodeRangeSet> current_font_range_set,
+ UScriptCode current_run_script,
+ hb_direction_t direction,
+ hb_language_t language) {
+ const FontPlatformData* platform_data = &(current_font->PlatformData());
+ HarfBuzzFace* face = platform_data->GetHarfBuzzFace();
+ if (!face) {
+ DLOG(ERROR) << "Could not create HarfBuzzFace from FontPlatformData.";
+ return false;
+ }
+
+ hb_buffer_set_language(buffer, language);
+ hb_buffer_set_script(buffer, ICUScriptToHBScript(current_run_script));
+ hb_buffer_set_direction(buffer, direction);
+
+ hb_font_t* hb_font =
+ face->GetScaledFont(std::move(current_font_range_set),
+ HB_DIRECTION_IS_VERTICAL(direction)
+ ? HarfBuzzFace::PrepareForVerticalLayout
+ : HarfBuzzFace::NoVerticalLayout);
+ hb_shape(hb_font, buffer, font_features, font_features_size);
+
+ // We cannot round all glyph positions during hb_shape because the
+ // hb_font_funcs_set_glyph_h_kerning_func only works for legacy kerning.
+ // OpenType uses gpos tables for kerning and harfbuzz does not call
+ // the callback to let us round as we go.
+ // Without this rounding, we get inconsistent spacing between kern points
+ // if subpixel positioning is disabled.
+ // See http://crbug.com/740385.
+ if (!face->ShouldSubpixelPosition())
+ RoundHarfBuzzBufferPositions(buffer);
+
+ return true;
+}
+
+BufferSlice ComputeSlice(RangeData* range_data,
+ const ReshapeQueueItem& current_queue_item,
+ const hb_glyph_info_t* glyph_info,
+ unsigned num_glyphs,
+ unsigned old_glyph_index,
+ unsigned new_glyph_index) {
+ // Compute the range indices of consecutive shaped or .notdef glyphs.
+ // Cluster information for RTL runs becomes reversed, e.g. glyph 0
+ // has cluster index 5 in a run of 6 characters.
+ BufferSlice result;
+ result.start_glyph_index = old_glyph_index;
+ result.num_glyphs = new_glyph_index - old_glyph_index;
+
+ if (HB_DIRECTION_IS_FORWARD(hb_buffer_get_direction(range_data->buffer))) {
+ result.start_character_index = glyph_info[old_glyph_index].cluster;
+ if (new_glyph_index == num_glyphs) {
+ // Clamp the end offsets of the queue item to the offsets representing
+ // the shaping window.
+ unsigned shape_end =
+ std::min(range_data->end, current_queue_item.start_index_ +
+ current_queue_item.num_characters_);
+ result.num_characters = shape_end - result.start_character_index;
+ } else {
+ result.num_characters =
+ glyph_info[new_glyph_index].cluster - result.start_character_index;
+ }
+ } else {
+ // Direction Backwards
+ result.start_character_index = glyph_info[new_glyph_index - 1].cluster;
+ if (old_glyph_index == 0) {
+ // Clamp the end offsets of the queue item to the offsets representing
+ // the shaping window.
+ unsigned shape_end =
+ std::min(range_data->end, current_queue_item.start_index_ +
+ current_queue_item.num_characters_);
+ result.num_characters = shape_end - result.start_character_index;
+ } else {
+ result.num_characters = glyph_info[old_glyph_index - 1].cluster -
+ glyph_info[new_glyph_index - 1].cluster;
+ }
+ }
+
+ return result;
+}
+
+void QueueCharacters(RangeData* range_data,
+ const SimpleFontData* current_font,
+ bool& font_cycle_queued,
+ const BufferSlice& slice) {
+ if (!font_cycle_queued) {
+ range_data->reshape_queue.push_back(
+ ReshapeQueueItem(kReshapeQueueNextFont, 0, 0));
+ font_cycle_queued = true;
+ }
+
+ DCHECK(slice.num_characters);
+ range_data->reshape_queue.push_back(ReshapeQueueItem(
+ kReshapeQueueRange, slice.start_character_index, slice.num_characters));
+}
+
+CanvasRotationInVertical CanvasRotationForRun(
+ FontOrientation font_orientation,
+ OrientationIterator::RenderOrientation render_orientation) {
+ if (font_orientation == FontOrientation::kVerticalUpright ||
+ (font_orientation == FontOrientation::kVerticalMixed &&
+ render_orientation == OrientationIterator::kOrientationKeep))
+ return CanvasRotationInVertical::kRotateCanvasUpright;
+ return CanvasRotationInVertical::kRegular;
+}
+
+} // namespace
+
+void HarfBuzzShaper::CommitGlyphs(RangeData* range_data,
+ const SimpleFontData* current_font,
+ UScriptCode current_run_script,
+ CanvasRotationInVertical canvas_rotation,
+ bool is_last_resort,
+ const BufferSlice& slice,
+ ShapeResult* shape_result) const {
+ hb_direction_t direction = range_data->HarfBuzzDirection(canvas_rotation);
+ // Here we need to specify glyph positions.
+ ShapeResult::RunInfo* run = new ShapeResult::RunInfo(
+ current_font, direction, canvas_rotation,
+ ICUScriptToHBScript(current_run_script), slice.start_character_index,
+ slice.num_glyphs, slice.num_characters);
+ shape_result->InsertRun(base::WrapUnique(run), slice.start_glyph_index,
+ slice.num_glyphs, range_data->buffer);
+ if (is_last_resort)
+ range_data->font->ReportNotDefGlyph();
+}
+
+void HarfBuzzShaper::ExtractShapeResults(
+ RangeData* range_data,
+ bool& font_cycle_queued,
+ const ReshapeQueueItem& current_queue_item,
+ const SimpleFontData* current_font,
+ UScriptCode current_run_script,
+ CanvasRotationInVertical canvas_rotation,
+ bool is_last_resort,
+ ShapeResult* shape_result) const {
+ enum ClusterResult { kShaped, kNotDef, kUnknown };
+ ClusterResult current_cluster_result = kUnknown;
+ ClusterResult previous_cluster_result = kUnknown;
+ unsigned previous_cluster = 0;
+ unsigned current_cluster = 0;
+
+ // Find first notdef glyph in buffer.
+ unsigned num_glyphs = hb_buffer_get_length(range_data->buffer);
+ hb_glyph_info_t* glyph_info =
+ hb_buffer_get_glyph_infos(range_data->buffer, nullptr);
+
+ unsigned last_change_glyph_index = 0;
+ unsigned previous_cluster_start_glyph_index = 0;
+
+ if (!num_glyphs)
+ return;
+
+ for (unsigned glyph_index = 0; glyph_index < num_glyphs; ++glyph_index) {
+ // We proceed by full clusters and determine a shaping result - either
+ // kShaped or kNotDef for each cluster.
+ ClusterResult glyph_result =
+ glyph_info[glyph_index].codepoint == 0 ? kNotDef : kShaped;
+ previous_cluster = current_cluster;
+ current_cluster = glyph_info[glyph_index].cluster;
+
+ if (current_cluster != previous_cluster) {
+ // We are transitioning to a new cluster (whose shaping result state we
+ // have not looked at yet). This means the cluster we just looked at is
+ // completely analysed and we can determine whether it was fully shaped
+ // and whether that means a state change to the cluster before that one.
+ if ((previous_cluster_result != current_cluster_result) &&
+ previous_cluster_result != kUnknown) {
+ BufferSlice slice = ComputeSlice(
+ range_data, current_queue_item, glyph_info, num_glyphs,
+ last_change_glyph_index, previous_cluster_start_glyph_index);
+ // If the most recent cluster is shaped and there is a state change,
+ // it means the previous ones were unshaped, so we queue them, unless
+ // we're using the last resort font.
+ if (current_cluster_result == kShaped && !is_last_resort) {
+ QueueCharacters(range_data, current_font, font_cycle_queued, slice);
+ } else {
+ // If the most recent cluster is unshaped and there is a state
+ // change, it means the previous one(s) were shaped, so we commit
+ // the glyphs. We also commit when we've reached the last resort
+ // font.
+ CommitGlyphs(range_data, current_font, current_run_script,
+ canvas_rotation, is_last_resort, slice, shape_result);
+ }
+ last_change_glyph_index = previous_cluster_start_glyph_index;
+ }
+
+ // No state change happened, continue.
+ previous_cluster_result = current_cluster_result;
+ previous_cluster_start_glyph_index = glyph_index;
+ // Reset current cluster result.
+ current_cluster_result = glyph_result;
+ } else {
+ // Update and merge current cluster result.
+ current_cluster_result =
+ glyph_result == kShaped && (current_cluster_result == kShaped ||
+ current_cluster_result == kUnknown)
+ ? kShaped
+ : kNotDef;
+ }
+ }
+
+ // End of the run.
+ if (current_cluster_result != previous_cluster_result &&
+ previous_cluster_result != kUnknown && !is_last_resort) {
+ // The last cluster in the run still had shaping status different from
+ // the cluster(s) before it, we need to submit one shaped and one
+ // unshaped segment.
+ if (current_cluster_result == kShaped) {
+ BufferSlice slice = ComputeSlice(
+ range_data, current_queue_item, glyph_info, num_glyphs,
+ last_change_glyph_index, previous_cluster_start_glyph_index);
+ QueueCharacters(range_data, current_font, font_cycle_queued, slice);
+ slice =
+ ComputeSlice(range_data, current_queue_item, glyph_info, num_glyphs,
+ previous_cluster_start_glyph_index, num_glyphs);
+ CommitGlyphs(range_data, current_font, current_run_script,
+ canvas_rotation, is_last_resort, slice, shape_result);
+ } else {
+ BufferSlice slice = ComputeSlice(
+ range_data, current_queue_item, glyph_info, num_glyphs,
+ last_change_glyph_index, previous_cluster_start_glyph_index);
+ CommitGlyphs(range_data, current_font, current_run_script,
+ canvas_rotation, is_last_resort, slice, shape_result);
+ slice =
+ ComputeSlice(range_data, current_queue_item, glyph_info, num_glyphs,
+ previous_cluster_start_glyph_index, num_glyphs);
+ QueueCharacters(range_data, current_font, font_cycle_queued, slice);
+ }
+ } else {
+ // There hasn't been a state change for the last cluster, so we can just
+ // either commit or queue what we have up until here.
+ BufferSlice slice =
+ ComputeSlice(range_data, current_queue_item, glyph_info, num_glyphs,
+ last_change_glyph_index, num_glyphs);
+ if (current_cluster_result == kNotDef && !is_last_resort) {
+ QueueCharacters(range_data, current_font, font_cycle_queued, slice);
+ } else {
+ CommitGlyphs(range_data, current_font, current_run_script,
+ canvas_rotation, is_last_resort, slice, shape_result);
+ }
+ }
+}
+
+bool HarfBuzzShaper::CollectFallbackHintChars(
+ const Deque<ReshapeQueueItem>& reshape_queue,
+ Vector<UChar32>& hint) const {
+ if (!reshape_queue.size())
+ return false;
+
+ hint.clear();
+
+ size_t num_chars_added = 0;
+ for (auto it = reshape_queue.begin(); it != reshape_queue.end(); ++it) {
+ if (it->action_ == kReshapeQueueNextFont)
+ break;
+
+ UChar32 hint_char;
+ CHECK_LE((it->start_index_ + it->num_characters_), text_length_);
+ UTF16TextIterator iterator(text_ + it->start_index_, it->num_characters_);
+ while (iterator.Consume(hint_char)) {
+ hint.push_back(hint_char);
+ num_chars_added++;
+ iterator.Advance();
+ }
+ }
+ return num_chars_added > 0;
+}
+
+namespace {
+
+void SplitUntilNextCaseChange(
+ const UChar* normalized_buffer,
+ Deque<blink::ReshapeQueueItem>* queue,
+ blink::ReshapeQueueItem& current_queue_item,
+ SmallCapsIterator::SmallCapsBehavior& small_caps_behavior) {
+ unsigned num_characters_until_case_change = 0;
+ SmallCapsIterator small_caps_iterator(
+ normalized_buffer + current_queue_item.start_index_,
+ current_queue_item.num_characters_);
+ small_caps_iterator.Consume(&num_characters_until_case_change,
+ &small_caps_behavior);
+ if (num_characters_until_case_change > 0 &&
+ num_characters_until_case_change < current_queue_item.num_characters_) {
+ queue->push_front(blink::ReshapeQueueItem(
+ blink::ReshapeQueueItemAction::kReshapeQueueRange,
+ current_queue_item.start_index_ + num_characters_until_case_change,
+ current_queue_item.num_characters_ - num_characters_until_case_change));
+ current_queue_item.num_characters_ = num_characters_until_case_change;
+ }
+}
+
+hb_feature_t CreateFeature(hb_tag_t tag, uint32_t value = 0) {
+ return {tag, value, 0 /* start */, static_cast<unsigned>(-1) /* end */};
+}
+
+// TODO(kojii): crbug.com/762493 This list is getting long enough to extract out
+// of HarfBuzzShaper.cpp.
+void SetFontFeatures(const Font* font, FeaturesVector* features) {
+ const FontDescription& description = font->GetFontDescription();
+
+ static hb_feature_t no_kern = CreateFeature(HB_TAG('k', 'e', 'r', 'n'));
+ static hb_feature_t no_vkrn = CreateFeature(HB_TAG('v', 'k', 'r', 'n'));
+ switch (description.GetKerning()) {
+ case FontDescription::kNormalKerning:
+ // kern/vkrn are enabled by default
+ break;
+ case FontDescription::kNoneKerning:
+ features->push_back(description.IsVerticalAnyUpright() ? no_vkrn
+ : no_kern);
+ break;
+ case FontDescription::kAutoKerning:
+ break;
+ }
+
+ static hb_feature_t no_clig = CreateFeature(HB_TAG('c', 'l', 'i', 'g'));
+ static hb_feature_t no_liga = CreateFeature(HB_TAG('l', 'i', 'g', 'a'));
+ switch (description.CommonLigaturesState()) {
+ case FontDescription::kDisabledLigaturesState:
+ features->push_back(no_liga);
+ features->push_back(no_clig);
+ break;
+ case FontDescription::kEnabledLigaturesState:
+ // liga and clig are on by default
+ break;
+ case FontDescription::kNormalLigaturesState:
+ break;
+ }
+ static hb_feature_t dlig = CreateFeature(HB_TAG('d', 'l', 'i', 'g'), 1);
+ switch (description.DiscretionaryLigaturesState()) {
+ case FontDescription::kDisabledLigaturesState:
+ // dlig is off by default
+ break;
+ case FontDescription::kEnabledLigaturesState:
+ features->push_back(dlig);
+ break;
+ case FontDescription::kNormalLigaturesState:
+ break;
+ }
+ static hb_feature_t hlig = CreateFeature(HB_TAG('h', 'l', 'i', 'g'), 1);
+ switch (description.HistoricalLigaturesState()) {
+ case FontDescription::kDisabledLigaturesState:
+ // hlig is off by default
+ break;
+ case FontDescription::kEnabledLigaturesState:
+ features->push_back(hlig);
+ break;
+ case FontDescription::kNormalLigaturesState:
+ break;
+ }
+ static hb_feature_t no_calt = CreateFeature(HB_TAG('c', 'a', 'l', 't'));
+ switch (description.ContextualLigaturesState()) {
+ case FontDescription::kDisabledLigaturesState:
+ features->push_back(no_calt);
+ break;
+ case FontDescription::kEnabledLigaturesState:
+ // calt is on by default
+ break;
+ case FontDescription::kNormalLigaturesState:
+ break;
+ }
+
+ static hb_feature_t hwid = CreateFeature(HB_TAG('h', 'w', 'i', 'd'), 1);
+ static hb_feature_t twid = CreateFeature(HB_TAG('t', 'w', 'i', 'd'), 1);
+ static hb_feature_t qwid = CreateFeature(HB_TAG('q', 'w', 'i', 'd'), 1);
+ switch (description.WidthVariant()) {
+ case kHalfWidth:
+ features->push_back(hwid);
+ break;
+ case kThirdWidth:
+ features->push_back(twid);
+ break;
+ case kQuarterWidth:
+ features->push_back(qwid);
+ break;
+ case kRegularWidth:
+ break;
+ }
+
+ // font-variant-east-asian:
+ const FontVariantEastAsian east_asian = description.VariantEastAsian();
+ if (UNLIKELY(!east_asian.IsAllNormal())) {
+ static hb_feature_t jp78 = CreateFeature(HB_TAG('j', 'p', '7', '8'), 1);
+ static hb_feature_t jp83 = CreateFeature(HB_TAG('j', 'p', '8', '3'), 1);
+ static hb_feature_t jp90 = CreateFeature(HB_TAG('j', 'p', '9', '0'), 1);
+ static hb_feature_t jp04 = CreateFeature(HB_TAG('j', 'p', '0', '4'), 1);
+ static hb_feature_t smpl = CreateFeature(HB_TAG('s', 'm', 'p', 'l'), 1);
+ static hb_feature_t trad = CreateFeature(HB_TAG('t', 'r', 'a', 'd'), 1);
+ switch (east_asian.Form()) {
+ case FontVariantEastAsian::kNormalForm:
+ break;
+ case FontVariantEastAsian::kJis78:
+ features->push_back(jp78);
+ break;
+ case FontVariantEastAsian::kJis83:
+ features->push_back(jp83);
+ break;
+ case FontVariantEastAsian::kJis90:
+ features->push_back(jp90);
+ break;
+ case FontVariantEastAsian::kJis04:
+ features->push_back(jp04);
+ break;
+ case FontVariantEastAsian::kSimplified:
+ features->push_back(smpl);
+ break;
+ case FontVariantEastAsian::kTraditional:
+ features->push_back(trad);
+ break;
+ default:
+ NOTREACHED();
+ }
+ static hb_feature_t fwid = CreateFeature(HB_TAG('f', 'w', 'i', 'd'), 1);
+ static hb_feature_t pwid = CreateFeature(HB_TAG('p', 'w', 'i', 'd'), 1);
+ switch (east_asian.Width()) {
+ case FontVariantEastAsian::kNormalWidth:
+ break;
+ case FontVariantEastAsian::kFullWidth:
+ features->push_back(fwid);
+ break;
+ case FontVariantEastAsian::kProportionalWidth:
+ features->push_back(pwid);
+ break;
+ default:
+ NOTREACHED();
+ }
+ static hb_feature_t ruby = CreateFeature(HB_TAG('r', 'u', 'b', 'y'), 1);
+ if (east_asian.Ruby())
+ features->push_back(ruby);
+ }
+
+ // font-variant-numeric:
+ static hb_feature_t lnum = CreateFeature(HB_TAG('l', 'n', 'u', 'm'), 1);
+ if (description.VariantNumeric().NumericFigureValue() ==
+ FontVariantNumeric::kLiningNums)
+ features->push_back(lnum);
+
+ static hb_feature_t onum = CreateFeature(HB_TAG('o', 'n', 'u', 'm'), 1);
+ if (description.VariantNumeric().NumericFigureValue() ==
+ FontVariantNumeric::kOldstyleNums)
+ features->push_back(onum);
+
+ static hb_feature_t pnum = CreateFeature(HB_TAG('p', 'n', 'u', 'm'), 1);
+ if (description.VariantNumeric().NumericSpacingValue() ==
+ FontVariantNumeric::kProportionalNums)
+ features->push_back(pnum);
+ static hb_feature_t tnum = CreateFeature(HB_TAG('t', 'n', 'u', 'm'), 1);
+ if (description.VariantNumeric().NumericSpacingValue() ==
+ FontVariantNumeric::kTabularNums)
+ features->push_back(tnum);
+
+ static hb_feature_t afrc = CreateFeature(HB_TAG('a', 'f', 'r', 'c'), 1);
+ if (description.VariantNumeric().NumericFractionValue() ==
+ FontVariantNumeric::kStackedFractions)
+ features->push_back(afrc);
+ static hb_feature_t frac = CreateFeature(HB_TAG('f', 'r', 'a', 'c'), 1);
+ if (description.VariantNumeric().NumericFractionValue() ==
+ FontVariantNumeric::kDiagonalFractions)
+ features->push_back(frac);
+
+ static hb_feature_t ordn = CreateFeature(HB_TAG('o', 'r', 'd', 'n'), 1);
+ if (description.VariantNumeric().OrdinalValue() ==
+ FontVariantNumeric::kOrdinalOn)
+ features->push_back(ordn);
+
+ static hb_feature_t zero = CreateFeature(HB_TAG('z', 'e', 'r', 'o'), 1);
+ if (description.VariantNumeric().SlashedZeroValue() ==
+ FontVariantNumeric::kSlashedZeroOn)
+ features->push_back(zero);
+
+ FontFeatureSettings* settings = description.FeatureSettings();
+ if (!settings)
+ return;
+
+ // TODO(drott): crbug.com/450619 Implement feature resolution instead of
+ // just appending the font-feature-settings.
+ unsigned num_features = settings->size();
+ for (unsigned i = 0; i < num_features; ++i) {
+ hb_feature_t feature;
+ const AtomicString& tag = settings->at(i).Tag();
+ feature.tag = HB_TAG(tag[0], tag[1], tag[2], tag[3]);
+ feature.value = settings->at(i).Value();
+ feature.start = 0;
+ feature.end = static_cast<unsigned>(-1);
+ features->push_back(feature);
+ }
+}
+
+class CapsFeatureSettingsScopedOverlay final {
+ STACK_ALLOCATED();
+
+ public:
+ CapsFeatureSettingsScopedOverlay(FeaturesVector*,
+ FontDescription::FontVariantCaps);
+ CapsFeatureSettingsScopedOverlay() = delete;
+ ~CapsFeatureSettingsScopedOverlay();
+
+ private:
+ void OverlayCapsFeatures(FontDescription::FontVariantCaps);
+ void PrependCounting(const hb_feature_t&);
+ FeaturesVector* features_;
+ size_t count_features_;
+};
+
+CapsFeatureSettingsScopedOverlay::CapsFeatureSettingsScopedOverlay(
+ FeaturesVector* features,
+ FontDescription::FontVariantCaps variant_caps)
+ : features_(features), count_features_(0) {
+ OverlayCapsFeatures(variant_caps);
+}
+
+void CapsFeatureSettingsScopedOverlay::OverlayCapsFeatures(
+ FontDescription::FontVariantCaps variant_caps) {
+ static hb_feature_t smcp = CreateFeature(HB_TAG('s', 'm', 'c', 'p'), 1);
+ static hb_feature_t pcap = CreateFeature(HB_TAG('p', 'c', 'a', 'p'), 1);
+ static hb_feature_t c2sc = CreateFeature(HB_TAG('c', '2', 's', 'c'), 1);
+ static hb_feature_t c2pc = CreateFeature(HB_TAG('c', '2', 'p', 'c'), 1);
+ static hb_feature_t unic = CreateFeature(HB_TAG('u', 'n', 'i', 'c'), 1);
+ static hb_feature_t titl = CreateFeature(HB_TAG('t', 'i', 't', 'l'), 1);
+ if (variant_caps == FontDescription::kSmallCaps ||
+ variant_caps == FontDescription::kAllSmallCaps) {
+ PrependCounting(smcp);
+ if (variant_caps == FontDescription::kAllSmallCaps) {
+ PrependCounting(c2sc);
+ }
+ }
+ if (variant_caps == FontDescription::kPetiteCaps ||
+ variant_caps == FontDescription::kAllPetiteCaps) {
+ PrependCounting(pcap);
+ if (variant_caps == FontDescription::kAllPetiteCaps) {
+ PrependCounting(c2pc);
+ }
+ }
+ if (variant_caps == FontDescription::kUnicase) {
+ PrependCounting(unic);
+ }
+ if (variant_caps == FontDescription::kTitlingCaps) {
+ PrependCounting(titl);
+ }
+}
+
+void CapsFeatureSettingsScopedOverlay::PrependCounting(
+ const hb_feature_t& feature) {
+ features_->push_front(feature);
+ count_features_++;
+}
+
+CapsFeatureSettingsScopedOverlay::~CapsFeatureSettingsScopedOverlay() {
+ features_->EraseAt(0, count_features_);
+}
+
+} // namespace
+
+void HarfBuzzShaper::ShapeSegment(RangeData* range_data,
+ RunSegmenter::RunSegmenterRange segment,
+ ShapeResult* result) const {
+ DCHECK(result);
+ DCHECK(range_data->buffer);
+
+ const Font* font = range_data->font;
+ const FontDescription& font_description = font->GetFontDescription();
+ const hb_language_t language =
+ font_description.LocaleOrDefault().HarfbuzzLanguage();
+ bool needs_caps_handling =
+ font_description.VariantCaps() != FontDescription::kCapsNormal;
+ OpenTypeCapsSupport caps_support;
+
+ scoped_refptr<FontFallbackIterator> fallback_iterator =
+ font->CreateFontFallbackIterator(segment.font_fallback_priority);
+
+ range_data->reshape_queue.push_back(
+ ReshapeQueueItem(kReshapeQueueNextFont, 0, 0));
+ range_data->reshape_queue.push_back(ReshapeQueueItem(
+ kReshapeQueueRange, segment.start, segment.end - segment.start));
+
+ bool font_cycle_queued = false;
+ Vector<UChar32> fallback_chars_hint;
+ scoped_refptr<FontDataForRangeSet> current_font_data_for_range_set;
+ while (range_data->reshape_queue.size()) {
+ ReshapeQueueItem current_queue_item = range_data->reshape_queue.TakeFirst();
+
+ if (current_queue_item.action_ == kReshapeQueueNextFont) {
+ // For now, we're building a character list with which we probe
+ // for needed fonts depending on the declared unicode-range of a
+ // segmented CSS font. Alternatively, we can build a fake font
+ // for the shaper and check whether any glyphs were found, or
+ // define a new API on the shaper which will give us coverage
+ // information?
+ if (!CollectFallbackHintChars(range_data->reshape_queue,
+ fallback_chars_hint)) {
+ // Give up shaping since we cannot retrieve a font fallback
+ // font without a hintlist.
+ range_data->reshape_queue.clear();
+ break;
+ }
+
+ current_font_data_for_range_set =
+ fallback_iterator->Next(fallback_chars_hint);
+ if (!current_font_data_for_range_set->FontData()) {
+ DCHECK(!range_data->reshape_queue.size());
+ break;
+ }
+ font_cycle_queued = false;
+ continue;
+ }
+
+ const SimpleFontData* font_data =
+ current_font_data_for_range_set->FontData();
+ SmallCapsIterator::SmallCapsBehavior small_caps_behavior =
+ SmallCapsIterator::kSmallCapsSameCase;
+ if (needs_caps_handling) {
+ caps_support = OpenTypeCapsSupport(
+ font_data->PlatformData().GetHarfBuzzFace(),
+ font_description.VariantCaps(), ICUScriptToHBScript(segment.script));
+ if (caps_support.NeedsRunCaseSplitting()) {
+ SplitUntilNextCaseChange(text_, &range_data->reshape_queue,
+ current_queue_item, small_caps_behavior);
+ // Skip queue items generated by SplitUntilNextCaseChange that do not
+ // contribute to the shape result if the range_data restricts shaping to
+ // a substring.
+ if (range_data->start >= current_queue_item.start_index_ +
+ current_queue_item.num_characters_ ||
+ range_data->end <= current_queue_item.start_index_)
+ continue;
+ }
+ }
+
+ DCHECK(current_queue_item.num_characters_);
+ const SimpleFontData* small_caps_adjusted_font =
+ needs_caps_handling &&
+ caps_support.NeedsSyntheticFont(small_caps_behavior)
+ ? font_data->SmallCapsFontData(font_description).get()
+ : font_data;
+
+ CaseMapIntend case_map_intend = CaseMapIntend::kKeepSameCase;
+ if (needs_caps_handling)
+ case_map_intend = caps_support.NeedsCaseChange(small_caps_behavior);
+
+ // Clamp the start and end offsets of the queue item to the offsets
+ // representing the shaping window.
+ unsigned shape_start =
+ std::max(range_data->start, current_queue_item.start_index_);
+ unsigned shape_end =
+ std::min(range_data->end, current_queue_item.start_index_ +
+ current_queue_item.num_characters_);
+ DCHECK_GT(shape_end, shape_start);
+
+ CaseMappingHarfBuzzBufferFiller(
+ case_map_intend, font_description.LocaleOrDefault(), range_data->buffer,
+ text_, text_length_, shape_start, shape_end - shape_start);
+
+ CanvasRotationInVertical canvas_rotation = CanvasRotationForRun(
+ small_caps_adjusted_font->PlatformData().Orientation(),
+ segment.render_orientation);
+
+ CapsFeatureSettingsScopedOverlay caps_overlay(
+ &range_data->font_features,
+ caps_support.FontFeatureToUse(small_caps_behavior));
+ hb_direction_t direction = range_data->HarfBuzzDirection(canvas_rotation);
+
+ if (!ShapeRange(range_data->buffer,
+ range_data->font_features.IsEmpty()
+ ? nullptr
+ : range_data->font_features.data(),
+ range_data->font_features.size(), small_caps_adjusted_font,
+ current_font_data_for_range_set->Ranges(), segment.script,
+ direction, language))
+ DLOG(ERROR) << "Shaping range failed.";
+
+ ExtractShapeResults(range_data, font_cycle_queued, current_queue_item,
+ small_caps_adjusted_font, segment.script,
+ canvas_rotation, !fallback_iterator->HasNext(), result);
+
+ hb_buffer_reset(range_data->buffer);
+ }
+}
+
+scoped_refptr<ShapeResult> HarfBuzzShaper::Shape(const Font* font,
+ TextDirection direction,
+ unsigned start,
+ unsigned end) const {
+ DCHECK(end >= start);
+ DCHECK(end <= text_length_);
+
+ unsigned length = end - start;
+ scoped_refptr<ShapeResult> result = ShapeResult::Create(font, length, direction);
+ HarfBuzzScopedPtr<hb_buffer_t> buffer(hb_buffer_create(), hb_buffer_destroy);
+
+ // Run segmentation needs to operate on the entire string, regardless of the
+ // shaping window (defined by the start and end parameters).
+ RunSegmenter::RunSegmenterRange segment_range = RunSegmenter::NullRange();
+ RunSegmenter run_segmenter(text_, text_length_,
+ font->GetFontDescription().Orientation());
+
+ RangeData range_data;
+ range_data.buffer = buffer.Get();
+ range_data.font = font;
+ range_data.text_direction = direction;
+ range_data.start = start;
+ range_data.end = end;
+ SetFontFeatures(font, &range_data.font_features);
+
+ while (run_segmenter.Consume(&segment_range)) {
+ // Only shape segments overlapping with the range indicated by start and
+ // end. Not only those strictly within.
+ if (start < segment_range.end && end > segment_range.start)
+ ShapeSegment(&range_data, segment_range, result.get());
+ }
+
+ // Ensure we have at least one run for StartIndexForResult().
+ if (UNLIKELY(result->runs_.IsEmpty() && start))
+ result->InsertRunForIndex(start);
+
+#if DCHECK_IS_ON()
+ if (result)
+ CheckShapeResultRange(result.get(), start, end, text_, font);
+#endif
+
+ return result;
+}
+
+scoped_refptr<ShapeResult> HarfBuzzShaper::Shape(const Font* font,
+ TextDirection direction) const {
+ return Shape(font, direction, 0, text_length_);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h
new file mode 100644
index 00000000000..5e86364a4ae
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_HARF_BUZZ_SHAPER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_HARF_BUZZ_SHAPER_H_
+
+#include "third_party/blink/renderer/platform/fonts/shaping/run_segmenter.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+class Font;
+class SimpleFontData;
+class HarfBuzzShaper;
+struct ReshapeQueueItem;
+struct RangeData;
+struct BufferSlice;
+
+class PLATFORM_EXPORT HarfBuzzShaper final {
+ public:
+ HarfBuzzShaper(const UChar*, unsigned length);
+
+ // Shape a range, defined by the start and end parameters, of the string
+ // supplied to the constructor.
+ // The start and end positions should represent boundaries where a break may
+ // occur, such as at the beginning or end of lines or at element boundaries.
+ // If given arbitrary positions the results are not guaranteed to be correct.
+ // May be called multiple times; font and direction may vary between calls.
+ scoped_refptr<ShapeResult> Shape(const Font*,
+ TextDirection,
+ unsigned start,
+ unsigned end) const;
+
+ // Shape the entire string with a single font and direction.
+ // Equivalent to calling the range version with a start offset of zero and an
+ // end offset equal to the length.
+ scoped_refptr<ShapeResult> Shape(const Font*, TextDirection) const;
+
+ const UChar* GetText() const { return text_; }
+ unsigned TextLength() const { return text_length_; }
+
+ ~HarfBuzzShaper() = default;
+
+ private:
+
+ // Shapes a single seqment, as identified by the RunSegmenterRange parameter,
+ // one or more times taking font fallback into account. The start and end
+ // parameters are for the entire text run, not the segment, and are used to
+ // determine pre- and post-context for shaping.
+ void ShapeSegment(RangeData*,
+ RunSegmenter::RunSegmenterRange,
+ ShapeResult*) const;
+
+ void ExtractShapeResults(RangeData*,
+ bool& font_cycle_queued,
+ const ReshapeQueueItem&,
+ const SimpleFontData*,
+ UScriptCode,
+ CanvasRotationInVertical,
+ bool is_last_resort,
+ ShapeResult*) const;
+
+ bool CollectFallbackHintChars(const Deque<ReshapeQueueItem>&,
+ Vector<UChar32>& hint) const;
+
+ void CommitGlyphs(RangeData*,
+ const SimpleFontData* current_font,
+ UScriptCode current_run_script,
+ CanvasRotationInVertical,
+ bool is_last_resort,
+ const BufferSlice&,
+ ShapeResult*) const;
+
+ const UChar* text_;
+ unsigned text_length_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_HARF_BUZZ_SHAPER_H_
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper_fuzzer.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper_fuzzer.cc
new file mode 100644
index 00000000000..7778209f5a8
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper_fuzzer.cc
@@ -0,0 +1,52 @@
+// Copyright 2017 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/font.h"
+#include "third_party/blink/renderer/platform/fonts/font_cache.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h"
+#include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <unicode/ustring.h>
+
+namespace blink {
+
+constexpr size_t kMaxInputLength = 256;
+
+// TODO crbug.com/771901: BlinkFuzzerTestSupport should also initialize the
+// custom fontconfig configuration that we use for content_shell.
+int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ static BlinkFuzzerTestSupport fuzzer_support = BlinkFuzzerTestSupport();
+ constexpr int32_t kDestinationCapacity = 2 * kMaxInputLength;
+ int32_t converted_length = 0;
+ UChar converted_input_buffer[kDestinationCapacity] = {0};
+ UErrorCode error_code = U_ZERO_ERROR;
+
+ // Discard trailing bytes.
+ u_strFromUTF32(converted_input_buffer, kDestinationCapacity,
+ &converted_length, reinterpret_cast<const UChar32*>(data),
+ size / sizeof(UChar32), &error_code);
+ if (U_FAILURE(error_code))
+ return 0;
+
+ FontCachePurgePreventer font_cache_purge_preventer;
+ FontDescription font_description;
+ Font font(font_description);
+ // Set font size to something other than the default 0 size in
+ // FontDescription, 16 matches the default text size in HTML.
+ font_description.SetComputedSize(16.0f);
+ // Only look for system fonts for now.
+ font.Update(nullptr);
+
+ HarfBuzzShaper shaper(converted_input_buffer, converted_length);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
+ return 0;
+}
+
+} // namespace blink
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ return blink::LLVMFuzzerTestOneInput(data, size);
+}
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper_test.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper_test.cc
new file mode 100644
index 00000000000..f038dd7f45a
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper_test.cc
@@ -0,0 +1,1258 @@
+// 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/harf_buzz_shaper.h"
+
+#include <unicode/uscript.h>
+
+#include "build/build_config.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/shape_result_inline_headers.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h"
+#include "third_party/blink/renderer/platform/layout_test_support.h"
+#include "third_party/blink/renderer/platform/testing/font_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+#include "third_party/blink/renderer/platform/text/text_break_iterator.h"
+#include "third_party/blink/renderer/platform/text/text_run.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+class HarfBuzzShaperTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ font_description.SetComputedSize(12.0);
+ font = Font(font_description);
+ font.Update(nullptr);
+ }
+
+ void TearDown() override {}
+
+ FontCachePurgePreventer font_cache_purge_preventer;
+ FontDescription font_description;
+ Font font;
+ unsigned start_index = 0;
+ unsigned num_characters = 0;
+ unsigned num_glyphs = 0;
+ hb_script_t script = HB_SCRIPT_INVALID;
+};
+
+class ScopedSubpixelOverride {
+ public:
+ ScopedSubpixelOverride(bool b) {
+ prev_layout_test_ = LayoutTestSupport::IsRunningLayoutTest();
+ prev_subpixel_allowed_ =
+ LayoutTestSupport::IsTextSubpixelPositioningAllowedForTest();
+ prev_antialias_ = LayoutTestSupport::IsFontAntialiasingEnabledForTest();
+ prev_fd_subpixel_ = FontDescription::SubpixelPositioning();
+
+ // This is required for all LayoutTestSupport settings to have effects.
+ LayoutTestSupport::SetIsRunningLayoutTest(true);
+
+ if (b) {
+ // Allow subpixel positioning.
+ LayoutTestSupport::SetTextSubpixelPositioningAllowedForTest(true);
+
+ // Now, enable subpixel positioning in platform-specific ways.
+
+ // Mac always enables subpixel positioning.
+
+ // On Windows, subpixel positioning also requires antialiasing.
+ LayoutTestSupport::SetFontAntialiasingEnabledForTest(true);
+
+ // On platforms other than Windows and Mac this needs to be set as
+ // well.
+ FontDescription::SetSubpixelPositioning(true);
+ } else {
+ // Explicitly disallow all subpixel positioning.
+ LayoutTestSupport::SetTextSubpixelPositioningAllowedForTest(false);
+ }
+ }
+ ~ScopedSubpixelOverride() {
+ FontDescription::SetSubpixelPositioning(prev_fd_subpixel_);
+ LayoutTestSupport::SetFontAntialiasingEnabledForTest(prev_antialias_);
+ LayoutTestSupport::SetTextSubpixelPositioningAllowedForTest(
+ prev_subpixel_allowed_);
+ LayoutTestSupport::SetIsRunningLayoutTest(prev_layout_test_);
+
+ // Fonts cached with a different subpixel positioning state are not
+ // automatically invalidated and need to be cleared between test
+ // runs.
+ FontCache::GetFontCache()->Invalidate();
+ }
+
+ private:
+ bool prev_layout_test_;
+ bool prev_subpixel_allowed_;
+ bool prev_antialias_;
+ bool prev_fd_subpixel_;
+};
+
+class ShapeParameterTest : public HarfBuzzShaperTest,
+ public testing::WithParamInterface<TextDirection> {
+ protected:
+ scoped_refptr<ShapeResult> ShapeWithParameter(HarfBuzzShaper* shaper) {
+ TextDirection direction = GetParam();
+ return shaper->Shape(&font, direction);
+ }
+};
+
+INSTANTIATE_TEST_CASE_P(HarfBuzzShaperTest,
+ ShapeParameterTest,
+ testing::Values(TextDirection::kLtr,
+ TextDirection::kRtl));
+
+static inline ShapeResultTestInfo* TestInfo(scoped_refptr<ShapeResult>& result) {
+ return static_cast<ShapeResultTestInfo*>(result.get());
+}
+
+TEST_F(HarfBuzzShaperTest, MutableUnique) {
+ scoped_refptr<ShapeResult> result =
+ ShapeResult::Create(&font, 0, TextDirection::kLtr);
+ EXPECT_TRUE(result->HasOneRef());
+
+ // At this point, |result| has only one ref count.
+ scoped_refptr<ShapeResult> result2 = result->MutableUnique();
+ EXPECT_EQ(result.get(), result2.get());
+ EXPECT_FALSE(result2->HasOneRef());
+
+ // Since |result| has 2 ref counts, it should return a clone.
+ scoped_refptr<ShapeResult> result3 = result->MutableUnique();
+ EXPECT_NE(result.get(), result3.get());
+ EXPECT_TRUE(result3->HasOneRef());
+}
+
+TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsLatin) {
+ String latin_common = To16Bit("ABC DEF.", 8);
+ HarfBuzzShaper shaper(latin_common.Characters16(), 8);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
+
+ EXPECT_EQ(1u, TestInfo(result)->NumberOfRunsForTesting());
+ ASSERT_TRUE(
+ TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script));
+ EXPECT_EQ(0u, start_index);
+ EXPECT_EQ(8u, num_glyphs);
+ EXPECT_EQ(HB_SCRIPT_LATIN, script);
+}
+
+TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsLeadingCommon) {
+ String leading_common = To16Bit("... test", 8);
+ HarfBuzzShaper shaper(leading_common.Characters16(), 8);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
+
+ EXPECT_EQ(1u, TestInfo(result)->NumberOfRunsForTesting());
+ ASSERT_TRUE(
+ TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script));
+ EXPECT_EQ(0u, start_index);
+ EXPECT_EQ(8u, num_glyphs);
+ EXPECT_EQ(HB_SCRIPT_LATIN, script);
+}
+
+TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsUnicodeVariants) {
+ struct {
+ const char* name;
+ UChar string[4];
+ unsigned length;
+ hb_script_t script;
+ } testlist[] = {
+ {"Standard Variants text style", {0x30, 0xFE0E}, 2, HB_SCRIPT_COMMON},
+ {"Standard Variants emoji style", {0x203C, 0xFE0F}, 2, HB_SCRIPT_COMMON},
+ {"Standard Variants of Ideograph", {0x4FAE, 0xFE00}, 2, HB_SCRIPT_HAN},
+ {"Ideographic Variants", {0x3402, 0xDB40, 0xDD00}, 3, HB_SCRIPT_HAN},
+ {"Not-defined Variants", {0x41, 0xDB40, 0xDDEF}, 3, HB_SCRIPT_LATIN},
+ };
+ for (auto& test : testlist) {
+ HarfBuzzShaper shaper(test.string, test.length);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
+
+ EXPECT_EQ(1u, TestInfo(result)->NumberOfRunsForTesting()) << test.name;
+ ASSERT_TRUE(
+ TestInfo(result)->RunInfoForTesting(0, start_index, num_glyphs, script))
+ << test.name;
+ EXPECT_EQ(0u, start_index) << test.name;
+ if (num_glyphs == 2) {
+// If the specified VS is not in the font, it's mapped to .notdef.
+// then hb_ot_hide_default_ignorables() swaps it to a space with zero-advance.
+// http://lists.freedesktop.org/archives/harfbuzz/2015-May/004888.html
+#if !defined(OS_MACOSX)
+ EXPECT_EQ(TestInfo(result)->FontDataForTesting(0)->SpaceGlyph(),
+ TestInfo(result)->GlyphForTesting(0, 1))
+ << test.name;
+#endif
+ EXPECT_EQ(0.f, TestInfo(result)->AdvanceForTesting(0, 1)) << test.name;
+ } else {
+ EXPECT_EQ(1u, num_glyphs) << test.name;
+ }
+ EXPECT_EQ(test.script, script) << test.name;
+ }
+}
+
+TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsDevanagariCommon) {
+ UChar devanagari_common_string[] = {0x915, 0x94d, 0x930, 0x28, 0x20, 0x29};
+ String devanagari_common_latin(devanagari_common_string, 6);
+ HarfBuzzShaper shaper(devanagari_common_latin.Characters16(), 6);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
+
+ EXPECT_EQ(2u, TestInfo(result)->NumberOfRunsForTesting());
+ 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_DEVANAGARI, script);
+
+ ASSERT_TRUE(
+ TestInfo(result)->RunInfoForTesting(1, start_index, num_glyphs, script));
+ EXPECT_EQ(3u, start_index);
+ EXPECT_EQ(3u, num_glyphs);
+ EXPECT_EQ(HB_SCRIPT_DEVANAGARI, script);
+}
+
+TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsDevanagariCommonLatinCommon) {
+ UChar devanagari_common_latin_string[] = {0x915, 0x94d, 0x930, 0x20,
+ 0x61, 0x62, 0x2E};
+ HarfBuzzShaper shaper(devanagari_common_latin_string, 7);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
+
+ EXPECT_EQ(3u, TestInfo(result)->NumberOfRunsForTesting());
+ 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_DEVANAGARI, script);
+
+ ASSERT_TRUE(
+ TestInfo(result)->RunInfoForTesting(1, start_index, num_glyphs, script));
+ EXPECT_EQ(3u, start_index);
+ EXPECT_EQ(1u, num_glyphs);
+ EXPECT_EQ(HB_SCRIPT_DEVANAGARI, script);
+
+ ASSERT_TRUE(
+ TestInfo(result)->RunInfoForTesting(2, start_index, num_glyphs, script));
+ EXPECT_EQ(4u, start_index);
+ EXPECT_EQ(3u, num_glyphs);
+ EXPECT_EQ(HB_SCRIPT_LATIN, script);
+}
+
+TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsArabicThaiHanLatin) {
+ UChar mixed_string[] = {0x628, 0x64A, 0x629, 0xE20, 0x65E5, 0x62};
+ HarfBuzzShaper shaper(mixed_string, 6);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
+
+ EXPECT_EQ(4u, TestInfo(result)->NumberOfRunsForTesting());
+ 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_ARABIC, script);
+
+ ASSERT_TRUE(
+ TestInfo(result)->RunInfoForTesting(1, start_index, num_glyphs, script));
+ EXPECT_EQ(3u, start_index);
+ EXPECT_EQ(1u, num_glyphs);
+ EXPECT_EQ(HB_SCRIPT_THAI, script);
+
+ ASSERT_TRUE(
+ TestInfo(result)->RunInfoForTesting(2, start_index, num_glyphs, script));
+ EXPECT_EQ(4u, start_index);
+ EXPECT_EQ(1u, num_glyphs);
+ EXPECT_EQ(HB_SCRIPT_HAN, script);
+
+ ASSERT_TRUE(
+ TestInfo(result)->RunInfoForTesting(3, start_index, num_glyphs, script));
+ EXPECT_EQ(5u, start_index);
+ EXPECT_EQ(1u, num_glyphs);
+ EXPECT_EQ(HB_SCRIPT_LATIN, script);
+}
+
+TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsArabicThaiHanLatinTwice) {
+ UChar mixed_string[] = {0x628, 0x64A, 0x629, 0xE20, 0x65E5, 0x62};
+ HarfBuzzShaper shaper(mixed_string, 6);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
+ EXPECT_EQ(4u, TestInfo(result)->NumberOfRunsForTesting());
+
+ // Shape again on the same shape object and check the number of runs.
+ // Should be equal if no state was retained between shape calls.
+ scoped_refptr<ShapeResult> result2 = shaper.Shape(&font, TextDirection::kLtr);
+ EXPECT_EQ(4u, TestInfo(result2)->NumberOfRunsForTesting());
+}
+
+TEST_F(HarfBuzzShaperTest, ResolveCandidateRunsArabic) {
+ UChar arabic_string[] = {0x628, 0x64A, 0x629};
+ HarfBuzzShaper shaper(arabic_string, 3);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kRtl);
+
+ EXPECT_EQ(1u, TestInfo(result)->NumberOfRunsForTesting());
+ 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_ARABIC, script);
+}
+
+// This is a simplified test and doesn't accuratly reflect how the shape range
+// is to be used. If you instead of the string you imagine the following HTML:
+// <div>Hello <span>World</span>!</div>
+// It better reflects the intended use where the range given to each shape call
+// corresponds to the text content of a TextNode.
+TEST_F(HarfBuzzShaperTest, ShapeLatinSegment) {
+ String string = To16Bit("Hello World!", 12);
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(string.Characters16(), 12);
+ scoped_refptr<ShapeResult> combined = shaper.Shape(&font, direction);
+ scoped_refptr<ShapeResult> first = shaper.Shape(&font, direction, 0, 6);
+ scoped_refptr<ShapeResult> second = shaper.Shape(&font, direction, 6, 11);
+ scoped_refptr<ShapeResult> third = shaper.Shape(&font, direction, 11, 12);
+
+ ASSERT_TRUE(TestInfo(first)->RunInfoForTesting(0, start_index, num_characters,
+ num_glyphs, script));
+ EXPECT_EQ(0u, start_index);
+ EXPECT_EQ(6u, num_characters);
+ ASSERT_TRUE(TestInfo(second)->RunInfoForTesting(
+ 0, start_index, num_characters, num_glyphs, script));
+ EXPECT_EQ(6u, start_index);
+ EXPECT_EQ(5u, num_characters);
+ ASSERT_TRUE(TestInfo(third)->RunInfoForTesting(0, start_index, num_characters,
+ num_glyphs, script));
+ EXPECT_EQ(11u, start_index);
+ EXPECT_EQ(1u, num_characters);
+
+ HarfBuzzShaper shaper2(string.Characters16(), 6);
+ scoped_refptr<ShapeResult> first_reference = shaper2.Shape(&font, direction);
+
+ HarfBuzzShaper shaper3(string.Characters16() + 6, 5);
+ scoped_refptr<ShapeResult> second_reference = shaper3.Shape(&font, direction);
+
+ HarfBuzzShaper shaper4(string.Characters16() + 11, 1);
+ scoped_refptr<ShapeResult> third_reference = shaper4.Shape(&font, direction);
+
+ // Width of each segment should be the same when shaped using start and end
+ // offset as it is when shaping the three segments using separate shaper
+ // instances.
+ // A full pixel is needed for tolerance to account for kerning on some
+ // platforms.
+ ASSERT_NEAR(first_reference->Width(), first->Width(), 1);
+ ASSERT_NEAR(second_reference->Width(), second->Width(), 1);
+ ASSERT_NEAR(third_reference->Width(), third->Width(), 1);
+
+ // Width of shape results for the entire string should match the combined
+ // shape results from the three segments.
+ float total_width = first->Width() + second->Width() + third->Width();
+ ASSERT_NEAR(combined->Width(), total_width, 1);
+}
+
+// Represents the case where a part of a cluster has a different color.
+// <div>0x647<span style="color: red;">0x64A</span></div>
+// This test requires context-aware shaping which hasn't been implemented yet.
+// See crbug.com/689155
+TEST_F(HarfBuzzShaperTest, DISABLED_ShapeArabicWithContext) {
+ UChar arabic_string[] = {0x647, 0x64A};
+ HarfBuzzShaper shaper(arabic_string, 2);
+
+ scoped_refptr<ShapeResult> combined = shaper.Shape(&font, TextDirection::kRtl);
+
+ scoped_refptr<ShapeResult> first = shaper.Shape(&font, TextDirection::kRtl, 0, 1);
+ scoped_refptr<ShapeResult> second = shaper.Shape(&font, TextDirection::kRtl, 1, 2);
+
+ // Combined width should be the same when shaping the two characters
+ // separately as when shaping them combined.
+ ASSERT_NEAR(combined->Width(), first->Width() + second->Width(), 0.1);
+}
+
+TEST_F(HarfBuzzShaperTest, ShapeVerticalUpright) {
+ font_description.SetOrientation(FontOrientation::kVerticalUpright);
+ font = Font(font_description);
+ font.Update(nullptr);
+
+ // This string should create 2 runs, ideographic and Latin, both in upright.
+ String string(u"\u65E5\u65E5\u65E5lllll");
+ TextDirection direction = TextDirection::kLtr;
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ // Check width and bounds are not too much different. ".1" is heuristic.
+ EXPECT_NEAR(result->Width(), result->Bounds().Width(), result->Width() * .1);
+
+ // Shape each run and merge them using CopyRange. Bounds() should match.
+ scoped_refptr<ShapeResult> result1 = shaper.Shape(&font, direction, 0, 3);
+ scoped_refptr<ShapeResult> result2 =
+ shaper.Shape(&font, direction, 3, string.length());
+
+ scoped_refptr<ShapeResult> composite_result =
+ ShapeResult::Create(&font, 0, direction);
+ result1->CopyRange(0, 3, composite_result.get());
+ result2->CopyRange(3, string.length(), composite_result.get());
+
+ EXPECT_EQ(result->Bounds(), composite_result->Bounds());
+}
+
+TEST_F(HarfBuzzShaperTest, RangeShapeSmallCaps) {
+ // Test passes if no assertion is hit of the ones below, but also the newly
+ // introduced one in HarfBuzzShaper::ShapeSegment: DCHECK_GT(shape_end,
+ // shape_start) is not hit.
+ FontDescription font_description;
+ font_description.SetVariantCaps(FontDescription::kSmallCaps);
+ font_description.SetComputedSize(12.0);
+ Font font(font_description);
+ font.Update(nullptr);
+
+ // Shaping index 2 to 3 means that case splitting for small caps splits before
+ // character index 2 since the initial 'a' needs to be uppercased, but the
+ // space character does not need to be uppercased. This triggered
+ // crbug.com/817271.
+ String string(u"a aa");
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result =
+ shaper.Shape(&font, TextDirection::kLtr, 2, 3);
+ EXPECT_EQ(1u, result->NumCharacters());
+
+ string = u"aa a";
+ HarfBuzzShaper shaper_two(string.Characters16(), string.length());
+ result = shaper_two.Shape(&font, TextDirection::kLtr, 3, 4);
+ EXPECT_EQ(1u, result->NumCharacters());
+
+ string = u"a aa";
+ HarfBuzzShaper shaper_three(string.Characters16(), string.length());
+ result = shaper_three.Shape(&font, TextDirection::kLtr, 1, 2);
+ EXPECT_EQ(1u, result->NumCharacters());
+
+ string = u"aa aa aa aa aa aa aa aa aa aa";
+ HarfBuzzShaper shaper_four(string.Characters16(), string.length());
+ result = shaper_four.Shape(&font, TextDirection::kLtr, 21, 23);
+ EXPECT_EQ(2u, result->NumCharacters());
+
+ string = u"aa aa aa aa aa aa aa aa aa aa";
+ HarfBuzzShaper shaper_five(string.Characters16(), string.length());
+ result = shaper_five.Shape(&font, TextDirection::kLtr, 27, 29);
+ EXPECT_EQ(2u, result->NumCharacters());
+}
+
+TEST_F(HarfBuzzShaperTest, ShapeVerticalMixed) {
+ font_description.SetOrientation(FontOrientation::kVerticalMixed);
+ font = Font(font_description);
+ font.Update(nullptr);
+
+ // This string should create 2 runs, ideographic in upright and Latin in
+ // rotated horizontal.
+ String string(u"\u65E5\u65E5\u65E5lllll");
+ TextDirection direction = TextDirection::kLtr;
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ // Check width and bounds are not too much different. ".1" is heuristic.
+ EXPECT_NEAR(result->Width(), result->Bounds().Width(), result->Width() * .1);
+
+ // Shape each run and merge them using CopyRange. Bounds() should match.
+ scoped_refptr<ShapeResult> result1 = shaper.Shape(&font, direction, 0, 3);
+ scoped_refptr<ShapeResult> result2 =
+ shaper.Shape(&font, direction, 3, string.length());
+
+ scoped_refptr<ShapeResult> composite_result =
+ ShapeResult::Create(&font, 0, direction);
+ result1->CopyRange(0, 3, composite_result.get());
+ result2->CopyRange(3, string.length(), composite_result.get());
+
+ EXPECT_EQ(result->Bounds(), composite_result->Bounds());
+}
+
+TEST_P(ShapeParameterTest, MissingGlyph) {
+ // U+FFF0 is not assigned as of Unicode 10.0.
+ String string(
+ u"\uFFF0"
+ u"Hello");
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = ShapeWithParameter(&shaper);
+ EXPECT_EQ(0u, result->StartIndexForResult());
+ EXPECT_EQ(string.length(), result->EndIndexForResult());
+}
+
+TEST_P(ShapeParameterTest, ZeroWidthSpace) {
+ UChar string[] = {kZeroWidthSpaceCharacter,
+ kZeroWidthSpaceCharacter,
+ 0x0627,
+ 0x0631,
+ 0x062F,
+ 0x0648,
+ kZeroWidthSpaceCharacter,
+ kZeroWidthSpaceCharacter};
+ const unsigned length = WTF_ARRAY_LENGTH(string);
+ HarfBuzzShaper shaper(string, length);
+ scoped_refptr<ShapeResult> result = ShapeWithParameter(&shaper);
+ EXPECT_EQ(0u, result->StartIndexForResult());
+ EXPECT_EQ(length, result->EndIndexForResult());
+#if DCHECK_IS_ON()
+ result->CheckConsistency();
+#endif
+}
+
+TEST_F(HarfBuzzShaperTest, NegativeLetterSpacing) {
+ String string(u"Hello");
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
+ float width = result->Width();
+ FloatRect bounds = result->Bounds();
+
+ ShapeResultSpacing<String> spacing(string);
+ FontDescription font_description;
+ font_description.SetLetterSpacing(-5);
+ spacing.SetSpacing(font_description);
+ result->ApplySpacing(spacing);
+
+ EXPECT_EQ(5 * 5, width - result->Width());
+ EXPECT_EQ(5 * 4 - 1, bounds.Width() - result->Bounds().Width());
+}
+
+TEST_F(HarfBuzzShaperTest, NegativeLetterSpacingTo0) {
+ String string(u"00000");
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
+ float char_width = result->Width() / string.length();
+
+ ShapeResultSpacing<String> spacing(string);
+ FontDescription font_description;
+ font_description.SetLetterSpacing(-char_width);
+ spacing.SetSpacing(font_description);
+ result->ApplySpacing(spacing);
+
+ // EXPECT_EQ(0.0f, result->Width());
+ EXPECT_NEAR(0.0f, result->Bounds().X(), 1);
+ // Because all characters are at 0, the glyph bounds must be the char_width.
+ // Allow being larger because accurate width requires re-measuring each glyph.
+ EXPECT_GE(result->Bounds().MaxX(), char_width);
+ EXPECT_LE(result->Bounds().MaxX(), char_width * 1.2);
+}
+
+TEST_F(HarfBuzzShaperTest, NegativeLetterSpacingToNegative) {
+ String string(u"00000");
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
+ float char_width = result->Width() / string.length();
+
+ ShapeResultSpacing<String> spacing(string);
+ FontDescription font_description;
+ font_description.SetLetterSpacing(-2 * char_width);
+ spacing.SetSpacing(font_description);
+ result->ApplySpacing(spacing);
+
+ // CSS does not allow negative width, it should be clampled to 0.
+ // EXPECT_EQ(0.0f, result->Width());
+ // Glyph bounding box should overflow to the left.
+ EXPECT_EQ(-char_width * string.length(), result->Bounds().X());
+ // MaxX() should be char_width. Allow being larger.
+ EXPECT_GE(result->Bounds().MaxX(), char_width);
+}
+
+TEST_F(HarfBuzzShaperTest, PositionForOffsetLatin) {
+ String string = To16Bit("Hello World!", 12);
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(string.Characters16(), 12);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+ scoped_refptr<ShapeResult> first = shaper.Shape(&font, direction, 0, 5); // Hello
+ scoped_refptr<ShapeResult> second = shaper.Shape(&font, direction, 6, 11); // World
+
+ EXPECT_EQ(0.0f, result->PositionForOffset(0));
+ ASSERT_NEAR(first->Width(), result->PositionForOffset(5), 1);
+ ASSERT_NEAR(second->Width(),
+ result->PositionForOffset(11) - result->PositionForOffset(6), 1);
+ ASSERT_NEAR(result->Width(), result->PositionForOffset(12), 0.1);
+}
+
+TEST_F(HarfBuzzShaperTest, PositionForOffsetArabic) {
+ UChar arabic_string[] = {0x628, 0x64A, 0x629};
+ TextDirection direction = TextDirection::kRtl;
+
+ HarfBuzzShaper shaper(arabic_string, 3);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ EXPECT_EQ(0.0f, result->PositionForOffset(3));
+ ASSERT_NEAR(result->Width(), result->PositionForOffset(0), 0.1);
+}
+
+TEST_F(HarfBuzzShaperTest, EmojiZWJSequence) {
+ UChar emoji_zwj_sequence[] = {0x270C, 0x200D, 0xD83C, 0xDFFF,
+ 0x270C, 0x200D, 0xD83C, 0xDFFC};
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(emoji_zwj_sequence, arraysize(emoji_zwj_sequence));
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+}
+
+// A Value-Parameterized Test class to test OffsetForPosition() with
+// |include_partial_glyphs| parameter.
+class IncludePartialGlyphs : public HarfBuzzShaperTest,
+ public testing::WithParamInterface<bool> {};
+
+INSTANTIATE_TEST_CASE_P(OffsetForPositionTest,
+ IncludePartialGlyphs,
+ testing::Bool());
+
+TEST_P(IncludePartialGlyphs, OffsetForPositionMatchesPositionForOffsetLatin) {
+ String string = To16Bit("Hello World!", 12);
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(string.Characters16(), 12);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ bool include_partial_glyphs = GetParam();
+ EXPECT_EQ(0u, result->OffsetForPosition(result->PositionForOffset(0),
+ include_partial_glyphs));
+ EXPECT_EQ(1u, result->OffsetForPosition(result->PositionForOffset(1),
+ include_partial_glyphs));
+ EXPECT_EQ(2u, result->OffsetForPosition(result->PositionForOffset(2),
+ include_partial_glyphs));
+ EXPECT_EQ(3u, result->OffsetForPosition(result->PositionForOffset(3),
+ include_partial_glyphs));
+ EXPECT_EQ(4u, result->OffsetForPosition(result->PositionForOffset(4),
+ include_partial_glyphs));
+ EXPECT_EQ(5u, result->OffsetForPosition(result->PositionForOffset(5),
+ include_partial_glyphs));
+ EXPECT_EQ(6u, result->OffsetForPosition(result->PositionForOffset(6),
+ include_partial_glyphs));
+ EXPECT_EQ(7u, result->OffsetForPosition(result->PositionForOffset(7),
+ include_partial_glyphs));
+ EXPECT_EQ(8u, result->OffsetForPosition(result->PositionForOffset(8),
+ include_partial_glyphs));
+ EXPECT_EQ(9u, result->OffsetForPosition(result->PositionForOffset(9),
+ include_partial_glyphs));
+ EXPECT_EQ(10u, result->OffsetForPosition(result->PositionForOffset(10),
+ include_partial_glyphs));
+ EXPECT_EQ(11u, result->OffsetForPosition(result->PositionForOffset(11),
+ include_partial_glyphs));
+ EXPECT_EQ(12u, result->OffsetForPosition(result->PositionForOffset(12),
+ include_partial_glyphs));
+}
+
+TEST_P(IncludePartialGlyphs, OffsetForPositionMatchesPositionForOffsetArabic) {
+ UChar arabic_string[] = {0x628, 0x64A, 0x629};
+ TextDirection direction = TextDirection::kRtl;
+
+ HarfBuzzShaper shaper(arabic_string, 3);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ bool include_partial_glyphs = GetParam();
+ EXPECT_EQ(0u, result->OffsetForPosition(result->PositionForOffset(0),
+ include_partial_glyphs));
+ EXPECT_EQ(1u, result->OffsetForPosition(result->PositionForOffset(1),
+ include_partial_glyphs));
+ EXPECT_EQ(2u, result->OffsetForPosition(result->PositionForOffset(2),
+ include_partial_glyphs));
+ EXPECT_EQ(3u, result->OffsetForPosition(result->PositionForOffset(3),
+ include_partial_glyphs));
+}
+
+TEST_P(IncludePartialGlyphs, OffsetForPositionMatchesPositionForOffsetMixed) {
+ UChar mixed_string[] = {0x628, 0x64A, 0x629, 0xE20, 0x65E5, 0x62};
+ HarfBuzzShaper shaper(mixed_string, 6);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kLtr);
+
+ bool include_partial_glyphs = GetParam();
+ EXPECT_EQ(0u, result->OffsetForPosition(result->PositionForOffset(0),
+ include_partial_glyphs));
+ EXPECT_EQ(1u, result->OffsetForPosition(result->PositionForOffset(1),
+ include_partial_glyphs));
+ EXPECT_EQ(2u, result->OffsetForPosition(result->PositionForOffset(2),
+ include_partial_glyphs));
+ EXPECT_EQ(3u, result->OffsetForPosition(result->PositionForOffset(3),
+ include_partial_glyphs));
+ EXPECT_EQ(4u, result->OffsetForPosition(result->PositionForOffset(4),
+ include_partial_glyphs));
+ EXPECT_EQ(5u, result->OffsetForPosition(result->PositionForOffset(5),
+ include_partial_glyphs));
+ EXPECT_EQ(6u, result->OffsetForPosition(result->PositionForOffset(6),
+ include_partial_glyphs));
+}
+
+TEST_F(HarfBuzzShaperTest, PositionForOffsetMissingGlyph) {
+ String string(u"\u0633\u0644\u0627\u0645");
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kRtl);
+ // Because the offset 1 and 2 should form a ligature, SubRange(2, 4) creates a
+ // ShapeResult that does not have its first glyph.
+ result = result->SubRange(2, 4);
+ result->PositionForOffset(0);
+ // Pass if |PositionForOffset| does not crash.
+}
+
+static struct ShapeResultCopyRangeTestData {
+ const char16_t* string;
+ TextDirection direction;
+ unsigned break_point;
+} shape_result_copy_range_test_data[] = {
+ {u"ABC", TextDirection::kLtr, 1},
+ {u"\u0648\u0644\u064A", TextDirection::kRtl, 1},
+ // These strings creates 3 runs. Split it in the middle of 2nd run.
+ {u"\u65E5Hello\u65E5\u65E5", TextDirection::kLtr, 3},
+ {u"\u0648\u0644\u064A AB \u0628\u062A", TextDirection::kRtl, 5}};
+
+std::ostream& operator<<(std::ostream& ostream,
+ const ShapeResultCopyRangeTestData& data) {
+ return ostream << String(data.string) << " @ " << data.break_point << ", "
+ << data.direction;
+}
+
+class ShapeResultCopyRangeTest
+ : public HarfBuzzShaperTest,
+ public testing::WithParamInterface<ShapeResultCopyRangeTestData> {};
+
+INSTANTIATE_TEST_CASE_P(HarfBuzzShaperTest,
+ ShapeResultCopyRangeTest,
+ testing::ValuesIn(shape_result_copy_range_test_data));
+
+// Split a ShapeResult and combine them should match to the original result.
+TEST_P(ShapeResultCopyRangeTest, Split) {
+ const auto& test_data = GetParam();
+ String string(test_data.string);
+ TextDirection direction = test_data.direction;
+
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ // Split the result.
+ scoped_refptr<ShapeResult> result1 = ShapeResult::Create(&font, 0, direction);
+ result->CopyRange(0, test_data.break_point, result1.get());
+ EXPECT_EQ(test_data.break_point, result1->NumCharacters());
+ EXPECT_EQ(0u, result1->StartIndexForResult());
+ EXPECT_EQ(test_data.break_point, result1->EndIndexForResult());
+
+ scoped_refptr<ShapeResult> result2 = ShapeResult::Create(&font, 0, direction);
+ result->CopyRange(test_data.break_point, string.length(), result2.get());
+ EXPECT_EQ(string.length() - test_data.break_point, result2->NumCharacters());
+ EXPECT_EQ(test_data.break_point, result2->StartIndexForResult());
+ EXPECT_EQ(string.length(), result2->EndIndexForResult());
+
+ // Combine them.
+ scoped_refptr<ShapeResult> composite_result =
+ ShapeResult::Create(&font, 0, direction);
+ result1->CopyRange(0, test_data.break_point, composite_result.get());
+ result2->CopyRange(0, string.length(), composite_result.get());
+ EXPECT_EQ(string.length(), composite_result->NumCharacters());
+
+ // Test character indexes match.
+ Vector<unsigned> expected_character_indexes =
+ TestInfo(result)->CharacterIndexesForTesting();
+ Vector<unsigned> composite_character_indexes =
+ TestInfo(result)->CharacterIndexesForTesting();
+ EXPECT_EQ(expected_character_indexes, composite_character_indexes);
+}
+
+// Shape ranges and combine them shold match to the result of shaping the whole
+// string.
+TEST_P(ShapeResultCopyRangeTest, ShapeRange) {
+ const auto& test_data = GetParam();
+ String string(test_data.string);
+ TextDirection direction = test_data.direction;
+
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ // Shape each range.
+ scoped_refptr<ShapeResult> result1 =
+ shaper.Shape(&font, direction, 0, test_data.break_point);
+ EXPECT_EQ(test_data.break_point, result1->NumCharacters());
+ scoped_refptr<ShapeResult> result2 =
+ shaper.Shape(&font, direction, test_data.break_point, string.length());
+ EXPECT_EQ(string.length() - test_data.break_point, result2->NumCharacters());
+
+ // Combine them.
+ scoped_refptr<ShapeResult> composite_result =
+ ShapeResult::Create(&font, 0, direction);
+ result1->CopyRange(0, test_data.break_point, composite_result.get());
+ result2->CopyRange(0, string.length(), composite_result.get());
+ EXPECT_EQ(string.length(), composite_result->NumCharacters());
+
+ // Test character indexes match.
+ Vector<unsigned> expected_character_indexes =
+ TestInfo(result)->CharacterIndexesForTesting();
+ Vector<unsigned> composite_character_indexes =
+ TestInfo(result)->CharacterIndexesForTesting();
+ EXPECT_EQ(expected_character_indexes, composite_character_indexes);
+}
+
+TEST_F(HarfBuzzShaperTest, ShapeResultCopyRangeIntoLatin) {
+ String string = To16Bit("Testing ShapeResult::createSubRun", 33);
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(string.Characters16(), 33);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ scoped_refptr<ShapeResult> composite_result =
+ ShapeResult::Create(&font, 0, direction);
+ result->CopyRange(0, 10, composite_result.get());
+ result->CopyRange(10, 20, composite_result.get());
+ result->CopyRange(20, 30, composite_result.get());
+ result->CopyRange(30, 33, composite_result.get());
+
+ EXPECT_EQ(result->NumCharacters(), composite_result->NumCharacters());
+ EXPECT_EQ(result->SnappedWidth(), composite_result->SnappedWidth());
+ EXPECT_EQ(result->Bounds(), composite_result->Bounds());
+ EXPECT_EQ(result->SnappedStartPositionForOffset(0),
+ composite_result->SnappedStartPositionForOffset(0));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(15),
+ composite_result->SnappedStartPositionForOffset(15));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(30),
+ composite_result->SnappedStartPositionForOffset(30));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(33),
+ composite_result->SnappedStartPositionForOffset(33));
+}
+
+TEST_F(HarfBuzzShaperTest, ShapeResultCopyRangeIntoArabicThaiHanLatin) {
+ UChar mixed_string[] = {0x628, 0x20, 0x64A, 0x629, 0x20, 0xE20, 0x65E5, 0x62};
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(mixed_string, 8);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ // Check width and bounds are not too much different. ".2" is heuristic.
+ EXPECT_NEAR(result->Width(), result->Bounds().Width(), result->Width() * .2);
+
+ scoped_refptr<ShapeResult> composite_result =
+ ShapeResult::Create(&font, 0, direction);
+ result->CopyRange(0, 4, composite_result.get());
+ result->CopyRange(4, 6, composite_result.get());
+ result->CopyRange(6, 8, composite_result.get());
+
+ EXPECT_EQ(result->NumCharacters(), composite_result->NumCharacters());
+ EXPECT_EQ(result->SnappedWidth(), composite_result->SnappedWidth());
+ EXPECT_EQ(result->Bounds(), composite_result->Bounds());
+ EXPECT_EQ(result->SnappedStartPositionForOffset(0),
+ composite_result->SnappedStartPositionForOffset(0));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(1),
+ composite_result->SnappedStartPositionForOffset(1));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(2),
+ composite_result->SnappedStartPositionForOffset(2));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(3),
+ composite_result->SnappedStartPositionForOffset(3));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(4),
+ composite_result->SnappedStartPositionForOffset(4));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(5),
+ composite_result->SnappedStartPositionForOffset(5));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(6),
+ composite_result->SnappedStartPositionForOffset(6));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(7),
+ composite_result->SnappedStartPositionForOffset(7));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(8),
+ composite_result->SnappedStartPositionForOffset(8));
+}
+
+TEST_F(HarfBuzzShaperTest, ShapeResultCopyRangeAcrossRuns) {
+ // Create 3 runs:
+ // [0]: 1 character.
+ // [1]: 5 characters.
+ // [2]: 2 character.
+ String mixed_string(u"\u65E5Hello\u65E5\u65E5");
+ TextDirection direction = TextDirection::kLtr;
+ HarfBuzzShaper shaper(mixed_string.Characters16(), mixed_string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ // Check width and bounds are not too much different. ".1" is heuristic.
+ EXPECT_NEAR(result->Width(), result->Bounds().Width(), result->Width() * .1);
+
+ // CopyRange(5, 7) should copy 1 character from [1] and 1 from [2].
+ scoped_refptr<ShapeResult> target = ShapeResult::Create(&font, 0, direction);
+ result->CopyRange(5, 7, target.get());
+ EXPECT_EQ(2u, target->NumCharacters());
+}
+
+TEST_F(HarfBuzzShaperTest, ShapeResultCopyRangeSegmentGlyphBoundingBox) {
+ String string(u"THello worldL");
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result1 = shaper.Shape(&font, direction, 0, 6);
+ scoped_refptr<ShapeResult> result2 =
+ shaper.Shape(&font, direction, 6, string.length());
+
+ scoped_refptr<ShapeResult> composite_result =
+ ShapeResult::Create(&font, 0, direction);
+ result1->CopyRange(0, 6, composite_result.get());
+ result2->CopyRange(6, string.length(), composite_result.get());
+
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+ EXPECT_EQ(result->Bounds(), composite_result->Bounds());
+
+ // Check width and bounds are not too much different. ".1" is heuristic.
+ EXPECT_NEAR(result->Width(), result->Bounds().Width(), result->Width() * .1);
+}
+
+TEST_F(HarfBuzzShaperTest, SubRange) {
+ String string(u"Hello world");
+ TextDirection direction = TextDirection::kRtl;
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ scoped_refptr<ShapeResult> sub_range = result->SubRange(4, 7);
+ DCHECK_EQ(4u, sub_range->StartIndexForResult());
+ DCHECK_EQ(7u, sub_range->EndIndexForResult());
+ DCHECK_EQ(3u, sub_range->NumCharacters());
+ DCHECK_EQ(result->Direction(), sub_range->Direction());
+}
+
+TEST_F(HarfBuzzShaperTest, SafeToBreakLatinCommonLigatures) {
+ FontDescription::VariantLigatures ligatures;
+ ligatures.common = FontDescription::kEnabledLigaturesState;
+
+ // MEgalopolis Extra has a lot of ligatures which this test relies on.
+ Font testFont = blink::test::CreateTestFont(
+ "MEgalopolis",
+ blink::test::PlatformTestDataPath(
+ "third_party/MEgalopolis/MEgalopolisExtra.woff"),
+ 16, &ligatures);
+
+ String string = To16Bit("ffi ff", 6);
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&testFont, TextDirection::kLtr);
+
+ EXPECT_EQ(0u, result->NextSafeToBreakOffset(0)); // At start of string.
+ EXPECT_EQ(3u, result->NextSafeToBreakOffset(1)); // At end of "ffi" ligature.
+ EXPECT_EQ(3u, result->NextSafeToBreakOffset(2)); // At end of "ffi" ligature.
+ EXPECT_EQ(3u, result->NextSafeToBreakOffset(3)); // At end of "ffi" ligature.
+ EXPECT_EQ(4u, result->NextSafeToBreakOffset(4)); // After space.
+ EXPECT_EQ(6u, result->NextSafeToBreakOffset(5)); // At end of "ff" ligature.
+ EXPECT_EQ(6u, result->NextSafeToBreakOffset(6)); // At end of "ff" ligature.
+
+ // Verify safe to break information in copied results to ensure that both
+ // copying and multi-run break information works.
+ scoped_refptr<ShapeResult> copied_result =
+ ShapeResult::Create(&testFont, 0, TextDirection::kLtr);
+ result->CopyRange(0, 3, copied_result.get());
+ result->CopyRange(3, string.length(), copied_result.get());
+
+ EXPECT_EQ(0u, copied_result->NextSafeToBreakOffset(0));
+ EXPECT_EQ(3u, copied_result->NextSafeToBreakOffset(1));
+ EXPECT_EQ(3u, copied_result->NextSafeToBreakOffset(2));
+ EXPECT_EQ(3u, copied_result->NextSafeToBreakOffset(3));
+ EXPECT_EQ(4u, copied_result->NextSafeToBreakOffset(4));
+ EXPECT_EQ(6u, copied_result->NextSafeToBreakOffset(5));
+ EXPECT_EQ(6u, copied_result->NextSafeToBreakOffset(6));
+}
+
+TEST_F(HarfBuzzShaperTest, SafeToBreakPreviousLatinCommonLigatures) {
+ FontDescription::VariantLigatures ligatures;
+ ligatures.common = FontDescription::kEnabledLigaturesState;
+
+ // MEgalopolis Extra has a lot of ligatures which this test relies on.
+ Font testFont = blink::test::CreateTestFont(
+ "MEgalopolis",
+ blink::test::PlatformTestDataPath(
+ "third_party/MEgalopolis/MEgalopolisExtra.woff"),
+ 16, &ligatures);
+
+ String string = To16Bit("ffi ff", 6);
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&testFont, TextDirection::kLtr);
+
+ EXPECT_EQ(6u, result->PreviousSafeToBreakOffset(6)); // At end of "ff" liga.
+ EXPECT_EQ(4u, result->PreviousSafeToBreakOffset(5)); // At end of "ff" liga.
+ EXPECT_EQ(4u, result->PreviousSafeToBreakOffset(4)); // After space.
+ EXPECT_EQ(3u, result->PreviousSafeToBreakOffset(3)); // At end of "ffi" liga.
+ EXPECT_EQ(0u, result->PreviousSafeToBreakOffset(2)); // At start of string.
+ EXPECT_EQ(0u, result->PreviousSafeToBreakOffset(1)); // At start of string.
+ EXPECT_EQ(0u, result->PreviousSafeToBreakOffset(0)); // At start of string.
+
+ // Verify safe to break information in copied results to ensure that both
+ // copying and multi-run break information works.
+ scoped_refptr<ShapeResult> copied_result =
+ ShapeResult::Create(&testFont, 0, TextDirection::kLtr);
+ result->CopyRange(0, 3, copied_result.get());
+ result->CopyRange(3, string.length(), copied_result.get());
+
+ EXPECT_EQ(6u, copied_result->PreviousSafeToBreakOffset(6));
+ EXPECT_EQ(4u, copied_result->PreviousSafeToBreakOffset(5));
+ EXPECT_EQ(4u, copied_result->PreviousSafeToBreakOffset(4));
+ EXPECT_EQ(3u, copied_result->PreviousSafeToBreakOffset(3));
+ EXPECT_EQ(0u, copied_result->PreviousSafeToBreakOffset(2));
+ EXPECT_EQ(0u, copied_result->PreviousSafeToBreakOffset(1));
+ EXPECT_EQ(0u, copied_result->PreviousSafeToBreakOffset(0));
+}
+
+TEST_F(HarfBuzzShaperTest, SafeToBreakLatinDiscretionaryLigatures) {
+ FontDescription::VariantLigatures ligatures;
+ ligatures.common = FontDescription::kEnabledLigaturesState;
+ ligatures.discretionary = FontDescription::kEnabledLigaturesState;
+
+ // MEgalopolis Extra has a lot of ligatures which this test relies on.
+ Font testFont = blink::test::CreateTestFont(
+ "MEgalopolis",
+ blink::test::PlatformTestDataPath(
+ "third_party/MEgalopolis/MEgalopolisExtra.woff"),
+ 16, &ligatures);
+
+ // RA and CA form ligatures, most glyph pairs have kerning.
+ String string(u"ABRACADABRA");
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&testFont, TextDirection::kLtr);
+ EXPECT_EQ(6u, result->NextSafeToBreakOffset(1)); // After CA ligature.
+ EXPECT_EQ(6u, result->NextSafeToBreakOffset(6)); // After CA ligature.
+ EXPECT_EQ(11u, result->NextSafeToBreakOffset(7)); // At end of string.
+ EXPECT_EQ(11u, result->NextSafeToBreakOffset(9)); // At end of string.
+ EXPECT_EQ(11u, result->NextSafeToBreakOffset(10)); // At end of string.
+
+ // Add zero-width spaces at the safe to break offsets.
+ String refString(u"ABRACA\u200BDAB\u200BRA");
+ HarfBuzzShaper refShaper(refString.Characters16(), refString.length());
+ scoped_refptr<ShapeResult> referenceResult =
+ refShaper.Shape(&testFont, TextDirection::kLtr);
+
+ // Results should be identical if it truly is safe to break at the designated
+ // safe-to-break offsets
+ EXPECT_EQ(result->SnappedWidth(), referenceResult->SnappedWidth());
+ EXPECT_EQ(result->Bounds(), referenceResult->Bounds());
+ EXPECT_EQ(result->SnappedStartPositionForOffset(0),
+ referenceResult->SnappedStartPositionForOffset(0));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(1),
+ referenceResult->SnappedStartPositionForOffset(1));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(2),
+ referenceResult->SnappedStartPositionForOffset(2));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(3),
+ referenceResult->SnappedStartPositionForOffset(3));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(4),
+ referenceResult->SnappedStartPositionForOffset(4));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(5),
+ referenceResult->SnappedStartPositionForOffset(5));
+
+ // First zero-width space is at position 6 so the the matching character in
+ // the reference results is 7.
+ EXPECT_EQ(result->SnappedStartPositionForOffset(6),
+ referenceResult->SnappedStartPositionForOffset(7));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(7),
+ referenceResult->SnappedStartPositionForOffset(8));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(8),
+ referenceResult->SnappedStartPositionForOffset(9));
+
+ // Second zero-width space is at position 9 so the the matching character in
+ // the reference results is 11.
+ EXPECT_EQ(result->SnappedStartPositionForOffset(9),
+ referenceResult->SnappedStartPositionForOffset(11));
+ EXPECT_EQ(result->SnappedStartPositionForOffset(10),
+ referenceResult->SnappedStartPositionForOffset(12));
+}
+
+// TODO(layout-dev): This test fails on Mac due to AAT shaping.
+TEST_F(HarfBuzzShaperTest, DISABLED_SafeToBreakArabicCommonLigatures) {
+ FontDescription::VariantLigatures ligatures;
+ ligatures.common = FontDescription::kEnabledLigaturesState;
+
+ // كسر الاختبار
+ String string(
+ u"\u0643\u0633\u0631\u0020\u0627\u0644\u0627\u062E\u062A\u0628\u0627"
+ u"\u0631");
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kRtl);
+
+ // Safe to break at 0, 3, 4, 5, 7, and 11.
+ EXPECT_EQ(0u, result->NextSafeToBreakOffset(0));
+ EXPECT_EQ(3u, result->NextSafeToBreakOffset(1));
+ EXPECT_EQ(3u, result->NextSafeToBreakOffset(2));
+ EXPECT_EQ(3u, result->NextSafeToBreakOffset(3));
+ EXPECT_EQ(4u, result->NextSafeToBreakOffset(4));
+ EXPECT_EQ(5u, result->NextSafeToBreakOffset(5));
+ EXPECT_EQ(7u, result->NextSafeToBreakOffset(6));
+ EXPECT_EQ(7u, result->NextSafeToBreakOffset(7));
+ EXPECT_EQ(11u, result->NextSafeToBreakOffset(8));
+ EXPECT_EQ(11u, result->NextSafeToBreakOffset(9));
+ EXPECT_EQ(11u, result->NextSafeToBreakOffset(10));
+ EXPECT_EQ(11u, result->NextSafeToBreakOffset(11));
+ EXPECT_EQ(12u, result->NextSafeToBreakOffset(12));
+}
+
+// TODO(layout-dev): Expand RTL test coverage and add tests for mixed
+// directionality strings.
+
+// Test when some characters are missing in |runs_|.
+// RTL on Mac may not have runs for all characters. crbug.com/774034
+TEST_P(ShapeParameterTest, SafeToBreakMissingRun) {
+ TextDirection direction = GetParam();
+ scoped_refptr<ShapeResult> result = ShapeResult::Create(&font, 7, direction);
+ result->InsertRunForTesting(2, 1, direction, {0});
+ result->InsertRunForTesting(3, 3, direction, {0, 1});
+ // The character index 7 is missing.
+ result->InsertRunForTesting(8, 2, direction, {0});
+
+ EXPECT_EQ(2u, result->NextSafeToBreakOffset(2));
+ EXPECT_EQ(3u, result->NextSafeToBreakOffset(3));
+ EXPECT_EQ(4u, result->NextSafeToBreakOffset(4));
+ EXPECT_EQ(6u, result->NextSafeToBreakOffset(5));
+ EXPECT_EQ(6u, result->NextSafeToBreakOffset(6));
+ EXPECT_EQ(8u, result->NextSafeToBreakOffset(7));
+ EXPECT_EQ(8u, result->NextSafeToBreakOffset(8));
+ EXPECT_EQ(10u, result->NextSafeToBreakOffset(9));
+
+ EXPECT_EQ(2u, result->PreviousSafeToBreakOffset(2));
+ EXPECT_EQ(3u, result->PreviousSafeToBreakOffset(3));
+ EXPECT_EQ(4u, result->PreviousSafeToBreakOffset(4));
+ EXPECT_EQ(4u, result->PreviousSafeToBreakOffset(5));
+ EXPECT_EQ(6u, result->PreviousSafeToBreakOffset(6));
+ EXPECT_EQ(6u, result->PreviousSafeToBreakOffset(7));
+ EXPECT_EQ(8u, result->PreviousSafeToBreakOffset(8));
+ EXPECT_EQ(8u, result->PreviousSafeToBreakOffset(9));
+}
+
+// Call this to ensure your test string has some kerning going on.
+static bool KerningIsHappening(const FontDescription& font_description,
+ TextDirection direction,
+ const String& str) {
+ FontDescription no_kern = font_description;
+ no_kern.SetKerning(FontDescription::kNoneKerning);
+
+ FontDescription kern = font_description;
+ kern.SetKerning(FontDescription::kAutoKerning);
+
+ Font font_no_kern(no_kern);
+ font_no_kern.Update(nullptr);
+
+ Font font_kern(kern);
+ font_kern.Update(nullptr);
+
+ HarfBuzzShaper shaper(str.Characters16(), str.length());
+
+ scoped_refptr<ShapeResult> result_no_kern =
+ shaper.Shape(&font_no_kern, direction);
+ scoped_refptr<ShapeResult> result_kern = shaper.Shape(&font_kern, direction);
+
+ for (unsigned i = 0; i < str.length(); i++) {
+ if (result_no_kern->PositionForOffset(i) !=
+ result_kern->PositionForOffset(i))
+ return true;
+ }
+ return false;
+}
+
+TEST_F(HarfBuzzShaperTest, KerningIsHappeningWorks) {
+ EXPECT_TRUE(
+ KerningIsHappening(font_description, TextDirection::kLtr, u"AVOID"));
+ EXPECT_FALSE(
+ KerningIsHappening(font_description, TextDirection::kLtr, u"NOID"));
+
+ // We won't kern vertically with the default font.
+ font_description.SetOrientation(FontOrientation::kVerticalUpright);
+
+ EXPECT_FALSE(
+ KerningIsHappening(font_description, TextDirection::kLtr, u"AVOID"));
+ EXPECT_FALSE(
+ KerningIsHappening(font_description, TextDirection::kLtr, u"NOID"));
+}
+
+TEST_F(HarfBuzzShaperTest,
+ ShapeHorizontalWithoutSubpixelPositionWithoutKerningIsRounded) {
+ ScopedSubpixelOverride subpixel_override(false);
+
+ String string(u"NOID");
+ TextDirection direction = TextDirection::kLtr;
+ ASSERT_FALSE(KerningIsHappening(font_description, direction, string));
+
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ for (unsigned i = 0; i < string.length(); i++) {
+ float position = result->PositionForOffset(i);
+ EXPECT_EQ(round(position), position)
+ << "Position not rounded at offset " << i;
+ }
+}
+
+TEST_F(HarfBuzzShaperTest,
+ ShapeHorizontalWithSubpixelPositionWithoutKerningIsNotRounded) {
+ ScopedSubpixelOverride subpixel_override(true);
+
+ String string(u"NOID");
+ TextDirection direction = TextDirection::kLtr;
+ ASSERT_FALSE(KerningIsHappening(font_description, direction, string));
+
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ for (unsigned i = 0; i < string.length(); i++) {
+ float position = result->PositionForOffset(i);
+ if (round(position) != position)
+ return;
+ }
+
+ EXPECT_TRUE(false) << "No unrounded positions found";
+}
+
+TEST_F(HarfBuzzShaperTest,
+ ShapeHorizontalWithoutSubpixelPositionWithKerningIsRounded) {
+ ScopedSubpixelOverride subpixel_override(false);
+
+ String string(u"AVOID");
+ TextDirection direction = TextDirection::kLtr;
+ ASSERT_TRUE(KerningIsHappening(font_description, direction, string));
+
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ for (unsigned i = 0; i < string.length(); i++) {
+ float position = result->PositionForOffset(i);
+ EXPECT_EQ(round(position), position)
+ << "Position not rounded at offset " << i;
+ }
+}
+
+TEST_F(HarfBuzzShaperTest,
+ ShapeHorizontalWithSubpixelPositionWithKerningIsNotRounded) {
+ ScopedSubpixelOverride subpixel_override(true);
+
+ String string(u"AVOID");
+ TextDirection direction = TextDirection::kLtr;
+ ASSERT_TRUE(KerningIsHappening(font_description, direction, string));
+
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ for (unsigned i = 0; i < string.length(); i++) {
+ float position = result->PositionForOffset(i);
+ if (round(position) != position)
+ return;
+ }
+
+ EXPECT_TRUE(false) << "No unrounded positions found";
+}
+
+TEST_F(HarfBuzzShaperTest, ShapeVerticalWithoutSubpixelPositionIsRounded) {
+ ScopedSubpixelOverride subpixel_override(false);
+
+ font_description.SetOrientation(FontOrientation::kVerticalUpright);
+ font = Font(font_description);
+ font.Update(nullptr);
+
+ String string(u"\u65E5\u65E5\u65E5");
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ for (unsigned i = 0; i < string.length(); i++) {
+ float position = result->PositionForOffset(i);
+ EXPECT_EQ(round(position), position)
+ << "Position not rounded at offset " << i;
+ }
+}
+
+TEST_F(HarfBuzzShaperTest, ShapeVerticalWithSubpixelPositionIsRounded) {
+ ScopedSubpixelOverride subpixel_override(true);
+
+ font_description.SetOrientation(FontOrientation::kVerticalUpright);
+ font = Font(font_description);
+ font.Update(nullptr);
+
+ String string(u"\u65E5\u65E5\u65E5");
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ // Vertical text is never subpixel positioned.
+ for (unsigned i = 0; i < string.length(); i++) {
+ float position = result->PositionForOffset(i);
+ EXPECT_EQ(round(position), position)
+ << "Position not rounded at offset " << i;
+ }
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/run_segmenter.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/run_segmenter.cc
new file mode 100644
index 00000000000..8a81e927d74
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/run_segmenter.cc
@@ -0,0 +1,108 @@
+// Copyright 2015 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/run_segmenter.h"
+
+#include <memory>
+
+#include "third_party/blink/renderer/platform/fonts/script_run_iterator.h"
+#include "third_party/blink/renderer/platform/fonts/small_caps_iterator.h"
+#include "third_party/blink/renderer/platform/fonts/symbols_iterator.h"
+#include "third_party/blink/renderer/platform/fonts/utf16_text_iterator.h"
+#include "third_party/blink/renderer/platform/text/character.h"
+#include "third_party/blink/renderer/platform/wtf/assertions.h"
+
+namespace blink {
+
+RunSegmenter::RunSegmenter(const UChar* buffer,
+ unsigned buffer_size,
+ FontOrientation run_orientation)
+ : buffer_size_(buffer_size),
+ candidate_range_(NullRange()),
+ script_run_iterator_(
+ std::make_unique<ScriptRunIterator>(buffer, buffer_size)),
+ orientation_iterator_(
+ run_orientation == FontOrientation::kVerticalMixed
+ ? std::make_unique<OrientationIterator>(buffer,
+ buffer_size,
+ run_orientation)
+ : nullptr),
+ symbols_iterator_(std::make_unique<SymbolsIterator>(buffer, buffer_size)),
+ last_split_(0),
+ script_run_iterator_position_(0),
+ orientation_iterator_position_(
+ run_orientation == FontOrientation::kVerticalMixed ? 0
+ : buffer_size_),
+ symbols_iterator_position_(0),
+ at_end_(false) {}
+
+void RunSegmenter::ConsumeScriptIteratorPastLastSplit() {
+ DCHECK(script_run_iterator_);
+ if (script_run_iterator_position_ <= last_split_ &&
+ script_run_iterator_position_ < buffer_size_) {
+ while (script_run_iterator_->Consume(script_run_iterator_position_,
+ candidate_range_.script)) {
+ if (script_run_iterator_position_ > last_split_)
+ return;
+ }
+ }
+}
+
+void RunSegmenter::ConsumeOrientationIteratorPastLastSplit() {
+ if (orientation_iterator_ && orientation_iterator_position_ <= last_split_ &&
+ orientation_iterator_position_ < buffer_size_) {
+ while (
+ orientation_iterator_->Consume(&orientation_iterator_position_,
+ &candidate_range_.render_orientation)) {
+ if (orientation_iterator_position_ > last_split_)
+ return;
+ }
+ }
+}
+
+void RunSegmenter::ConsumeSymbolsIteratorPastLastSplit() {
+ DCHECK(symbols_iterator_);
+ if (symbols_iterator_position_ <= last_split_ &&
+ symbols_iterator_position_ < buffer_size_) {
+ while (
+ symbols_iterator_->Consume(&symbols_iterator_position_,
+ &candidate_range_.font_fallback_priority)) {
+ if (symbols_iterator_position_ > last_split_)
+ return;
+ }
+ }
+}
+
+bool RunSegmenter::Consume(RunSegmenterRange* next_range) {
+ if (at_end_ || !buffer_size_)
+ return false;
+
+ ConsumeScriptIteratorPastLastSplit();
+ ConsumeOrientationIteratorPastLastSplit();
+ ConsumeSymbolsIteratorPastLastSplit();
+
+ if (script_run_iterator_position_ <= orientation_iterator_position_ &&
+ script_run_iterator_position_ <= symbols_iterator_position_) {
+ last_split_ = script_run_iterator_position_;
+ }
+
+ if (orientation_iterator_position_ <= script_run_iterator_position_ &&
+ orientation_iterator_position_ <= symbols_iterator_position_) {
+ last_split_ = orientation_iterator_position_;
+ }
+
+ if (symbols_iterator_position_ <= script_run_iterator_position_ &&
+ symbols_iterator_position_ <= orientation_iterator_position_) {
+ last_split_ = symbols_iterator_position_;
+ }
+
+ candidate_range_.start = candidate_range_.end;
+ candidate_range_.end = last_split_;
+ *next_range = candidate_range_;
+
+ at_end_ = last_split_ == buffer_size_;
+ return true;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/run_segmenter.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/run_segmenter.h
new file mode 100644
index 00000000000..e5d8c6059df
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/run_segmenter.h
@@ -0,0 +1,67 @@
+// Copyright 2015 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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_RUN_SEGMENTER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_RUN_SEGMENTER_H_
+
+#include <unicode/uscript.h>
+#include <memory>
+#include "third_party/blink/renderer/platform/fonts/font_orientation.h"
+#include "third_party/blink/renderer/platform/fonts/orientation_iterator.h"
+#include "third_party/blink/renderer/platform/fonts/script_run_iterator.h"
+#include "third_party/blink/renderer/platform/fonts/small_caps_iterator.h"
+#include "third_party/blink/renderer/platform/fonts/symbols_iterator.h"
+#include "third_party/blink/renderer/platform/fonts/utf16_text_iterator.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/noncopyable.h"
+
+namespace blink {
+
+// A tool for segmenting runs prior to shaping, combining ScriptIterator,
+// OrientationIterator and SmallCapsIterator, depending on orientaton and
+// font-variant of the text run.
+class PLATFORM_EXPORT RunSegmenter {
+ STACK_ALLOCATED();
+ WTF_MAKE_NONCOPYABLE(RunSegmenter);
+
+ public:
+ // Indices into the UTF-16 buffer that is passed in
+ struct RunSegmenterRange {
+ DISALLOW_NEW();
+ unsigned start;
+ unsigned end;
+ UScriptCode script;
+ OrientationIterator::RenderOrientation render_orientation;
+ FontFallbackPriority font_fallback_priority;
+ };
+
+ RunSegmenter(const UChar* buffer, unsigned buffer_size, FontOrientation);
+
+ bool Consume(RunSegmenterRange*);
+
+ static RunSegmenterRange NullRange() {
+ return {0, 0, USCRIPT_INVALID_CODE, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText};
+ }
+
+ private:
+ void ConsumeOrientationIteratorPastLastSplit();
+ void ConsumeScriptIteratorPastLastSplit();
+ void ConsumeSymbolsIteratorPastLastSplit();
+
+ unsigned buffer_size_;
+ RunSegmenterRange candidate_range_;
+ std::unique_ptr<ScriptRunIterator> script_run_iterator_;
+ std::unique_ptr<OrientationIterator> orientation_iterator_;
+ std::unique_ptr<SymbolsIterator> symbols_iterator_;
+ unsigned last_split_;
+ unsigned script_run_iterator_position_;
+ unsigned orientation_iterator_position_;
+ unsigned symbols_iterator_position_;
+ bool at_end_;
+};
+
+} // namespace blink
+
+#endif
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/run_segmenter_test.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/run_segmenter_test.cc
new file mode 100644
index 00000000000..231a4e9f895
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/run_segmenter_test.cc
@@ -0,0 +1,251 @@
+// Copyright 2015 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/run_segmenter.h"
+
+#include <string>
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/platform/fonts/orientation_iterator.h"
+#include "third_party/blink/renderer/platform/wtf/assertions.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+struct SegmenterTestRun {
+ std::string text;
+ UScriptCode script;
+ OrientationIterator::RenderOrientation render_orientation;
+ FontFallbackPriority font_fallback_priority;
+};
+
+struct SegmenterExpectedRun {
+ unsigned start;
+ unsigned limit;
+ UScriptCode script;
+ OrientationIterator::RenderOrientation render_orientation;
+ FontFallbackPriority font_fallback_priority;
+
+ SegmenterExpectedRun(
+ unsigned the_start,
+ unsigned the_limit,
+ UScriptCode the_script,
+ OrientationIterator::RenderOrientation the_render_orientation,
+ FontFallbackPriority the_font_fallback_priority)
+ : start(the_start),
+ limit(the_limit),
+ script(the_script),
+ render_orientation(the_render_orientation),
+ font_fallback_priority(the_font_fallback_priority) {}
+};
+
+class RunSegmenterTest : public testing::Test {
+ protected:
+ void CheckRuns(const Vector<SegmenterTestRun>& runs,
+ FontOrientation orientation) {
+ String text(g_empty_string16_bit);
+ Vector<SegmenterExpectedRun> expect;
+ for (auto& run : runs) {
+ unsigned length_before = text.length();
+ text.append(String::FromUTF8(run.text.c_str()));
+ expect.push_back(SegmenterExpectedRun(length_before, text.length(),
+ run.script, run.render_orientation,
+ run.font_fallback_priority));
+ }
+ RunSegmenter run_segmenter(text.Characters16(), text.length(), orientation);
+ VerifyRuns(&run_segmenter, expect);
+ }
+
+ void VerifyRuns(RunSegmenter* run_segmenter,
+ const Vector<SegmenterExpectedRun>& expect) {
+ RunSegmenter::RunSegmenterRange segmenter_range;
+ unsigned long run_count = 0;
+ while (run_segmenter->Consume(&segmenter_range)) {
+ ASSERT_LT(run_count, expect.size());
+ ASSERT_EQ(expect[run_count].start, segmenter_range.start);
+ ASSERT_EQ(expect[run_count].limit, segmenter_range.end);
+ ASSERT_EQ(expect[run_count].script, segmenter_range.script);
+ ASSERT_EQ(expect[run_count].render_orientation,
+ segmenter_range.render_orientation);
+ ASSERT_EQ(expect[run_count].font_fallback_priority,
+ segmenter_range.font_fallback_priority);
+ ++run_count;
+ }
+ ASSERT_EQ(expect.size(), run_count);
+ }
+};
+
+// Some of our compilers cannot initialize a vector from an array yet.
+#define DECLARE_SEGMENTER_RUNSVECTOR(...) \
+ static const SegmenterTestRun kRunsArray[] = __VA_ARGS__; \
+ Vector<SegmenterTestRun> runs; \
+ runs.Append(kRunsArray, sizeof(kRunsArray) / sizeof(*kRunsArray));
+
+#define CHECK_RUNS_MIXED(...) \
+ DECLARE_SEGMENTER_RUNSVECTOR(__VA_ARGS__); \
+ CheckRuns(runs, FontOrientation::kVerticalMixed);
+
+#define CHECK_RUNS_HORIZONTAL(...) \
+ DECLARE_SEGMENTER_RUNSVECTOR(__VA_ARGS__); \
+ CheckRuns(runs, FontOrientation::kHorizontal);
+
+TEST_F(RunSegmenterTest, Empty) {
+ String empty(g_empty_string16_bit);
+ RunSegmenter::RunSegmenterRange segmenter_range = {
+ 0, 0, USCRIPT_INVALID_CODE, OrientationIterator::kOrientationKeep};
+ RunSegmenter run_segmenter(empty.Characters16(), empty.length(),
+ FontOrientation::kVerticalMixed);
+ DCHECK(!run_segmenter.Consume(&segmenter_range));
+ ASSERT_EQ(segmenter_range.start, 0u);
+ ASSERT_EQ(segmenter_range.end, 0u);
+ ASSERT_EQ(segmenter_range.script, USCRIPT_INVALID_CODE);
+ ASSERT_EQ(segmenter_range.render_orientation,
+ OrientationIterator::kOrientationKeep);
+ ASSERT_EQ(segmenter_range.font_fallback_priority,
+ FontFallbackPriority::kText);
+}
+
+TEST_F(RunSegmenterTest, LatinPunctuationSideways) {
+ CHECK_RUNS_MIXED({{"Abc.;?Xyz", USCRIPT_LATIN,
+ OrientationIterator::kOrientationRotateSideways,
+ FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, OneSpace) {
+ CHECK_RUNS_MIXED(
+ {{" ", USCRIPT_COMMON, OrientationIterator::kOrientationRotateSideways,
+ FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, ArabicHangul) {
+ CHECK_RUNS_MIXED(
+ {{"نص", USCRIPT_ARABIC, OrientationIterator::kOrientationRotateSideways,
+ FontFallbackPriority::kText},
+ {"키스의", USCRIPT_HANGUL, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, JapaneseHindiEmojiMix) {
+ CHECK_RUNS_MIXED(
+ {{"百家姓", USCRIPT_HAN, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText},
+ {"ऋषियों", USCRIPT_DEVANAGARI,
+ OrientationIterator::kOrientationRotateSideways,
+ FontFallbackPriority::kText},
+ {"🌱🌲🌳🌴", USCRIPT_DEVANAGARI, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kEmojiEmoji},
+ {"百家姓", USCRIPT_HAN, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText},
+ {"🌱🌲", USCRIPT_HAN, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kEmojiEmoji}});
+}
+
+TEST_F(RunSegmenterTest, CombiningCirlce) {
+ CHECK_RUNS_HORIZONTAL(
+ {{"◌́◌̀◌̈◌̂◌̄◌̊", USCRIPT_COMMON, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, HangulSpace) {
+ CHECK_RUNS_MIXED(
+ {{"키스의", USCRIPT_HANGUL, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText},
+ {" ", USCRIPT_HANGUL, OrientationIterator::kOrientationRotateSideways,
+ FontFallbackPriority::kText},
+ {"고유조건은", USCRIPT_HANGUL, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, TechnicalCommonUpright) {
+ CHECK_RUNS_MIXED(
+ {{"⌀⌁⌂", USCRIPT_COMMON, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, PunctuationCommonSideways) {
+ CHECK_RUNS_MIXED(
+ {{".…¡", USCRIPT_COMMON, OrientationIterator::kOrientationRotateSideways,
+ FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, JapanesePunctuationMixedInside) {
+ CHECK_RUNS_MIXED(
+ {{"いろはに", USCRIPT_HIRAGANA, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText},
+ {".…¡", USCRIPT_HIRAGANA,
+ OrientationIterator::kOrientationRotateSideways,
+ FontFallbackPriority::kText},
+ {"ほへと", USCRIPT_HIRAGANA, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, JapanesePunctuationMixedInsideHorizontal) {
+ CHECK_RUNS_HORIZONTAL(
+ {{"いろはに.…¡ほへと", USCRIPT_HIRAGANA,
+ OrientationIterator::kOrientationKeep, FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, PunctuationDevanagariCombining) {
+ CHECK_RUNS_HORIZONTAL(
+ {{"क+े", USCRIPT_DEVANAGARI, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, EmojiZWJSequences) {
+ CHECK_RUNS_HORIZONTAL(
+ {{"👩‍👩‍👧‍👦👩‍❤️‍💋‍👨", USCRIPT_LATIN,
+ OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kEmojiEmoji},
+ {"abcd", USCRIPT_LATIN, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText},
+ {"👩‍👩‍", USCRIPT_LATIN, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kEmojiEmoji},
+ {"efg", USCRIPT_LATIN, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, JapaneseLetterlikeEnd) {
+ CHECK_RUNS_MIXED(
+ {{"いろは", USCRIPT_HIRAGANA, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText},
+ {"ℐℒℐℒℐℒℐℒℐℒℐℒℐℒ", USCRIPT_HIRAGANA,
+ OrientationIterator::kOrientationRotateSideways,
+ FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, JapaneseCase) {
+ CHECK_RUNS_MIXED(
+ {{"いろは", USCRIPT_HIRAGANA, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText},
+ {"aaAA", USCRIPT_LATIN, OrientationIterator::kOrientationRotateSideways,
+ FontFallbackPriority::kText},
+ {"いろは", USCRIPT_HIRAGANA, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, DingbatsMiscSymbolsModifier) {
+ CHECK_RUNS_HORIZONTAL({{"⛹🏻✍🏻✊🏼", USCRIPT_COMMON,
+ OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kEmojiEmoji}});
+}
+
+TEST_F(RunSegmenterTest, ArmenianCyrillicCase) {
+ CHECK_RUNS_HORIZONTAL(
+ {{"աբգ", USCRIPT_ARMENIAN, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText},
+ {"αβγ", USCRIPT_GREEK, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText},
+ {"ԱԲԳ", USCRIPT_ARMENIAN, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kText}});
+}
+
+TEST_F(RunSegmenterTest, EmojiSubdivisionFlags) {
+ CHECK_RUNS_HORIZONTAL(
+ {{"🏴󠁧󠁢󠁷󠁬󠁳󠁿🏴󠁧󠁢󠁳󠁣󠁴󠁿🏴󠁧󠁢"
+ "󠁥󠁮󠁧󠁿",
+ USCRIPT_COMMON, OrientationIterator::kOrientationKeep,
+ FontFallbackPriority::kEmojiEmoji}});
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_cache.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_cache.cc
new file mode 100644
index 00000000000..3fbc1ef3978
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_cache.cc
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2012, 2017 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_cache.h"
+#include "third_party/blink/renderer/platform/wtf/string_hasher.h"
+
+namespace blink {
+
+void ShapeCache::SmallStringKey::HashString() {
+ // TODO(cavalcantii): replace this for a better hash function,
+ // see crbug.com/735674.
+ hash_ = StringHasher::ComputeHash(characters_, length_);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_cache.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_cache.h
new file mode 100644
index 00000000000..0153c4c89a2
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_cache.h
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2012 Apple Inc. All rights reserved.
+ * Copyright (C) 2015 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_CACHE_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_CACHE_H_
+
+#include "base/memory/weak_ptr.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h"
+#include "third_party/blink/renderer/platform/text/text_run.h"
+#include "third_party/blink/renderer/platform/wtf/forward.h"
+#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
+#include "third_party/blink/renderer/platform/wtf/hash_set.h"
+#include "third_party/blink/renderer/platform/wtf/hash_table_deleted_value_type.h"
+
+namespace blink {
+
+struct ShapeCacheEntry {
+ DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
+ ShapeCacheEntry() { shape_result_ = nullptr; }
+ scoped_refptr<const ShapeResult> shape_result_;
+};
+
+class ShapeCache {
+ USING_FAST_MALLOC(ShapeCache);
+ WTF_MAKE_NONCOPYABLE(ShapeCache);
+ // Used to optimize small strings as hash table keys. Avoids malloc'ing an
+ // out-of-line StringImpl.
+ class SmallStringKey {
+ DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
+
+ void HashString();
+
+ public:
+ static unsigned Capacity() { return kCapacity; }
+
+ SmallStringKey()
+ : length_(kEmptyValueLength),
+ direction_(static_cast<unsigned>(TextDirection::kLtr)) {}
+
+ SmallStringKey(WTF::HashTableDeletedValueType)
+ : length_(kDeletedValueLength),
+ direction_(static_cast<unsigned>(TextDirection::kLtr)) {}
+
+ SmallStringKey(const LChar* characters,
+ unsigned short length,
+ TextDirection direction)
+ : length_(length), direction_(static_cast<unsigned>(direction)) {
+ DCHECK(length <= kCapacity);
+ // Up-convert from LChar to UChar.
+ for (unsigned short i = 0; i < length; ++i) {
+ characters_[i] = characters[i];
+ }
+
+ HashString();
+ }
+
+ SmallStringKey(const UChar* characters,
+ unsigned short length,
+ TextDirection direction)
+ : length_(length), direction_(static_cast<unsigned>(direction)) {
+ DCHECK(length <= kCapacity);
+ memcpy(characters_, characters, length * sizeof(UChar));
+ HashString();
+ }
+
+ const UChar* Characters() const { return characters_; }
+ unsigned short length() const { return length_; }
+ TextDirection Direction() const {
+ return static_cast<TextDirection>(direction_);
+ }
+ unsigned GetHash() const { return hash_; }
+
+ bool IsHashTableDeletedValue() const {
+ return length_ == kDeletedValueLength;
+ }
+ bool IsHashTableEmptyValue() const { return length_ == kEmptyValueLength; }
+
+ private:
+ static const unsigned kCapacity = 15;
+ static const unsigned kEmptyValueLength = kCapacity + 1;
+ static const unsigned kDeletedValueLength = kCapacity + 2;
+
+ unsigned hash_;
+ unsigned length_ : 15;
+ unsigned direction_ : 1;
+ UChar characters_[kCapacity];
+ };
+
+ public:
+ ShapeCache() : weak_factory_(this), version_(0) {
+ // TODO(cavalcantii): Investigate tradeoffs of reserving space
+ // in short_string_map.
+ }
+
+ ShapeCacheEntry* Add(const TextRun& run, ShapeCacheEntry entry) {
+ if (run.length() > SmallStringKey::Capacity())
+ return nullptr;
+
+ return AddSlowCase(run, entry);
+ }
+
+ void ClearIfVersionChanged(unsigned version) {
+ if (version != version_) {
+ Clear();
+ version_ = version;
+ }
+ }
+
+ void Clear() {
+ single_char_map_.clear();
+ short_string_map_.clear();
+ }
+
+ unsigned size() const {
+ return single_char_map_.size() + short_string_map_.size();
+ }
+
+ size_t ByteSize() const {
+ size_t self_byte_size = 0;
+ for (auto cache_entry : single_char_map_) {
+ self_byte_size += cache_entry.value.shape_result_->ByteSize();
+ }
+ for (auto cache_entry : short_string_map_) {
+ self_byte_size += cache_entry.value.shape_result_->ByteSize();
+ }
+ return self_byte_size;
+ }
+
+ base::WeakPtr<ShapeCache> GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
+
+ private:
+ ShapeCacheEntry* AddSlowCase(const TextRun& run, ShapeCacheEntry entry) {
+ unsigned length = run.length();
+ bool is_new_entry;
+ ShapeCacheEntry* value;
+ if (length == 1) {
+ uint32_t key = run[0];
+ // All current codepoints in UTF-32 are bewteen 0x0 and 0x10FFFF,
+ // as such use bit 31 (zero-based) to indicate direction.
+ if (run.Direction() == TextDirection::kRtl)
+ key |= (1u << 31);
+ SingleCharMap::AddResult add_result = single_char_map_.insert(key, entry);
+ is_new_entry = add_result.is_new_entry;
+ value = &add_result.stored_value->value;
+ } else {
+ SmallStringKey small_string_key;
+ if (run.Is8Bit()) {
+ small_string_key =
+ SmallStringKey(run.Characters8(), length, run.Direction());
+ } else {
+ small_string_key =
+ SmallStringKey(run.Characters16(), length, run.Direction());
+ }
+
+ SmallStringMap::AddResult add_result =
+ short_string_map_.insert(small_string_key, entry);
+ is_new_entry = add_result.is_new_entry;
+ value = &add_result.stored_value->value;
+ }
+
+ if ((!is_new_entry) || (size() < kMaxSize)) {
+ return value;
+ }
+
+ // No need to be fancy: we're just trying to avoid pathological growth.
+ single_char_map_.clear();
+ short_string_map_.clear();
+
+ return nullptr;
+ }
+
+ struct SmallStringKeyHash {
+ STATIC_ONLY(SmallStringKeyHash);
+ static unsigned GetHash(const SmallStringKey& key) { return key.GetHash(); }
+ static bool Equal(const SmallStringKey& a, const SmallStringKey& b) {
+ return a == b;
+ }
+ // Empty and deleted values have lengths that are not equal to any valid
+ // length.
+ static const bool safe_to_compare_to_empty_or_deleted = true;
+ };
+
+ struct SmallStringKeyHashTraits : WTF::SimpleClassHashTraits<SmallStringKey> {
+ STATIC_ONLY(SmallStringKeyHashTraits);
+ static const bool kHasIsEmptyValueFunction = true;
+ static bool IsEmptyValue(const SmallStringKey& key) {
+ return key.IsHashTableEmptyValue();
+ }
+ static const unsigned kMinimumTableSize = 16;
+ };
+
+ friend bool operator==(const SmallStringKey&, const SmallStringKey&);
+
+ typedef HashMap<SmallStringKey,
+ ShapeCacheEntry,
+ SmallStringKeyHash,
+ SmallStringKeyHashTraits>
+ SmallStringMap;
+ typedef HashMap<uint32_t,
+ ShapeCacheEntry,
+ DefaultHash<uint32_t>::Hash,
+ WTF::UnsignedWithZeroKeyHashTraits<uint32_t>>
+ SingleCharMap;
+
+ // Hard limit to guard against pathological growth. The expected number of
+ // cache entries is a lot lower given the average word count for a web page
+ // is well below 1,000 and even full length books rarely have over 10,000
+ // unique words [1]. 1: http://www.mine-control.com/zack/guttenberg/
+ // Our definition of a word is somewhat different from the norm in that we
+ // only segment on space. Thus "foo", "foo-", and "foo)" would count as
+ // three separate words. Given that 10,000 seems like a reasonable maximum.
+ static const unsigned kMaxSize = 10000;
+
+ SingleCharMap single_char_map_;
+ SmallStringMap short_string_map_;
+ base::WeakPtrFactory<ShapeCache> weak_factory_;
+ unsigned version_;
+};
+
+inline bool operator==(const ShapeCache::SmallStringKey& a,
+ const ShapeCache::SmallStringKey& b) {
+ if (a.length() != b.length() || a.Direction() != b.Direction())
+ return false;
+ return WTF::Equal(a.Characters(), b.Characters(), a.length());
+}
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_CACHE_H_
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc
new file mode 100644
index 00000000000..f872c8c8451
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc
@@ -0,0 +1,953 @@
+/*
+ * Copyright (c) 2012 Google Inc. All rights reserved.
+ * Copyright (C) 2013 BlackBerry Limited. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h"
+
+#include <hb.h>
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "third_party/blink/renderer/platform/fonts/character_range.h"
+#include "third_party/blink/renderer/platform/fonts/font.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+
+namespace blink {
+
+unsigned ShapeResult::RunInfo::NextSafeToBreakOffset(unsigned offset) const {
+ DCHECK_LE(offset, num_characters_);
+ for (unsigned i = 0; i < safe_break_offsets_.size(); i++) {
+ if (safe_break_offsets_[i] >= offset)
+ return safe_break_offsets_[i];
+ }
+
+ // Next safe break is at the end of the run.
+ return num_characters_;
+}
+
+unsigned ShapeResult::RunInfo::PreviousSafeToBreakOffset(
+ unsigned offset) const {
+ if (offset >= num_characters_)
+ return num_characters_;
+
+ for (unsigned i = safe_break_offsets_.size(); i > 0; i--) {
+ if (safe_break_offsets_[i - 1] <= offset)
+ return safe_break_offsets_[i - 1];
+ }
+
+ // Next safe break is at the start of the run.
+ return 0;
+}
+
+float ShapeResult::RunInfo::XPositionForVisualOffset(
+ unsigned offset,
+ AdjustMidCluster adjust_mid_cluster) const {
+ DCHECK_LT(offset, num_characters_);
+ if (Rtl())
+ offset = num_characters_ - offset - 1;
+ return XPositionForOffset(offset, adjust_mid_cluster);
+}
+
+float ShapeResult::RunInfo::XPositionForOffset(
+ unsigned offset,
+ AdjustMidCluster adjust_mid_cluster) const {
+ DCHECK_LE(offset, num_characters_);
+ const unsigned num_glyphs = glyph_data_.size();
+ unsigned glyph_index = 0;
+ float position = 0;
+ if (Rtl()) {
+ while (glyph_index < num_glyphs &&
+ glyph_data_[glyph_index].character_index > offset) {
+ position += glyph_data_[glyph_index].advance;
+ ++glyph_index;
+ }
+ // If |glyph_index| is at the end, the glyph for |offset| is missing, along
+ // with all glyphs before it. We can't adjust position to the start
+ // direction.
+ if (glyph_index == num_glyphs)
+ return position;
+ // Adjust offset if it's not on the cluster boundary. In RTL, this means
+ // that the adjusted position is the left side of the character.
+ if (adjust_mid_cluster == AdjustMidCluster::kToEnd &&
+ glyph_data_[glyph_index].character_index < offset) {
+ return position;
+ }
+ // For RTL, we need to return the right side boundary of the character.
+ // Add advance of glyphs which are part of the character.
+ while (glyph_index < num_glyphs - 1 &&
+ glyph_data_[glyph_index].character_index ==
+ glyph_data_[glyph_index + 1].character_index) {
+ position += glyph_data_[glyph_index].advance;
+ ++glyph_index;
+ }
+ position += glyph_data_[glyph_index].advance;
+ } else {
+ while (glyph_index < num_glyphs &&
+ glyph_data_[glyph_index].character_index < offset) {
+ position += glyph_data_[glyph_index].advance;
+ ++glyph_index;
+ }
+ // Adjust offset if it's not on the cluster boundary.
+ if (adjust_mid_cluster == AdjustMidCluster::kToStart && glyph_index &&
+ (glyph_index < num_glyphs ? glyph_data_[glyph_index].character_index
+ : num_characters_) > offset) {
+ offset = glyph_data_[--glyph_index].character_index;
+ for (; glyph_data_[glyph_index].character_index == offset;
+ --glyph_index) {
+ position -= glyph_data_[glyph_index].advance;
+ if (!glyph_index)
+ break;
+ }
+ }
+ }
+ return position;
+}
+
+static bool TargetPastEdge(bool rtl, float target_x, float next_x) {
+ // In LTR, the edge belongs to the character on right.
+ if (!rtl)
+ return target_x < next_x;
+
+ // In RTL, the edge belongs to the character on left.
+ return target_x <= next_x;
+}
+
+int ShapeResult::RunInfo::CharacterIndexForXPosition(
+ float target_x,
+ bool include_partial_glyphs) const {
+ DCHECK(target_x >= 0 && target_x <= width_);
+ if (target_x <= 0)
+ return !Rtl() ? 0 : num_characters_;
+ const unsigned num_glyphs = glyph_data_.size();
+ float current_x = 0;
+ float current_advance = 0;
+ unsigned glyph_index = 0;
+ unsigned prev_character_index = num_characters_; // used only when rtl()
+
+ while (glyph_index < num_glyphs) {
+ float prev_advance = current_advance;
+ unsigned current_character_index = glyph_data_[glyph_index].character_index;
+ current_advance = glyph_data_[glyph_index].advance;
+ while (glyph_index < num_glyphs - 1 &&
+ current_character_index ==
+ glyph_data_[glyph_index + 1].character_index)
+ current_advance += glyph_data_[++glyph_index].advance;
+ float next_x;
+ if (include_partial_glyphs) {
+ // For hit testing, find the closest caret point by incuding
+ // end-half of the previous character and start-half of the current
+ // character.
+ current_advance = current_advance / 2.0;
+ next_x = current_x + prev_advance + current_advance;
+ // When include_partial_glyphs, "<=" or "<" is not a big deal because
+ // |next_x| is not at the character boundary.
+ if (target_x <= next_x)
+ return Rtl() ? prev_character_index : current_character_index;
+ } else {
+ next_x = current_x + current_advance;
+ if (TargetPastEdge(Rtl(), target_x, next_x))
+ return current_character_index;
+ }
+ current_x = next_x;
+ prev_character_index = current_character_index;
+ ++glyph_index;
+ }
+
+ return Rtl() ? 0 : num_characters_;
+}
+
+void ShapeResult::RunInfo::SetGlyphAndPositions(unsigned index,
+ uint16_t glyph_id,
+ float advance,
+ float offset_x,
+ float offset_y) {
+ HarfBuzzRunGlyphData& data = glyph_data_[index];
+ data.glyph = glyph_id;
+ data.advance = advance;
+ data.offset = FloatSize(offset_x, offset_y);
+}
+
+ShapeResult::ShapeResult(const SimpleFontData* font_data,
+ unsigned num_characters,
+ TextDirection direction)
+ : width_(0),
+ primary_font_(font_data),
+ num_characters_(num_characters),
+ num_glyphs_(0),
+ direction_(static_cast<unsigned>(direction)),
+ has_vertical_offsets_(0) {}
+
+ShapeResult::ShapeResult(const Font* font,
+ unsigned num_characters,
+ TextDirection direction)
+ : ShapeResult(font->PrimaryFont(), num_characters, direction) {}
+
+ShapeResult::ShapeResult(const ShapeResult& other)
+ : width_(other.width_),
+ glyph_bounding_box_(other.glyph_bounding_box_),
+ primary_font_(other.primary_font_),
+ num_characters_(other.num_characters_),
+ num_glyphs_(other.num_glyphs_),
+ direction_(other.direction_),
+ has_vertical_offsets_(other.has_vertical_offsets_) {
+ runs_.ReserveCapacity(other.runs_.size());
+ for (const auto& run : other.runs_)
+ runs_.push_back(std::make_unique<RunInfo>(*run));
+}
+
+ShapeResult::~ShapeResult() = default;
+
+size_t ShapeResult::ByteSize() const {
+ size_t self_byte_size = sizeof(this);
+ for (unsigned i = 0; i < runs_.size(); ++i) {
+ self_byte_size += runs_[i]->ByteSize();
+ }
+ return self_byte_size;
+}
+
+CharacterRange ShapeResult::GetCharacterRange(unsigned from,
+ unsigned to) const {
+ return ShapeResultBuffer::GetCharacterRange(this, Direction(), Width(), from,
+ to);
+}
+
+unsigned ShapeResult::StartIndexForResult() const {
+ if (UNLIKELY(runs_.IsEmpty()))
+ return 0;
+ const RunInfo& first_run = *runs_.front();
+ if (!Rtl())
+ return first_run.start_index_;
+ unsigned end = first_run.start_index_ + first_run.num_characters_;
+ DCHECK_GE(end, NumCharacters());
+ return end - NumCharacters();
+}
+
+unsigned ShapeResult::EndIndexForResult() const {
+ if (UNLIKELY(runs_.IsEmpty()))
+ return NumCharacters();
+ const RunInfo& first_run = *runs_.front();
+ if (!Rtl())
+ return first_run.start_index_ + NumCharacters();
+ return first_run.start_index_ + first_run.num_characters_;
+}
+
+scoped_refptr<ShapeResult> ShapeResult::MutableUnique() const {
+ if (HasOneRef())
+ return const_cast<ShapeResult*>(this);
+ return ShapeResult::Create(*this);
+}
+
+unsigned ShapeResult::NextSafeToBreakOffset(unsigned index) const {
+ for (auto it = runs_.begin(); it != runs_.end(); ++it) {
+ const auto& run = *it;
+ if (!run)
+ continue;
+
+ unsigned run_start = run->start_index_;
+ if (index >= run_start) {
+ unsigned offset = index - run_start;
+ if (offset <= run->num_characters_) {
+ return run->NextSafeToBreakOffset(offset) + run_start;
+ }
+ if (Rtl()) {
+ if (it == runs_.begin())
+ return run_start + run->num_characters_;
+ const auto& previous_run = *--it;
+ return previous_run->start_index_;
+ }
+ } else if (!Rtl()) {
+ return run_start;
+ }
+ }
+
+ return EndIndexForResult();
+}
+
+unsigned ShapeResult::PreviousSafeToBreakOffset(unsigned index) const {
+ for (auto it = runs_.rbegin(); it != runs_.rend(); ++it) {
+ const auto& run = *it;
+ if (!run)
+ continue;
+
+ unsigned run_start = run->start_index_;
+ if (index >= run_start) {
+ unsigned offset = index - run_start;
+ if (offset <= run->num_characters_) {
+ return run->PreviousSafeToBreakOffset(offset) + run_start;
+ }
+ if (!Rtl()) {
+ return run_start + run->num_characters_;
+ }
+ } else if (Rtl()) {
+ if (it == runs_.rbegin())
+ return run->start_index_;
+ const auto& previous_run = *--it;
+ return previous_run->start_index_ + previous_run->num_characters_;
+ }
+ }
+
+ return StartIndexForResult();
+}
+
+// If the position is outside of the result, returns the start or the end offset
+// depends on the position.
+unsigned ShapeResult::OffsetForPosition(float target_x,
+ bool include_partial_glyphs) const {
+ unsigned characters_so_far = 0;
+ float current_x = 0;
+
+ if (Rtl()) {
+ if (target_x <= 0)
+ return num_characters_;
+ characters_so_far = num_characters_;
+ for (unsigned i = 0; i < runs_.size(); ++i) {
+ if (!runs_[i])
+ continue;
+ characters_so_far -= runs_[i]->num_characters_;
+ float next_x = current_x + runs_[i]->width_;
+ float offset_for_run = target_x - current_x;
+ if (offset_for_run >= 0 && offset_for_run <= runs_[i]->width_) {
+ // The x value in question is within this script run.
+ const unsigned index = runs_[i]->CharacterIndexForXPosition(
+ offset_for_run, include_partial_glyphs);
+ return characters_so_far + index;
+ }
+ current_x = next_x;
+ }
+ } else {
+ if (target_x <= 0)
+ return 0;
+ for (unsigned i = 0; i < runs_.size(); ++i) {
+ if (!runs_[i])
+ continue;
+ float next_x = current_x + runs_[i]->width_;
+ float offset_for_run = target_x - current_x;
+ if (offset_for_run >= 0 && offset_for_run <= runs_[i]->width_) {
+ const unsigned index = runs_[i]->CharacterIndexForXPosition(
+ offset_for_run, include_partial_glyphs);
+ return characters_so_far + index;
+ }
+ characters_so_far += runs_[i]->num_characters_;
+ current_x = next_x;
+ }
+ }
+
+ return characters_so_far;
+}
+
+float ShapeResult::PositionForOffset(
+ unsigned absolute_offset,
+ AdjustMidCluster adjust_mid_cluster) const {
+ float x = 0;
+ float offset_x = 0;
+
+ // The absolute_offset argument represents the offset for the entire
+ // ShapeResult while offset is continuously updated to be relative to the
+ // current run.
+ unsigned offset = absolute_offset;
+
+ if (Rtl()) {
+ // Convert logical offsets to visual offsets, because results are in
+ // logical order while runs are in visual order.
+ x = width_;
+ if (offset < NumCharacters())
+ offset = NumCharacters() - offset - 1;
+ x -= Width();
+ }
+
+ for (unsigned i = 0; i < runs_.size(); i++) {
+ if (!runs_[i])
+ continue;
+ DCHECK_EQ(Rtl(), runs_[i]->Rtl());
+ unsigned num_characters = runs_[i]->num_characters_;
+
+ if (!offset_x && offset < num_characters) {
+ offset_x =
+ runs_[i]->XPositionForVisualOffset(offset, adjust_mid_cluster) + x;
+ break;
+ }
+
+ offset -= num_characters;
+ x += runs_[i]->width_;
+ }
+
+ // The position in question might be just after the text.
+ if (!offset_x && absolute_offset == NumCharacters())
+ return Rtl() ? 0 : width_;
+
+ return offset_x;
+}
+
+void ShapeResult::FallbackFonts(
+ HashSet<const SimpleFontData*>* fallback) const {
+ DCHECK(fallback);
+ DCHECK(primary_font_);
+ for (unsigned i = 0; i < runs_.size(); ++i) {
+ if (runs_[i] && runs_[i]->font_data_ &&
+ runs_[i]->font_data_ != primary_font_) {
+ fallback->insert(runs_[i]->font_data_.get());
+ }
+ }
+}
+
+void ShapeResult::GetRunFontData(Vector<RunFontData>* font_data) const {
+ for (const auto& run : runs_) {
+ font_data->push_back(
+ RunFontData({run->font_data_.get(), run->glyph_data_.size()}));
+ }
+}
+
+// TODO(kojii): VC2015 fails to explicit instantiation of a member function.
+// Typed functions + this private function are to instantiate instances.
+template <typename TextContainerType>
+void ShapeResult::ApplySpacingImpl(
+ ShapeResultSpacing<TextContainerType>& spacing,
+ int text_start_offset) {
+ float offset = 0;
+ float total_space = 0;
+ float space = 0;
+ for (auto& run : runs_) {
+ if (!run)
+ continue;
+ unsigned run_start_index = run->start_index_ + text_start_offset;
+ float total_space_for_run = 0;
+ for (size_t i = 0; i < run->glyph_data_.size(); i++) {
+ HarfBuzzRunGlyphData& glyph_data = run->glyph_data_[i];
+
+ // Skip if it's not a grapheme cluster boundary.
+ if (i + 1 < run->glyph_data_.size() &&
+ glyph_data.character_index ==
+ run->glyph_data_[i + 1].character_index) {
+ continue;
+ }
+
+ space = spacing.ComputeSpacing(
+ run_start_index + glyph_data.character_index, offset);
+ glyph_data.advance += space;
+ total_space_for_run += space;
+
+ // |offset| is non-zero only when justifying CJK characters that follow
+ // non-CJK characters.
+ if (UNLIKELY(offset)) {
+ if (run->IsHorizontal()) {
+ glyph_data.offset.SetWidth(glyph_data.offset.Width() + offset);
+ } else {
+ glyph_data.offset.SetHeight(glyph_data.offset.Height() + offset);
+ has_vertical_offsets_ = true;
+ }
+ offset = 0;
+ }
+ }
+ run->width_ += total_space_for_run;
+ total_space += total_space_for_run;
+ }
+ width_ += total_space;
+
+ // The spacing on the right of the last glyph does not affect the glyph
+ // bounding box. Thus, the glyph bounding box becomes smaller than the advance
+ // if the letter spacing is positve, or larger if negative.
+ if (space) {
+ total_space -= space;
+
+ // TODO(kojii): crbug.com/768284: There are cases where
+ // InlineTextBox::LogicalWidth() is round down of ShapeResult::Width() in
+ // LayoutUnit. Ceiling the width did not help. Add 1px to avoid cut-off.
+ if (space < 0)
+ total_space += 1;
+ }
+
+ // Set the width because glyph bounding box is in logical space.
+ float glyph_bounding_box_width = glyph_bounding_box_.Width() + total_space;
+ if (width_ >= 0 && glyph_bounding_box_width >= 0) {
+ glyph_bounding_box_.SetWidth(glyph_bounding_box_width);
+ return;
+ }
+
+ // Negative word-spacing and/or letter-spacing may cause some glyphs to
+ // overflow the left boundary and result negative measured width. Adjust glyph
+ // bounds accordingly to cover the overflow.
+ // The negative width should be clamped to 0 in CSS box model, but it's up to
+ // caller's responsibility.
+ float left = std::min(width_, glyph_bounding_box_width);
+ if (left < glyph_bounding_box_.X()) {
+ // The right edge should be the width of the first character in most cases,
+ // but computing it requires re-measuring bounding box of each glyph. Leave
+ // it unchanged, which gives an excessive right edge but assures it covers
+ // all glyphs.
+ glyph_bounding_box_.ShiftXEdgeTo(left);
+ } else {
+ glyph_bounding_box_.SetWidth(glyph_bounding_box_width);
+ }
+}
+
+void ShapeResult::ApplySpacing(ShapeResultSpacing<String>& spacing,
+ int text_start_offset) {
+ ApplySpacingImpl(spacing, text_start_offset);
+}
+
+scoped_refptr<ShapeResult> ShapeResult::ApplySpacingToCopy(
+ ShapeResultSpacing<TextRun>& spacing,
+ const TextRun& run) const {
+ unsigned index_of_sub_run = spacing.Text().IndexOfSubRun(run);
+ DCHECK_NE(std::numeric_limits<unsigned>::max(), index_of_sub_run);
+ scoped_refptr<ShapeResult> result = ShapeResult::Create(*this);
+ if (index_of_sub_run != std::numeric_limits<unsigned>::max())
+ result->ApplySpacingImpl(spacing, index_of_sub_run);
+ return result;
+}
+
+namespace {
+
+float HarfBuzzPositionToFloat(hb_position_t value) {
+ return static_cast<float>(value) / (1 << 16);
+}
+
+// Checks whether it's safe to break without reshaping before the given glyph.
+bool IsSafeToBreakBefore(const hb_glyph_info_t* glyph_infos,
+ unsigned num_glyphs,
+ unsigned i) {
+ // Before the first glyph is safe to break.
+ if (!i)
+ return true;
+
+ // Not at a cluster boundary.
+ if (glyph_infos[i].cluster == glyph_infos[i - 1].cluster)
+ return false;
+
+ // The HB_GLYPH_FLAG_UNSAFE_TO_BREAK flag is set for all glyphs in a
+ // given cluster so we only need to check the last one.
+ hb_glyph_flags_t flags = hb_glyph_info_get_glyph_flags(glyph_infos + i);
+ return (flags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) == 0;
+}
+
+} // anonymous namespace
+
+// Computes glyph positions, sets advance and offset of each glyph to RunInfo.
+//
+// Also computes glyph bounding box of the run. In this function, glyph bounding
+// box is in physical.
+template <bool is_horizontal_run>
+void ShapeResult::ComputeGlyphPositions(ShapeResult::RunInfo* run,
+ unsigned start_glyph,
+ unsigned num_glyphs,
+ hb_buffer_t* harf_buzz_buffer,
+ FloatRect* glyph_bounding_box) {
+ DCHECK_EQ(is_horizontal_run, run->IsHorizontal());
+ const SimpleFontData* current_font_data = run->font_data_.get();
+ const hb_glyph_info_t* glyph_infos =
+ hb_buffer_get_glyph_infos(harf_buzz_buffer, nullptr);
+ const hb_glyph_position_t* glyph_positions =
+ hb_buffer_get_glyph_positions(harf_buzz_buffer, nullptr);
+ const unsigned start_cluster =
+ HB_DIRECTION_IS_FORWARD(hb_buffer_get_direction(harf_buzz_buffer))
+ ? glyph_infos[start_glyph].cluster
+ : glyph_infos[start_glyph + num_glyphs - 1].cluster;
+
+ // Compute glyph_origin and glyph_bounding_box in physical, since both offsets
+ // and boudning box of glyphs are in physical. It's the caller's
+ // responsibility to convert the united physical bounds to logical.
+ float total_advance = 0.0f;
+ FloatPoint glyph_origin;
+ if (is_horizontal_run)
+ glyph_origin.SetX(width_);
+ else
+ glyph_origin.SetY(width_);
+ bool has_vertical_offsets = !is_horizontal_run;
+
+ // HarfBuzz returns result in visual order, no need to flip for RTL.
+ for (unsigned i = 0; i < num_glyphs; ++i) {
+ uint16_t glyph = glyph_infos[start_glyph + i].codepoint;
+ hb_glyph_position_t pos = glyph_positions[start_glyph + i];
+
+ // Offset is primarily used when painting glyphs. Keep it in physical.
+ float offset_x = HarfBuzzPositionToFloat(pos.x_offset);
+ float offset_y = -HarfBuzzPositionToFloat(pos.y_offset);
+
+ // One out of x_advance and y_advance is zero, depending on
+ // whether the buffer direction is horizontal or vertical.
+ // Convert to float and negate to avoid integer-overflow for ULONG_MAX.
+ float advance;
+ if (is_horizontal_run)
+ advance = HarfBuzzPositionToFloat(pos.x_advance);
+ else
+ advance = -HarfBuzzPositionToFloat(pos.y_advance);
+
+ uint16_t character_index =
+ glyph_infos[start_glyph + i].cluster - start_cluster;
+ run->glyph_data_[i].character_index = character_index;
+
+ run->SetGlyphAndPositions(i, glyph, advance, offset_x, offset_y);
+ total_advance += advance;
+ has_vertical_offsets |= (offset_y != 0);
+
+ // SetGlyphAndPositions() above sets to draw glyphs at |glyph_origin +
+ // offset_{x,y}|. Move glyph_bounds to that point.
+ // Then move the current point by |advance| from |glyph_origin|.
+ // All positions in hb_glyph_position_t are relative to the current point.
+ // https://behdad.github.io/harfbuzz/harfbuzz-Buffers.html#hb-glyph-position-t-struct
+ FloatRect glyph_bounds = current_font_data->BoundsForGlyph(glyph);
+ if (!glyph_bounds.IsEmpty()) {
+ glyph_bounds.Move(glyph_origin.X() + offset_x,
+ glyph_origin.Y() + offset_y);
+ glyph_bounding_box->Unite(glyph_bounds);
+ }
+ if (is_horizontal_run)
+ glyph_origin.SetX(glyph_origin.X() + advance);
+ else
+ glyph_origin.SetY(glyph_origin.Y() + advance);
+
+ // Check if it is safe to break without reshaping before the cluster.
+ if (IsSafeToBreakBefore(glyph_infos + start_glyph, num_glyphs, i)) {
+ if (run->Rtl())
+ run->safe_break_offsets_.push_front(character_index);
+ else
+ run->safe_break_offsets_.push_back(character_index);
+ }
+ }
+
+ run->width_ = std::max(0.0f, total_advance);
+ has_vertical_offsets_ |= has_vertical_offsets;
+}
+
+void ShapeResult::InsertRun(std::unique_ptr<ShapeResult::RunInfo> run_to_insert,
+ unsigned start_glyph,
+ unsigned num_glyphs,
+ hb_buffer_t* harf_buzz_buffer) {
+ DCHECK_GT(num_glyphs, 0u);
+ std::unique_ptr<ShapeResult::RunInfo> run(std::move(run_to_insert));
+ DCHECK_EQ(num_glyphs, run->glyph_data_.size());
+
+ FloatRect glyph_bounding_box;
+ if (run->IsHorizontal()) {
+ // Inserting a horizontal run into a horizontal or vertical result. In both
+ // cases, no adjustments are needed because |glyph_bounding_box_| is in
+ // logical coordinates and uses alphabetic baseline.
+ ComputeGlyphPositions<true>(run.get(), start_glyph, num_glyphs,
+ harf_buzz_buffer, &glyph_bounding_box);
+ } else {
+ // Inserting a vertical run to a vertical result.
+ ComputeGlyphPositions<false>(run.get(), start_glyph, num_glyphs,
+ harf_buzz_buffer, &glyph_bounding_box);
+ // Convert physical glyph_bounding_box to logical.
+ glyph_bounding_box = glyph_bounding_box.TransposedRect();
+ // The glyph bounding box of a vertical run uses ideographic baseline.
+ // Adjust the box Y position because the bounding box of a ShapeResult uses
+ // alphabetic baseline.
+ // See diagrams of base lines at
+ // https://drafts.csswg.org/css-writing-modes-3/#intro-baselines
+ const FontMetrics& font_metrics = run->font_data_->GetFontMetrics();
+ int baseline_adjust = font_metrics.Ascent(kIdeographicBaseline) -
+ font_metrics.Ascent(kAlphabeticBaseline);
+ glyph_bounding_box.SetY(glyph_bounding_box.Y() + baseline_adjust);
+ }
+ glyph_bounding_box_.Unite(glyph_bounding_box);
+ width_ += run->width_;
+ num_glyphs_ += num_glyphs;
+ DCHECK_GE(num_glyphs_, num_glyphs);
+
+ InsertRun(std::move(run));
+}
+
+void ShapeResult::InsertRun(std::unique_ptr<ShapeResult::RunInfo> run) {
+ // The runs are stored in result->m_runs in visual order. For LTR, we place
+ // the run to be inserted before the next run with a bigger character
+ // start index. For RTL, we place the run before the next run with a lower
+ // character index. Otherwise, for both directions, at the end.
+ if (HB_DIRECTION_IS_FORWARD(run->direction_)) {
+ for (size_t pos = 0; pos < runs_.size(); ++pos) {
+ if (runs_.at(pos)->start_index_ > run->start_index_) {
+ runs_.insert(pos, std::move(run));
+ break;
+ }
+ }
+ } else {
+ for (size_t pos = 0; pos < runs_.size(); ++pos) {
+ if (runs_.at(pos)->start_index_ < run->start_index_) {
+ runs_.insert(pos, std::move(run));
+ break;
+ }
+ }
+ }
+ // If we didn't find an existing slot to place it, append.
+ if (run)
+ runs_.push_back(std::move(run));
+}
+
+// Insert a |RunInfo| without glyphs. |StartIndexForResult()| needs a run to
+// compute the start character index. When all glyphs are missing, this function
+// synthesize a run without glyphs.
+void ShapeResult::InsertRunForIndex(unsigned start_character_index) {
+ DCHECK(runs_.IsEmpty());
+ runs_.push_back(std::make_unique<RunInfo>(
+ primary_font_.get(), !Rtl() ? HB_DIRECTION_LTR : HB_DIRECTION_RTL,
+ CanvasRotationInVertical::kRegular, HB_SCRIPT_UNKNOWN,
+ start_character_index, 0, num_characters_));
+}
+
+ShapeResult::RunInfo* ShapeResult::InsertRunForTesting(
+ unsigned start_index,
+ unsigned num_characters,
+ TextDirection direction,
+ Vector<uint16_t> safe_break_offsets) {
+ std::unique_ptr<RunInfo> run = std::make_unique<ShapeResult::RunInfo>(
+ nullptr, IsLtr(direction) ? HB_DIRECTION_LTR : HB_DIRECTION_RTL,
+ CanvasRotationInVertical::kRegular, HB_SCRIPT_COMMON, start_index, 0,
+ num_characters);
+ run->safe_break_offsets_.AppendVector(safe_break_offsets);
+ RunInfo* run_ptr = run.get();
+ InsertRun(std::move(run));
+ return run_ptr;
+}
+
+// Moves runs at (run_size_before, end) to the front of |runs_|.
+//
+// Runs in RTL result are in visual order, and that new runs should be
+// prepended. This function adjusts the run order after runs were appended.
+void ShapeResult::ReorderRtlRuns(unsigned run_size_before) {
+ DCHECK(Rtl());
+ DCHECK_GT(runs_.size(), run_size_before);
+ if (runs_.size() == run_size_before + 1) {
+ if (!run_size_before)
+ return;
+ std::unique_ptr<RunInfo> new_run(std::move(runs_.back()));
+ runs_.Shrink(runs_.size() - 1);
+ runs_.push_front(std::move(new_run));
+ return;
+ }
+
+ // |push_front| is O(n) that we should not call it multiple times.
+ // Create a new list in the correct order and swap it.
+ Vector<std::unique_ptr<RunInfo>> new_runs;
+ new_runs.ReserveInitialCapacity(runs_.size());
+ for (unsigned i = run_size_before; i < runs_.size(); i++)
+ new_runs.push_back(std::move(runs_[i]));
+
+ // Then append existing runs.
+ for (unsigned i = 0; i < run_size_before; i++)
+ new_runs.push_back(std::move(runs_[i]));
+ runs_.swap(new_runs);
+}
+
+void ShapeResult::CopyRange(unsigned start_offset,
+ unsigned end_offset,
+ ShapeResult* target) const {
+ if (!runs_.size())
+ return;
+
+#if DCHECK_IS_ON()
+ unsigned target_num_characters_before = target->num_characters_;
+#endif
+
+ // When |target| is empty, its character indexes are the specified sub range
+ // of |this|. Otherwise the character indexes are renumbered to be continuous.
+ int index_diff = !target->num_characters_
+ ? 0
+ : target->EndIndexForResult() -
+ std::max(start_offset, StartIndexForResult());
+ unsigned target_run_size_before = target->runs_.size();
+ float total_width = 0;
+ for (const auto& run : runs_) {
+ unsigned run_start = run->start_index_;
+ unsigned run_end = run_start + run->num_characters_;
+
+ if (start_offset < run_end && end_offset > run_start) {
+ unsigned start = start_offset > run_start ? start_offset - run_start : 0;
+ unsigned end = std::min(end_offset, run_end) - run_start;
+ DCHECK(end > start);
+
+ auto sub_run = run->CreateSubRun(start, end);
+ sub_run->start_index_ += index_diff;
+ total_width += sub_run->width_;
+ target->num_characters_ += sub_run->num_characters_;
+ target->num_glyphs_ += sub_run->glyph_data_.size();
+ target->runs_.push_back(std::move(sub_run));
+ }
+ }
+
+ if (target->runs_.size() == target_run_size_before)
+ return;
+
+ // Runs in RTL result are in visual order, and that new runs should be
+ // prepended. Reorder appended runs.
+ DCHECK_EQ(Rtl(), target->Rtl());
+ if (target->Rtl())
+ target->ReorderRtlRuns(target_run_size_before);
+
+ // Compute new glyph bounding box.
+ // If |start_offset| or |end_offset| are the start/end of |this|, use
+ // |glyph_bounding_box_| from |this| for the side. Otherwise, we cannot
+ // compute accurate glyph bounding box; approximate by assuming there are no
+ // glyph overflow nor underflow.
+ float left = target->width_;
+ target->width_ += total_width;
+ float right = target->width_;
+ if (start_offset <= StartIndexForResult())
+ left += glyph_bounding_box_.X();
+ if (end_offset >= EndIndexForResult())
+ right += glyph_bounding_box_.MaxX() - width_;
+ FloatRect adjusted_box(left, glyph_bounding_box_.Y(),
+ std::max(right - left, 0.0f),
+ glyph_bounding_box_.Height());
+ target->glyph_bounding_box_.UniteIfNonZero(adjusted_box);
+
+ target->has_vertical_offsets_ |= has_vertical_offsets_;
+
+#if DCHECK_IS_ON()
+ DCHECK_EQ(target->num_characters_ - target_num_characters_before,
+ std::min(end_offset, EndIndexForResult()) -
+ std::max(start_offset, StartIndexForResult()));
+
+ target->CheckConsistency();
+#endif
+}
+
+scoped_refptr<ShapeResult> ShapeResult::SubRange(unsigned start_offset,
+ unsigned end_offset) const {
+ scoped_refptr<ShapeResult> sub_range =
+ Create(primary_font_.get(), 0, Direction());
+ CopyRange(start_offset, end_offset, sub_range.get());
+ return sub_range;
+}
+
+#if DCHECK_IS_ON()
+void ShapeResult::CheckConsistency() const {
+ if (runs_.IsEmpty()) {
+ DCHECK_EQ(0u, num_characters_);
+ DCHECK_EQ(0u, num_glyphs_);
+ return;
+ }
+
+ unsigned index = StartIndexForResult();
+ unsigned num_glyphs = 0;
+ if (!Rtl()) {
+ for (const auto& run : runs_) {
+ DCHECK_EQ(index, run->start_index_);
+ index += run->num_characters_;
+ num_glyphs += run->glyph_data_.size();
+ }
+ } else {
+ // RTL on Mac may not have runs for the all characters. crbug.com/774034
+ index = runs_.back()->start_index_;
+ for (auto it = runs_.rbegin(); it != runs_.rend(); ++it) {
+ const auto& run = *it;
+ DCHECK_EQ(index, run->start_index_);
+ index += run->num_characters_;
+ num_glyphs += run->glyph_data_.size();
+ }
+ }
+ DCHECK_EQ(index, EndIndexForResult());
+ DCHECK_EQ(num_glyphs, num_glyphs_);
+}
+#endif
+
+scoped_refptr<ShapeResult> ShapeResult::CreateForTabulationCharacters(
+ const Font* font,
+ const TextRun& text_run,
+ float position_offset,
+ unsigned count) {
+ const SimpleFontData* font_data = font->PrimaryFont();
+ // Tab characters are always LTR or RTL, not TTB, even when
+ // isVerticalAnyUpright().
+ std::unique_ptr<ShapeResult::RunInfo> run = std::make_unique<RunInfo>(
+ font_data, text_run.Rtl() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR,
+ CanvasRotationInVertical::kRegular, HB_SCRIPT_COMMON, 0, count, count);
+ float position = text_run.XPos() + position_offset;
+ float start_position = position;
+ for (unsigned i = 0; i < count; i++) {
+ float advance = font->TabWidth(font_data, text_run.GetTabSize(), position);
+ run->glyph_data_[i].character_index = i;
+ run->SetGlyphAndPositions(i, font_data->SpaceGlyph(), advance, 0, 0);
+
+ // Assume it's safe to break after a tab character.
+ run->safe_break_offsets_.push_back(run->glyph_data_[i].character_index);
+ position += advance;
+ }
+ run->width_ = position - start_position;
+
+ scoped_refptr<ShapeResult> result =
+ ShapeResult::Create(font, count, text_run.Direction());
+ result->width_ = run->width_;
+ result->num_glyphs_ = count;
+ DCHECK_EQ(result->num_glyphs_, count); // no overflow
+ result->has_vertical_offsets_ =
+ font_data->PlatformData().IsVerticalAnyUpright();
+ result->runs_.push_back(std::move(run));
+ return result;
+}
+
+void ShapeResult::ToString(StringBuilder* output) const {
+ output->Append("#chars=");
+ output->AppendNumber(num_characters_);
+ output->Append(", #glyphs=");
+ output->AppendNumber(num_glyphs_);
+ output->Append(", dir=");
+ output->AppendNumber(direction_);
+ output->Append(", runs[");
+ output->AppendNumber(runs_.size());
+ output->Append("]{");
+ for (unsigned run_index = 0; run_index < runs_.size(); run_index++) {
+ output->AppendNumber(run_index);
+ const auto& run = *runs_[run_index];
+ output->Append(":{start=");
+ output->AppendNumber(run.start_index_);
+ output->Append(", #chars=");
+ output->AppendNumber(run.num_characters_);
+ output->Append(", dir=");
+ output->AppendNumber(run.direction_);
+ output->Append(", glyphs[");
+ output->AppendNumber(run.glyph_data_.size());
+ output->Append("]{");
+ for (unsigned glyph_index = 0; glyph_index < run.glyph_data_.size();
+ glyph_index++) {
+ output->AppendNumber(glyph_index);
+ const auto& glyph_data = run.glyph_data_[glyph_index];
+ output->Append(":{char=");
+ output->AppendNumber(glyph_data.character_index);
+ output->Append(", glyph=");
+ output->AppendNumber(glyph_data.glyph);
+ output->Append("}");
+ }
+ output->Append("}}");
+ }
+ output->Append("}");
+}
+
+String ShapeResult::ToString() const {
+ StringBuilder output;
+ ToString(&output);
+ return output.ToString();
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
new file mode 100644
index 00000000000..3d842044e48
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_H_
+
+#include <memory>
+#include "third_party/blink/renderer/platform/fonts/canvas_rotation_in_vertical.h"
+#include "third_party/blink/renderer/platform/geometry/float_rect.h"
+#include "third_party/blink/renderer/platform/layout_unit.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/text/text_direction.h"
+#include "third_party/blink/renderer/platform/wtf/forward.h"
+#include "third_party/blink/renderer/platform/wtf/hash_set.h"
+#include "third_party/blink/renderer/platform/wtf/noncopyable.h"
+#include "third_party/blink/renderer/platform/wtf/ref_counted.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+struct hb_buffer_t;
+
+namespace blink {
+
+struct CharacterRange;
+class Font;
+template <typename TextContainerType>
+class PLATFORM_EXPORT ShapeResultSpacing;
+class SimpleFontData;
+class TextRun;
+
+enum class AdjustMidCluster {
+ // Adjust the middle of a grapheme cluster to the logical end boundary.
+ kToEnd,
+ // Adjust the middle of a grapheme cluster to the logical start boundary.
+ kToStart
+};
+
+class PLATFORM_EXPORT ShapeResult : public RefCounted<ShapeResult> {
+ public:
+ static scoped_refptr<ShapeResult> Create(const Font* font,
+ unsigned num_characters,
+ TextDirection direction) {
+ return base::AdoptRef(new ShapeResult(font, num_characters, direction));
+ }
+ static scoped_refptr<ShapeResult> CreateForTabulationCharacters(
+ const Font*,
+ const TextRun&,
+ float position_offset,
+ unsigned count);
+ ~ShapeResult();
+
+ // Returns a mutable unique instance. If |this| has more than 1 ref count,
+ // a clone is created.
+ scoped_refptr<ShapeResult> MutableUnique() const;
+
+ // The logical width of this result.
+ float Width() const { return width_; }
+ LayoutUnit SnappedWidth() const { return LayoutUnit::FromFloatCeil(width_); }
+ // The glyph bounding box, in logical coordinates, using alphabetic baseline
+ // even when the result is in vertical flow.
+ const FloatRect& Bounds() const { return glyph_bounding_box_; }
+ unsigned NumCharacters() const { return num_characters_; }
+ CharacterRange GetCharacterRange(unsigned from, unsigned to) const;
+ // The character start/end index of a range shape result.
+ unsigned StartIndexForResult() const;
+ unsigned EndIndexForResult() const;
+ void FallbackFonts(HashSet<const SimpleFontData*>*) const;
+ TextDirection Direction() const {
+ return static_cast<TextDirection>(direction_);
+ }
+ bool Rtl() const { return Direction() == TextDirection::kRtl; }
+
+ // True if at least one glyph in this result has vertical offsets.
+ //
+ // Vertical result always has vertical offsets, but horizontal result may also
+ // have vertical offsets.
+ bool HasVerticalOffsets() const { return has_vertical_offsets_; }
+
+ // For memory reporting.
+ size_t ByteSize() const;
+
+ // Returns the next or previous offsets respectively at which it is safe to
+ // break without reshaping.
+ // The |offset| given and the return value is for the original string, between
+ // |StartIndexForResult| and |EndIndexForResult|.
+ unsigned NextSafeToBreakOffset(unsigned offset) const;
+ unsigned PreviousSafeToBreakOffset(unsigned offset) const;
+
+ unsigned OffsetForPosition(float target_x, bool include_partial_glyphs) const;
+ float PositionForOffset(unsigned offset,
+ AdjustMidCluster = AdjustMidCluster::kToEnd) const;
+ LayoutUnit SnappedStartPositionForOffset(unsigned offset) const {
+ return LayoutUnit::FromFloatFloor(PositionForOffset(offset));
+ }
+ LayoutUnit SnappedEndPositionForOffset(unsigned offset) const {
+ return LayoutUnit::FromFloatCeil(PositionForOffset(offset));
+ }
+
+ // Apply spacings (letter-spacing, word-spacing, and justification) as
+ // configured to |ShapeResultSpacing|.
+ // |text_start_offset| adjusts the character index in the ShapeResult before
+ // giving it to |ShapeResultSpacing|. It can be negative if
+ // |StartIndexForResult()| is larger than the text in |ShapeResultSpacing|.
+ void ApplySpacing(ShapeResultSpacing<String>&, int text_start_offset = 0);
+ scoped_refptr<ShapeResult> ApplySpacingToCopy(ShapeResultSpacing<TextRun>&,
+ const TextRun&) const;
+
+ void CopyRange(unsigned start, unsigned end, ShapeResult*) const;
+ scoped_refptr<ShapeResult> SubRange(unsigned start_offset,
+ unsigned end_offset) const;
+
+ // Computes the list of fonts along with the number of glyphs for each font.
+ struct RunFontData {
+ SimpleFontData* font_data_;
+ size_t glyph_count_;
+ };
+ void GetRunFontData(Vector<RunFontData>* font_data) const;
+
+ String ToString() const;
+ void ToString(StringBuilder*) const;
+
+ struct RunInfo;
+ RunInfo* InsertRunForTesting(unsigned start_index,
+ unsigned num_characters,
+ TextDirection,
+ Vector<uint16_t> safe_break_offsets = {});
+#if DCHECK_IS_ON()
+ void CheckConsistency() const;
+#endif
+
+ protected:
+ ShapeResult(const SimpleFontData*, unsigned num_characters, TextDirection);
+ ShapeResult(const Font*, unsigned num_characters, TextDirection);
+ ShapeResult(const ShapeResult&);
+
+ static scoped_refptr<ShapeResult> Create(const SimpleFontData* font_data,
+ unsigned num_characters,
+ TextDirection direction) {
+ return base::AdoptRef(
+ new ShapeResult(font_data, num_characters, direction));
+ }
+ static scoped_refptr<ShapeResult> Create(const ShapeResult& other) {
+ return base::AdoptRef(new ShapeResult(other));
+ }
+
+ template <typename TextContainerType>
+ void ApplySpacingImpl(ShapeResultSpacing<TextContainerType>&,
+ int text_start_offset = 0);
+ template <bool is_horizontal_run>
+ void ComputeGlyphPositions(ShapeResult::RunInfo*,
+ unsigned start_glyph,
+ unsigned num_glyphs,
+ hb_buffer_t*,
+ FloatRect* glyph_bounding_box);
+ void InsertRun(std::unique_ptr<ShapeResult::RunInfo>,
+ unsigned start_glyph,
+ unsigned num_glyphs,
+ hb_buffer_t*);
+ void InsertRun(std::unique_ptr<ShapeResult::RunInfo>);
+ void InsertRunForIndex(unsigned start_character_index);
+ void ReorderRtlRuns(unsigned run_size_before);
+
+ float width_;
+ FloatRect glyph_bounding_box_;
+ Vector<std::unique_ptr<RunInfo>> runs_;
+ scoped_refptr<const SimpleFontData> primary_font_;
+
+ unsigned num_characters_;
+ unsigned num_glyphs_ : 30;
+
+ // Overall direction for the TextRun, dictates which order each individual
+ // sub run (represented by RunInfo structs in the m_runs vector) can have a
+ // different text direction.
+ unsigned direction_ : 1;
+
+ // Tracks whether any runs contain glyphs with a y-offset != 0.
+ unsigned has_vertical_offsets_ : 1;
+
+ friend class HarfBuzzShaper;
+ friend class ShapeResultBuffer;
+ friend class ShapeResultBloberizer;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_H_
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.cc
new file mode 100644
index 00000000000..43d94a6941b
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.cc
@@ -0,0 +1,420 @@
+// Copyright 2017 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/shape_result_bloberizer.h"
+
+#include <hb.h>
+#include "third_party/blink/renderer/platform/fonts/font.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h"
+#include "third_party/blink/renderer/platform/text/text_break_iterator.h"
+#include "third_party/blink/renderer/platform/text/text_run.h"
+
+namespace blink {
+
+ShapeResultBloberizer::ShapeResultBloberizer(const Font& font,
+ float device_scale_factor,
+ Type type)
+ : font_(font), device_scale_factor_(device_scale_factor), type_(type) {}
+
+bool ShapeResultBloberizer::HasPendingVerticalOffsets() const {
+ // We exclusively store either horizontal/x-only ofssets -- in which case
+ // m_offsets.size == size, or vertical/xy offsets -- in which case
+ // m_offsets.size == size * 2.
+ DCHECK(pending_glyphs_.size() == pending_offsets_.size() ||
+ pending_glyphs_.size() * 2 == pending_offsets_.size());
+ return pending_glyphs_.size() != pending_offsets_.size();
+}
+
+void ShapeResultBloberizer::CommitPendingRun() {
+ if (pending_glyphs_.IsEmpty())
+ return;
+
+ if (pending_canvas_rotation_ != builder_rotation_) {
+ // The pending run rotation doesn't match the current blob; start a new
+ // blob.
+ CommitPendingBlob();
+ builder_rotation_ = pending_canvas_rotation_;
+ }
+
+ PaintFont run_font;
+ run_font.SetTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ pending_font_data_->PlatformData().SetupPaintFont(
+ &run_font, device_scale_factor_, &font_);
+
+ const auto run_size = pending_glyphs_.size();
+ const auto& buffer = HasPendingVerticalOffsets()
+ ? builder_.AllocRunPos(run_font, run_size)
+ : builder_.AllocRunPosH(run_font, run_size, 0);
+
+ std::copy(pending_glyphs_.begin(), pending_glyphs_.end(), buffer.glyphs);
+ std::copy(pending_offsets_.begin(), pending_offsets_.end(), buffer.pos);
+
+ builder_run_count_ += 1;
+ pending_glyphs_.Shrink(0);
+ pending_offsets_.Shrink(0);
+}
+
+void ShapeResultBloberizer::CommitPendingBlob() {
+ if (!builder_run_count_)
+ return;
+
+ blobs_.emplace_back(builder_.TakeTextBlob(), builder_rotation_);
+ builder_run_count_ = 0;
+}
+
+const ShapeResultBloberizer::BlobBuffer& ShapeResultBloberizer::Blobs() {
+ CommitPendingRun();
+ CommitPendingBlob();
+ DCHECK(pending_glyphs_.IsEmpty());
+ DCHECK_EQ(builder_run_count_, 0u);
+
+ return blobs_;
+}
+
+float ShapeResultBloberizer::FillGlyphs(
+ const TextRunPaintInfo& run_info,
+ const ShapeResultBuffer& result_buffer) {
+ if (CanUseFastPath(run_info.from, run_info.to, run_info.run.length(),
+ result_buffer.HasVerticalOffsets())) {
+ return FillFastHorizontalGlyphs(result_buffer, run_info.run.Direction());
+ }
+
+ float advance = 0;
+ auto results = result_buffer.results_;
+
+ if (run_info.run.Rtl()) {
+ unsigned word_offset = run_info.run.length();
+ for (unsigned j = 0; j < results.size(); j++) {
+ unsigned resolved_index = results.size() - 1 - j;
+ const scoped_refptr<const ShapeResult>& word_result = results[resolved_index];
+ word_offset -= word_result->NumCharacters();
+ advance =
+ FillGlyphsForResult(word_result.get(), run_info.run, run_info.from,
+ run_info.to, advance, word_offset);
+ }
+ } else {
+ unsigned word_offset = 0;
+ for (const auto& word_result : results) {
+ advance =
+ FillGlyphsForResult(word_result.get(), run_info.run, run_info.from,
+ run_info.to, advance, word_offset);
+ word_offset += word_result->NumCharacters();
+ }
+ }
+
+ return advance;
+}
+
+float ShapeResultBloberizer::FillGlyphs(const StringView& text,
+ unsigned from,
+ unsigned to,
+ const ShapeResult* result) {
+ DCHECK(result);
+ DCHECK(to <= text.length());
+ if (CanUseFastPath(from, to, result))
+ return FillFastHorizontalGlyphs(result);
+
+ float advance = 0;
+ float word_offset = 0;
+ return FillGlyphsForResult(result, text, from, to, advance, word_offset);
+}
+
+void ShapeResultBloberizer::FillTextEmphasisGlyphs(
+ const TextRunPaintInfo& run_info,
+ const GlyphData& emphasis_data,
+ const ShapeResultBuffer& result_buffer) {
+ float advance = 0;
+ unsigned word_offset = run_info.run.Rtl() ? run_info.run.length() : 0;
+ auto results = result_buffer.results_;
+
+ for (unsigned j = 0; j < results.size(); j++) {
+ unsigned resolved_index = run_info.run.Rtl() ? results.size() - 1 - j : j;
+ const scoped_refptr<const ShapeResult>& word_result = results[resolved_index];
+ for (unsigned i = 0; i < word_result->runs_.size(); i++) {
+ unsigned resolved_offset =
+ word_offset - (run_info.run.Rtl() ? word_result->NumCharacters() : 0);
+ advance += FillTextEmphasisGlyphsForRun(
+ word_result->runs_[i].get(), run_info.run,
+ run_info.run.CharactersLength(), run_info.run.Direction(),
+ run_info.from, run_info.to, emphasis_data, advance, resolved_offset);
+ }
+ word_offset += word_result->NumCharacters() * (run_info.run.Rtl() ? -1 : 1);
+ }
+}
+
+void ShapeResultBloberizer::FillTextEmphasisGlyphs(const StringView& text,
+ TextDirection direction,
+ unsigned from,
+ unsigned to,
+ const GlyphData& emphasis,
+ const ShapeResult* result) {
+ float advance = 0;
+ unsigned offset = 0;
+
+ for (unsigned i = 0; i < result->runs_.size(); i++) {
+ advance += FillTextEmphasisGlyphsForRun(result->runs_[i].get(), text,
+ text.length(), direction, from, to,
+ emphasis, advance, offset);
+ }
+}
+
+namespace {
+
+template <typename TextContainerType>
+inline bool IsSkipInkException(const ShapeResultBloberizer& bloberizer,
+ const TextContainerType& text,
+ unsigned character_index) {
+ // We want to skip descenders in general, but it is undesirable renderings for
+ // CJK characters.
+ return bloberizer.GetType() == ShapeResultBloberizer::Type::kTextIntercepts &&
+ !Character::CanTextDecorationSkipInk(
+ text.CodepointAt(character_index));
+}
+
+template <typename TextContainerType>
+inline void AddGlyphToBloberizer(ShapeResultBloberizer& bloberizer,
+ float advance,
+ hb_direction_t direction,
+ CanvasRotationInVertical canvas_rotation,
+ const SimpleFontData* font_data,
+ const HarfBuzzRunGlyphData& glyph_data,
+ const TextContainerType& text,
+ unsigned character_index) {
+ FloatPoint start_offset = HB_DIRECTION_IS_HORIZONTAL(direction)
+ ? FloatPoint(advance, 0)
+ : FloatPoint(0, advance);
+ if (!IsSkipInkException(bloberizer, text, character_index)) {
+ bloberizer.Add(glyph_data.glyph, font_data, canvas_rotation,
+ start_offset + glyph_data.offset);
+ }
+}
+
+inline void AddEmphasisMark(ShapeResultBloberizer& bloberizer,
+ const GlyphData& emphasis_data,
+ CanvasRotationInVertical canvas_rotation,
+ FloatPoint glyph_center,
+ float mid_glyph_offset) {
+ const SimpleFontData* emphasis_font_data = emphasis_data.font_data;
+ DCHECK(emphasis_font_data);
+
+ bool is_vertical =
+ emphasis_font_data->PlatformData().IsVerticalAnyUpright() &&
+ emphasis_data.canvas_rotation ==
+ CanvasRotationInVertical::kRotateCanvasUpright;
+
+ if (!is_vertical) {
+ bloberizer.Add(emphasis_data.glyph, emphasis_font_data,
+ CanvasRotationInVertical::kRegular,
+ mid_glyph_offset - glyph_center.X());
+ } else {
+ bloberizer.Add(
+ emphasis_data.glyph, emphasis_font_data,
+ CanvasRotationInVertical::kRotateCanvasUpright,
+ FloatPoint(-glyph_center.X(), mid_glyph_offset - glyph_center.Y()));
+ }
+}
+
+inline unsigned CountGraphemesInCluster(const UChar* str,
+ unsigned str_length,
+ uint16_t start_index,
+ uint16_t end_index) {
+ if (start_index > end_index) {
+ uint16_t temp_index = start_index;
+ start_index = end_index;
+ end_index = temp_index;
+ }
+ uint16_t length = end_index - start_index;
+ DCHECK_LE(static_cast<unsigned>(start_index + length), str_length);
+ TextBreakIterator* cursor_pos_iterator =
+ CursorMovementIterator(&str[start_index], length);
+
+ int cursor_pos = cursor_pos_iterator->current();
+ int num_graphemes = -1;
+ while (0 <= cursor_pos) {
+ cursor_pos = cursor_pos_iterator->next();
+ num_graphemes++;
+ }
+ return std::max(0, num_graphemes);
+}
+
+} // namespace
+
+template <typename TextContainerType>
+float ShapeResultBloberizer::FillGlyphsForResult(const ShapeResult* result,
+ const TextContainerType& text,
+ unsigned from,
+ unsigned to,
+ float initial_advance,
+ unsigned run_offset) {
+ auto total_advance = initial_advance;
+
+ for (const auto& run : result->runs_) {
+ total_advance = run->ForEachGlyphInRange(
+ total_advance, from, to, run_offset,
+ [&](const HarfBuzzRunGlyphData& glyph_data, float total_advance,
+ uint16_t character_index) -> bool {
+
+ AddGlyphToBloberizer(*this, total_advance, run->direction_,
+ run->canvas_rotation_, run->font_data_.get(),
+ glyph_data, text, character_index);
+ return true;
+ });
+ }
+
+ return total_advance;
+}
+
+bool ShapeResultBloberizer::CanUseFastPath(unsigned from,
+ unsigned to,
+ unsigned length,
+ bool has_vertical_offsets) {
+ return !from && to == length && !has_vertical_offsets &&
+ GetType() != ShapeResultBloberizer::Type::kTextIntercepts;
+}
+
+bool ShapeResultBloberizer::CanUseFastPath(unsigned from,
+ unsigned to,
+ const ShapeResult* shape_result) {
+ return from <= shape_result->StartIndexForResult() &&
+ to >= shape_result->EndIndexForResult() &&
+ !shape_result->HasVerticalOffsets() &&
+ GetType() != ShapeResultBloberizer::Type::kTextIntercepts;
+}
+
+float ShapeResultBloberizer::FillFastHorizontalGlyphs(
+ const ShapeResultBuffer& result_buffer,
+ TextDirection text_direction) {
+ DCHECK(!result_buffer.HasVerticalOffsets());
+ DCHECK_NE(GetType(), ShapeResultBloberizer::Type::kTextIntercepts);
+
+ float advance = 0;
+ auto results = result_buffer.results_;
+
+ for (unsigned i = 0; i < results.size(); ++i) {
+ const auto& word_result =
+ IsLtr(text_direction) ? results[i] : results[results.size() - 1 - i];
+ advance = FillFastHorizontalGlyphs(word_result.get(), advance);
+ }
+
+ return advance;
+}
+
+float ShapeResultBloberizer::FillFastHorizontalGlyphs(
+ const ShapeResult* shape_result,
+ float advance) {
+ DCHECK(!shape_result->HasVerticalOffsets());
+ DCHECK_NE(GetType(), ShapeResultBloberizer::Type::kTextIntercepts);
+
+ for (const auto& run : shape_result->runs_) {
+ DCHECK(run);
+ DCHECK(HB_DIRECTION_IS_HORIZONTAL(run->direction_));
+
+ advance =
+ run->ForEachGlyph(advance,
+ [&](const HarfBuzzRunGlyphData& glyph_data,
+ float total_advance) -> bool {
+ DCHECK(!glyph_data.offset.Height());
+ Add(glyph_data.glyph, run->font_data_.get(),
+ run->CanvasRotation(),
+ total_advance + glyph_data.offset.Width());
+ return true;
+ });
+ }
+
+ return advance;
+}
+
+template <typename TextContainerType>
+float ShapeResultBloberizer::FillTextEmphasisGlyphsForRun(
+ const ShapeResult::RunInfo* run,
+ const TextContainerType& text,
+ unsigned text_length,
+ TextDirection direction,
+ unsigned from,
+ unsigned to,
+ const GlyphData& emphasis_data,
+ float initial_advance,
+ unsigned run_offset) {
+ if (!run)
+ return 0;
+
+ unsigned graphemes_in_cluster = 1;
+ float cluster_advance = 0;
+
+ FloatPoint glyph_center =
+ emphasis_data.font_data->BoundsForGlyph(emphasis_data.glyph).Center();
+
+ // A "cluster" in this context means a cluster as it is used by HarfBuzz:
+ // The minimal group of characters and corresponding glyphs, that cannot be
+ // broken down further from a text shaping point of view. A cluster can
+ // contain multiple glyphs and grapheme clusters, with mutually overlapping
+ // boundaries. Below we count grapheme clusters per HarfBuzz clusters, then
+ // linearly split the sum of corresponding glyph advances by the number of
+ // grapheme clusters in order to find positions for emphasis mark drawing.
+ uint16_t cluster_start = static_cast<uint16_t>(
+ direction == TextDirection::kRtl
+ ? run->start_index_ + run->num_characters_ + run_offset
+ : run->GlyphToCharacterIndex(0) + run_offset);
+
+ float advance_so_far = initial_advance;
+ const unsigned num_glyphs = run->glyph_data_.size();
+ for (unsigned i = 0; i < num_glyphs; ++i) {
+ const HarfBuzzRunGlyphData& glyph_data = run->glyph_data_[i];
+ uint16_t current_character_index =
+ run->start_index_ + glyph_data.character_index + run_offset;
+ bool is_run_end = (i + 1 == num_glyphs);
+ bool is_cluster_end =
+ is_run_end || (run->GlyphToCharacterIndex(i + 1) + run_offset !=
+ current_character_index);
+
+ if ((direction == TextDirection::kRtl && current_character_index >= to) ||
+ (direction != TextDirection::kRtl && current_character_index < from)) {
+ advance_so_far += glyph_data.advance;
+ direction == TextDirection::kRtl ? --cluster_start : ++cluster_start;
+ continue;
+ }
+
+ cluster_advance += glyph_data.advance;
+
+ if (text.Is8Bit()) {
+ float glyph_advance_x = glyph_data.advance;
+ if (Character::CanReceiveTextEmphasis(text[current_character_index])) {
+ AddEmphasisMark(*this, emphasis_data, run->CanvasRotation(),
+ glyph_center, advance_so_far + glyph_advance_x / 2);
+ }
+ advance_so_far += glyph_advance_x;
+ } else if (is_cluster_end) {
+ uint16_t cluster_end;
+ if (direction == TextDirection::kRtl) {
+ cluster_end = current_character_index;
+ } else {
+ cluster_end = static_cast<uint16_t>(
+ is_run_end ? run->start_index_ + run->num_characters_ + run_offset
+ : run->GlyphToCharacterIndex(i + 1) + run_offset);
+ }
+ graphemes_in_cluster = CountGraphemesInCluster(
+ text.Characters16(), text_length, cluster_start, cluster_end);
+ if (!graphemes_in_cluster || !cluster_advance)
+ continue;
+
+ float glyph_advance_x = cluster_advance / graphemes_in_cluster;
+ for (unsigned j = 0; j < graphemes_in_cluster; ++j) {
+ // Do not put emphasis marks on space, separator, and control
+ // characters.
+ if (Character::CanReceiveTextEmphasis(text[current_character_index])) {
+ AddEmphasisMark(*this, emphasis_data, run->CanvasRotation(),
+ glyph_center, advance_so_far + glyph_advance_x / 2);
+ }
+ advance_so_far += glyph_advance_x;
+ }
+ cluster_start = cluster_end;
+ cluster_advance = 0;
+ }
+ }
+ return advance_so_far - initial_advance;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.h
new file mode 100644
index 00000000000..320e7305dfa
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.h
@@ -0,0 +1,170 @@
+// Copyright 2017 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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_BLOBERIZER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_BLOBERIZER_H_
+
+#include "third_party/blink/renderer/platform/fonts/canvas_rotation_in_vertical.h"
+#include "third_party/blink/renderer/platform/fonts/glyph.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h"
+#include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
+#include "third_party/blink/renderer/platform/geometry/float_point.h"
+#include "third_party/blink/renderer/platform/graphics/paint/paint_text_blob.h"
+#include "third_party/blink/renderer/platform/graphics/paint/paint_typeface.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+#include "third_party/skia/include/core/SkTextBlob.h"
+
+namespace blink {
+
+class Font;
+
+class PLATFORM_EXPORT ShapeResultBloberizer {
+ WTF_MAKE_NONCOPYABLE(ShapeResultBloberizer);
+ STACK_ALLOCATED();
+
+ public:
+ enum class Type { kNormal, kTextIntercepts };
+
+ ShapeResultBloberizer(const Font&,
+ float device_scale_factor,
+ Type = Type::kNormal);
+
+ Type GetType() const { return type_; }
+
+ float FillGlyphs(const TextRunPaintInfo&, const ShapeResultBuffer&);
+ float FillGlyphs(const StringView&,
+ unsigned from,
+ unsigned to,
+ const ShapeResult*);
+ void FillTextEmphasisGlyphs(const TextRunPaintInfo&,
+ const GlyphData& emphasis_data,
+ const ShapeResultBuffer&);
+ void FillTextEmphasisGlyphs(const StringView&,
+ TextDirection,
+ unsigned from,
+ unsigned to,
+ const GlyphData& emphasis_data,
+ const ShapeResult*);
+
+ void Add(Glyph glyph,
+ const SimpleFontData* font_data,
+ CanvasRotationInVertical canvas_rotation,
+ float h_offset) {
+ // cannot mix x-only/xy offsets
+ DCHECK(!HasPendingVerticalOffsets());
+
+ if (UNLIKELY(font_data != pending_font_data_) ||
+ UNLIKELY(canvas_rotation != pending_canvas_rotation_)) {
+ CommitPendingRun();
+ pending_font_data_ = font_data;
+ pending_canvas_rotation_ = canvas_rotation;
+ DCHECK_EQ(canvas_rotation, CanvasRotationInVertical::kRegular);
+ }
+
+ pending_glyphs_.push_back(glyph);
+ pending_offsets_.push_back(h_offset);
+ }
+
+ void Add(Glyph glyph,
+ const SimpleFontData* font_data,
+ CanvasRotationInVertical canvas_rotation,
+ const FloatPoint& offset) {
+ // cannot mix x-only/xy offsets
+ DCHECK(pending_glyphs_.IsEmpty() || HasPendingVerticalOffsets());
+
+ if (UNLIKELY(font_data != pending_font_data_) ||
+ UNLIKELY(canvas_rotation != pending_canvas_rotation_)) {
+ CommitPendingRun();
+ pending_font_data_ = font_data;
+ pending_canvas_rotation_ = canvas_rotation;
+ pending_vertical_baseline_x_offset_ =
+ canvas_rotation == CanvasRotationInVertical::kRegular
+ ? 0
+ : font_data->GetFontMetrics().FloatAscent() -
+ font_data->GetFontMetrics().FloatAscent(
+ kIdeographicBaseline);
+ }
+
+ pending_glyphs_.push_back(glyph);
+ pending_offsets_.push_back(offset.X() +
+ pending_vertical_baseline_x_offset_);
+ pending_offsets_.push_back(offset.Y());
+ }
+
+ struct BlobInfo {
+ BlobInfo(scoped_refptr<PaintTextBlob> b, CanvasRotationInVertical r)
+ : blob(std::move(b)), rotation(r) {}
+ scoped_refptr<PaintTextBlob> blob;
+ CanvasRotationInVertical rotation;
+ };
+
+ using BlobBuffer = Vector<BlobInfo, 16>;
+ const BlobBuffer& Blobs();
+
+ private:
+ friend class ShapeResultBloberizerTestInfo;
+
+ // Where TextContainerType can be either a TextRun or a StringView.
+ // For legacy layout and LayoutNG respectively.
+ template <typename TextContainerType>
+ float FillGlyphsForResult(const ShapeResult*,
+ const TextContainerType&,
+ unsigned from,
+ unsigned to,
+ float initial_advance,
+ unsigned run_offset);
+
+ // Whether the FillFastHorizontalGlyphs can be used. Only applies for full
+ // runs with no vertical offsets and no text intercepts.
+ bool CanUseFastPath(unsigned from,
+ unsigned to,
+ unsigned length,
+ bool has_vertical_offsets);
+ bool CanUseFastPath(unsigned from, unsigned to, const ShapeResult*);
+ float FillFastHorizontalGlyphs(const ShapeResultBuffer&, TextDirection);
+ float FillFastHorizontalGlyphs(const ShapeResult*, float advance = 0);
+
+ template <typename TextContainerType>
+ float FillTextEmphasisGlyphsForRun(const ShapeResult::RunInfo*,
+ const TextContainerType&,
+ unsigned text_length,
+ TextDirection,
+ unsigned from,
+ unsigned to,
+ const GlyphData& emphasis_data,
+ float initial_advance,
+ unsigned run_offset);
+
+ void CommitPendingRun();
+ void CommitPendingBlob();
+
+ bool HasPendingVerticalOffsets() const;
+
+ const Font& font_;
+ const float device_scale_factor_;
+ const Type type_;
+
+ // Current text blob state.
+ PaintTextBlobBuilder builder_;
+ CanvasRotationInVertical builder_rotation_ =
+ CanvasRotationInVertical::kRegular;
+ size_t builder_run_count_ = 0;
+
+ // Current run state.
+ const SimpleFontData* pending_font_data_ = nullptr;
+ CanvasRotationInVertical pending_canvas_rotation_ =
+ CanvasRotationInVertical::kRegular;
+ Vector<Glyph, 1024> pending_glyphs_;
+ Vector<float, 1024> pending_offsets_;
+ float pending_vertical_baseline_x_offset_ = 0;
+
+ // Constructed blobs.
+ BlobBuffer blobs_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_BLOBERIZER_H_
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer_test.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer_test.cc
new file mode 100644
index 00000000000..25c450376ca
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer_test.cc
@@ -0,0 +1,356 @@
+// Copyright 2017 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/shape_result_bloberizer.h"
+
+#include <memory>
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/platform/fonts/character_range.h"
+#include "third_party/blink/renderer/platform/fonts/font.h"
+#include "third_party/blink/renderer/platform/fonts/opentype/open_type_vertical_data.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h"
+#include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
+#include "third_party/blink/renderer/platform/graphics/paint/paint_typeface.h"
+#include "third_party/blink/renderer/platform/wtf/optional.h"
+
+namespace blink {
+
+namespace {
+
+// Creating minimal test SimpleFontData objects,
+// the font won't have any glyphs, but that's okay.
+static scoped_refptr<SimpleFontData> CreateTestSimpleFontData(
+ bool force_rotation = false) {
+ FontPlatformData platform_data(
+ PaintTypeface::FromSkTypeface(SkTypeface::MakeDefault()), CString(), 10,
+ false, false,
+ force_rotation ? FontOrientation::kVerticalUpright
+ : FontOrientation::kHorizontal);
+ return SimpleFontData::Create(platform_data, nullptr);
+}
+
+class ShapeResultBloberizerTest : 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);
+ font.Update(nullptr);
+ ASSERT_TRUE(font.CanShapeWordByWord());
+ fallback_fonts = nullptr;
+ cache = std::make_unique<ShapeCache>();
+ }
+
+ 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;
+};
+
+} // anonymous namespace
+
+TEST_F(ShapeResultBloberizerTest, StartsEmpty) {
+ Font font;
+ ShapeResultBloberizer bloberizer(font, 1);
+
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::PendingRunFontData(bloberizer),
+ nullptr);
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::PendingRunGlyphs(bloberizer).size(),
+ 0ul);
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::PendingRunOffsets(bloberizer).size(),
+ 0ul);
+ EXPECT_FALSE(
+ ShapeResultBloberizerTestInfo::HasPendingRunVerticalOffsets(bloberizer));
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::PendingBlobRunCount(bloberizer),
+ 0ul);
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::CommittedBlobCount(bloberizer), 0ul);
+
+ EXPECT_TRUE(bloberizer.Blobs().IsEmpty());
+}
+
+TEST_F(ShapeResultBloberizerTest, StoresGlyphsOffsets) {
+ Font font;
+ ShapeResultBloberizer bloberizer(font, 1);
+
+ scoped_refptr<SimpleFontData> font1 = CreateTestSimpleFontData();
+ scoped_refptr<SimpleFontData> font2 = CreateTestSimpleFontData();
+
+ // 2 pending glyphs
+ bloberizer.Add(42, font1.get(), CanvasRotationInVertical::kRegular, 10);
+ bloberizer.Add(43, font1.get(), CanvasRotationInVertical::kRegular, 15);
+
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::PendingRunFontData(bloberizer),
+ font1.get());
+ EXPECT_FALSE(
+ ShapeResultBloberizerTestInfo::HasPendingRunVerticalOffsets(bloberizer));
+ {
+ const auto& glyphs =
+ ShapeResultBloberizerTestInfo::PendingRunGlyphs(bloberizer);
+ EXPECT_EQ(glyphs.size(), 2ul);
+ EXPECT_EQ(42, glyphs[0]);
+ EXPECT_EQ(43, glyphs[1]);
+
+ const auto& offsets =
+ ShapeResultBloberizerTestInfo::PendingRunOffsets(bloberizer);
+ EXPECT_EQ(offsets.size(), 2ul);
+ EXPECT_EQ(10, offsets[0]);
+ EXPECT_EQ(15, offsets[1]);
+ }
+
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::PendingBlobRunCount(bloberizer),
+ 0ul);
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::CommittedBlobCount(bloberizer), 0ul);
+
+ // one more glyph, different font => pending run flush
+ bloberizer.Add(44, font2.get(), CanvasRotationInVertical::kRegular, 12);
+
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::PendingRunFontData(bloberizer),
+ font2.get());
+ EXPECT_FALSE(
+ ShapeResultBloberizerTestInfo::HasPendingRunVerticalOffsets(bloberizer));
+ {
+ const auto& glyphs =
+ ShapeResultBloberizerTestInfo::PendingRunGlyphs(bloberizer);
+ EXPECT_EQ(glyphs.size(), 1ul);
+ EXPECT_EQ(44, glyphs[0]);
+
+ const auto& offsets =
+ ShapeResultBloberizerTestInfo::PendingRunOffsets(bloberizer);
+ EXPECT_EQ(offsets.size(), 1ul);
+ EXPECT_EQ(12, offsets[0]);
+ }
+
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::PendingBlobRunCount(bloberizer),
+ 1ul);
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::CommittedBlobCount(bloberizer), 0ul);
+
+ // flush everything (1 blob w/ 2 runs)
+ EXPECT_EQ(bloberizer.Blobs().size(), 1ul);
+}
+
+TEST_F(ShapeResultBloberizerTest, StoresGlyphsVerticalOffsets) {
+ Font font;
+ ShapeResultBloberizer bloberizer(font, 1);
+
+ scoped_refptr<SimpleFontData> font1 = CreateTestSimpleFontData();
+ scoped_refptr<SimpleFontData> font2 = CreateTestSimpleFontData();
+
+ // 2 pending glyphs
+ bloberizer.Add(42, font1.get(), CanvasRotationInVertical::kRegular,
+ FloatPoint(10, 0));
+ bloberizer.Add(43, font1.get(), CanvasRotationInVertical::kRegular,
+ FloatPoint(15, 0));
+
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::PendingRunFontData(bloberizer),
+ font1.get());
+ EXPECT_TRUE(
+ ShapeResultBloberizerTestInfo::HasPendingRunVerticalOffsets(bloberizer));
+ {
+ const auto& glyphs =
+ ShapeResultBloberizerTestInfo::PendingRunGlyphs(bloberizer);
+ EXPECT_EQ(glyphs.size(), 2ul);
+ EXPECT_EQ(42, glyphs[0]);
+ EXPECT_EQ(43, glyphs[1]);
+
+ const auto& offsets =
+ ShapeResultBloberizerTestInfo::PendingRunOffsets(bloberizer);
+ EXPECT_EQ(offsets.size(), 4ul);
+ EXPECT_EQ(10, offsets[0]);
+ EXPECT_EQ(0, offsets[1]);
+ EXPECT_EQ(15, offsets[2]);
+ EXPECT_EQ(0, offsets[3]);
+ }
+
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::PendingBlobRunCount(bloberizer),
+ 0ul);
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::CommittedBlobCount(bloberizer), 0ul);
+
+ // one more glyph, different font => pending run flush
+ bloberizer.Add(44, font2.get(), CanvasRotationInVertical::kRegular,
+ FloatPoint(12, 2));
+
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::PendingRunFontData(bloberizer),
+ font2.get());
+ EXPECT_TRUE(
+ ShapeResultBloberizerTestInfo::HasPendingRunVerticalOffsets(bloberizer));
+ {
+ const auto& glyphs =
+ ShapeResultBloberizerTestInfo::PendingRunGlyphs(bloberizer);
+ EXPECT_EQ(glyphs.size(), 1ul);
+ EXPECT_EQ(44, glyphs[0]);
+
+ const auto& offsets =
+ ShapeResultBloberizerTestInfo::PendingRunOffsets(bloberizer);
+ EXPECT_EQ(offsets.size(), 2ul);
+ EXPECT_EQ(12, offsets[0]);
+ EXPECT_EQ(2, offsets[1]);
+ }
+
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::PendingBlobRunCount(bloberizer),
+ 1ul);
+ EXPECT_EQ(ShapeResultBloberizerTestInfo::CommittedBlobCount(bloberizer), 0ul);
+
+ // flush everything (1 blob w/ 2 runs)
+ EXPECT_EQ(bloberizer.Blobs().size(), 1ul);
+}
+
+TEST_F(ShapeResultBloberizerTest, MixedBlobRotation) {
+ Font font;
+ ShapeResultBloberizer bloberizer(font, 1);
+
+ scoped_refptr<SimpleFontData> test_font = CreateTestSimpleFontData();
+
+ struct {
+ CanvasRotationInVertical canvas_rotation;
+ size_t expected_pending_glyphs;
+ size_t expected_pending_runs;
+ size_t expected_committed_blobs;
+ } append_ops[] = {
+ // append 2 horizontal glyphs -> these go into the pending glyph buffer
+ {CanvasRotationInVertical::kRegular, 1u, 0u, 0u},
+ {CanvasRotationInVertical::kRegular, 2u, 0u, 0u},
+
+ // append 3 vertical rotated glyphs -> push the prev pending (horizontal)
+ // glyphs into a new run in the current (horizontal) blob
+ {CanvasRotationInVertical::kRotateCanvasUpright, 1u, 1u, 0u},
+ {CanvasRotationInVertical::kRotateCanvasUpright, 2u, 1u, 0u},
+ {CanvasRotationInVertical::kRotateCanvasUpright, 3u, 1u, 0u},
+
+ // append 2 more horizontal glyphs -> flush the current (horizontal) blob,
+ // push prev (vertical) pending glyphs into new vertical blob run
+ {CanvasRotationInVertical::kRegular, 1u, 1u, 1u},
+ {CanvasRotationInVertical::kRegular, 2u, 1u, 1u},
+
+ // append 1 more vertical glyph -> flush current (vertical) blob, push
+ // prev (horizontal) pending glyphs into a new horizontal blob run
+ {CanvasRotationInVertical::kRotateCanvasUpright, 1u, 1u, 2u},
+ };
+
+ for (const auto& op : append_ops) {
+ bloberizer.Add(42, test_font.get(), op.canvas_rotation, FloatPoint());
+ EXPECT_EQ(
+ op.expected_pending_glyphs,
+ ShapeResultBloberizerTestInfo::PendingRunGlyphs(bloberizer).size());
+ EXPECT_EQ(op.canvas_rotation,
+ ShapeResultBloberizerTestInfo::PendingBlobRotation(bloberizer));
+ EXPECT_EQ(op.expected_pending_runs,
+ ShapeResultBloberizerTestInfo::PendingBlobRunCount(bloberizer));
+ EXPECT_EQ(op.expected_committed_blobs,
+ ShapeResultBloberizerTestInfo::CommittedBlobCount(bloberizer));
+ }
+
+ // flush everything -> 4 blobs total
+ EXPECT_EQ(4u, bloberizer.Blobs().size());
+}
+
+// Tests that filling a glyph buffer for a specific range returns the same
+// results when shaping word by word as when shaping the full run in one go.
+TEST_F(ShapeResultBloberizerTest, CommonAccentLeftToRightFillGlyphBuffer) {
+ // "/. ." with an accent mark over the first dot.
+ const UChar kStr[] = {0x2F, 0x301, 0x2E, 0x20, 0x2E, 0x0};
+ TextRun text_run(kStr, 5);
+ TextRunPaintInfo run_info(text_run);
+ run_info.to = 3;
+
+ ShapeResultBloberizer bloberizer(font, 1);
+ CachingWordShaper word_shaper(font);
+ ShapeResultBuffer buffer;
+ word_shaper.FillResultBuffer(run_info, &buffer);
+ bloberizer.FillGlyphs(run_info, buffer);
+
+ Font reference_font(font_description);
+ reference_font.Update(nullptr);
+ reference_font.SetCanShapeWordByWordForTesting(false);
+
+ ShapeResultBloberizer reference_bloberizer(reference_font, 1);
+ CachingWordShaper reference_word_shaper(font);
+ ShapeResultBuffer reference_buffer;
+ reference_word_shaper.FillResultBuffer(run_info, &reference_buffer);
+ reference_bloberizer.FillGlyphs(run_info, reference_buffer);
+
+ const auto& glyphs =
+ ShapeResultBloberizerTestInfo::PendingRunGlyphs(bloberizer);
+ ASSERT_EQ(glyphs.size(), 3ul);
+ const auto reference_glyphs =
+ ShapeResultBloberizerTestInfo::PendingRunGlyphs(reference_bloberizer);
+ ASSERT_EQ(reference_glyphs.size(), 3ul);
+
+ EXPECT_EQ(reference_glyphs[0], glyphs[0]);
+ EXPECT_EQ(reference_glyphs[1], glyphs[1]);
+ EXPECT_EQ(reference_glyphs[2], glyphs[2]);
+}
+
+// Tests that filling a glyph buffer for a specific range returns the same
+// results when shaping word by word as when shaping the full run in one go.
+TEST_F(ShapeResultBloberizerTest, CommonAccentRightToLeftFillGlyphBuffer) {
+ // "[] []" with an accent mark over the last square bracket.
+ const UChar kStr[] = {0x5B, 0x5D, 0x20, 0x5B, 0x301, 0x5D, 0x0};
+ TextRun text_run(kStr, 6);
+ text_run.SetDirection(TextDirection::kRtl);
+ TextRunPaintInfo run_info(text_run);
+ run_info.from = 1;
+
+ ShapeResultBloberizer bloberizer(font, 1);
+ CachingWordShaper word_shaper(font);
+ ShapeResultBuffer buffer;
+ word_shaper.FillResultBuffer(run_info, &buffer);
+ bloberizer.FillGlyphs(run_info, buffer);
+
+ Font reference_font(font_description);
+ reference_font.Update(nullptr);
+ reference_font.SetCanShapeWordByWordForTesting(false);
+
+ ShapeResultBloberizer reference_bloberizer(reference_font, 1);
+ CachingWordShaper reference_word_shaper(font);
+ ShapeResultBuffer reference_buffer;
+ reference_word_shaper.FillResultBuffer(run_info, &reference_buffer);
+ reference_bloberizer.FillGlyphs(run_info, reference_buffer);
+
+ const auto& glyphs =
+ ShapeResultBloberizerTestInfo::PendingRunGlyphs(bloberizer);
+ ASSERT_EQ(5u, glyphs.size());
+ const auto reference_glyphs =
+ ShapeResultBloberizerTestInfo::PendingRunGlyphs(reference_bloberizer);
+ ASSERT_EQ(5u, reference_glyphs.size());
+
+ EXPECT_EQ(reference_glyphs[0], glyphs[0]);
+ EXPECT_EQ(reference_glyphs[1], glyphs[1]);
+ EXPECT_EQ(reference_glyphs[2], glyphs[2]);
+ EXPECT_EQ(reference_glyphs[3], glyphs[3]);
+ EXPECT_EQ(reference_glyphs[4], glyphs[4]);
+}
+
+// Tests that runs with zero glyphs (the ZWJ non-printable character in this
+// case) are handled correctly. This test passes if it does not cause a crash.
+TEST_F(ShapeResultBloberizerTest, SubRunWithZeroGlyphs) {
+ // "Foo &zwnj; bar"
+ const UChar kStr[] = {0x46, 0x6F, 0x6F, 0x20, 0x200C,
+ 0x20, 0x62, 0x61, 0x71, 0x0};
+ TextRun text_run(kStr, 9);
+
+ CachingWordShaper shaper(font);
+ FloatRect glyph_bounds;
+ ASSERT_GT(shaper.Width(text_run, nullptr, &glyph_bounds), 0);
+
+ ShapeResultBloberizer bloberizer(font, 1);
+ TextRunPaintInfo run_info(text_run);
+ run_info.to = 8;
+
+ CachingWordShaper word_shaper(font);
+ ShapeResultBuffer buffer;
+ word_shaper.FillResultBuffer(run_info, &buffer);
+ bloberizer.FillGlyphs(run_info, buffer);
+
+ shaper.GetCharacterRange(text_run, 0, 8);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.cc
new file mode 100644
index 00000000000..23511eaa6c2
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.cc
@@ -0,0 +1,235 @@
+// Copyright 2016 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/shape_result_buffer.h"
+
+#include "third_party/blink/renderer/platform/fonts/character_range.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h"
+#include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
+#include "third_party/blink/renderer/platform/geometry/float_point.h"
+#include "third_party/blink/renderer/platform/text/text_direction.h"
+
+namespace blink {
+
+// TODO(eae): This is a bit of a hack to allow reuse of the implementation
+// for both ShapeResultBuffer and single ShapeResult use cases. Ideally the
+// logic should move into ShapeResult itself and then the ShapeResultBuffer
+// implementation may wrap that.
+CharacterRange ShapeResultBuffer::GetCharacterRange(
+ scoped_refptr<const ShapeResult> result,
+ TextDirection direction,
+ float total_width,
+ unsigned from,
+ unsigned to) {
+ Vector<scoped_refptr<const ShapeResult>, 64> results;
+ results.push_back(result);
+ return GetCharacterRangeInternal(results, direction, total_width, from, to);
+}
+
+CharacterRange ShapeResultBuffer::GetCharacterRangeInternal(
+ const Vector<scoped_refptr<const ShapeResult>, 64>& results,
+ TextDirection direction,
+ float total_width,
+ unsigned absolute_from,
+ unsigned absolute_to) {
+ float current_x = 0;
+ float from_x = 0;
+ float to_x = 0;
+ bool found_from_x = false;
+ bool found_to_x = false;
+ float min_y = 0;
+ float max_y = 0;
+
+ if (direction == TextDirection::kRtl)
+ current_x = total_width;
+
+ // The absoluteFrom and absoluteTo arguments represent the start/end offset
+ // for the entire run, from/to are continuously updated to be relative to
+ // the current word (ShapeResult instance).
+ int from = absolute_from;
+ int to = absolute_to;
+
+ unsigned total_num_characters = 0;
+ for (unsigned j = 0; j < results.size(); j++) {
+ const scoped_refptr<const ShapeResult> result = results[j];
+ if (direction == TextDirection::kRtl) {
+ // Convert logical offsets to visual offsets, because results are in
+ // logical order while runs are in visual order.
+ if (!found_from_x && from >= 0 &&
+ static_cast<unsigned>(from) < result->NumCharacters())
+ from = result->NumCharacters() - from - 1;
+ if (!found_to_x && to >= 0 &&
+ static_cast<unsigned>(to) < result->NumCharacters())
+ to = result->NumCharacters() - to - 1;
+ current_x -= result->Width();
+ }
+ for (unsigned i = 0; i < result->runs_.size(); i++) {
+ if (!result->runs_[i])
+ continue;
+ DCHECK_EQ(direction == TextDirection::kRtl, result->runs_[i]->Rtl());
+ int num_characters = result->runs_[i]->num_characters_;
+ if (!found_from_x && from >= 0 && from < num_characters) {
+ from_x = result->runs_[i]->XPositionForVisualOffset(
+ from, AdjustMidCluster::kToStart) +
+ current_x;
+ found_from_x = true;
+ } else {
+ from -= num_characters;
+ }
+
+ if (!found_to_x && to >= 0 && to < num_characters) {
+ to_x = result->runs_[i]->XPositionForVisualOffset(
+ to, AdjustMidCluster::kToEnd) +
+ current_x;
+ found_to_x = true;
+ } else {
+ to -= num_characters;
+ }
+
+ if (found_from_x || found_to_x) {
+ min_y = std::min(min_y, result->Bounds().Y());
+ max_y = std::max(max_y, result->Bounds().MaxY());
+ }
+
+ if (found_from_x && found_to_x)
+ break;
+ current_x += result->runs_[i]->width_;
+ }
+ if (direction == TextDirection::kRtl)
+ current_x -= result->Width();
+ total_num_characters += result->NumCharacters();
+ }
+
+ // The position in question might be just after the text.
+ if (!found_from_x && absolute_from == total_num_characters) {
+ from_x = direction == TextDirection::kRtl ? 0 : total_width;
+ found_from_x = true;
+ }
+ if (!found_to_x && absolute_to == total_num_characters) {
+ to_x = direction == TextDirection::kRtl ? 0 : total_width;
+ found_to_x = true;
+ }
+ if (!found_from_x)
+ from_x = 0;
+ if (!found_to_x)
+ to_x = direction == TextDirection::kRtl ? 0 : total_width;
+
+ // None of our runs is part of the selection, possibly invalid arguments.
+ if (!found_to_x && !found_from_x)
+ from_x = to_x = 0;
+ if (from_x < to_x)
+ return CharacterRange(from_x, to_x, -min_y, max_y);
+ return CharacterRange(to_x, from_x, -min_y, max_y);
+}
+
+CharacterRange ShapeResultBuffer::GetCharacterRange(TextDirection direction,
+ float total_width,
+ unsigned from,
+ unsigned to) const {
+ return GetCharacterRangeInternal(results_, direction, total_width, from, to);
+}
+
+void ShapeResultBuffer::AddRunInfoRanges(const ShapeResult::RunInfo& run_info,
+ float offset,
+ Vector<CharacterRange>& ranges) {
+ Vector<float> character_widths(run_info.num_characters_);
+ for (const auto& glyph : run_info.glyph_data_)
+ character_widths[glyph.character_index] += glyph.advance;
+
+ for (unsigned character_index = 0; character_index < run_info.num_characters_;
+ character_index++) {
+ float start = offset;
+ offset += character_widths[character_index];
+ float end = offset;
+
+ // To match getCharacterRange we flip ranges to ensure start <= end.
+ if (end < start)
+ ranges.push_back(CharacterRange(end, start, 0, 0));
+ else
+ ranges.push_back(CharacterRange(start, end, 0, 0));
+ }
+}
+
+Vector<CharacterRange> ShapeResultBuffer::IndividualCharacterRanges(
+ TextDirection direction,
+ float total_width) const {
+ Vector<CharacterRange> ranges;
+ float current_x = direction == TextDirection::kRtl ? total_width : 0;
+ for (const scoped_refptr<const ShapeResult> result : results_) {
+ if (direction == TextDirection::kRtl)
+ current_x -= result->Width();
+ unsigned run_count = result->runs_.size();
+ for (unsigned index = 0; index < run_count; index++) {
+ unsigned run_index =
+ direction == TextDirection::kRtl ? run_count - 1 - index : index;
+ AddRunInfoRanges(*result->runs_[run_index], current_x, ranges);
+ current_x += result->runs_[run_index]->width_;
+ }
+ if (direction == TextDirection::kRtl)
+ current_x -= result->Width();
+ }
+ return ranges;
+}
+
+int ShapeResultBuffer::OffsetForPosition(const TextRun& run,
+ float target_x,
+ bool include_partial_glyphs) const {
+ unsigned total_offset;
+ if (run.Rtl()) {
+ total_offset = run.length();
+ for (unsigned i = results_.size(); i; --i) {
+ const scoped_refptr<const ShapeResult>& word_result = results_[i - 1];
+ if (!word_result)
+ continue;
+ total_offset -= word_result->NumCharacters();
+ if (target_x >= 0 && target_x <= word_result->Width()) {
+ int offset_for_word =
+ word_result->OffsetForPosition(target_x, include_partial_glyphs);
+ return total_offset + offset_for_word;
+ }
+ target_x -= word_result->Width();
+ }
+ } else {
+ total_offset = 0;
+ for (const auto& word_result : results_) {
+ if (!word_result)
+ continue;
+ int offset_for_word =
+ word_result->OffsetForPosition(target_x, include_partial_glyphs);
+ DCHECK_GE(offset_for_word, 0);
+ total_offset += offset_for_word;
+ if (target_x >= 0 && target_x <= word_result->Width())
+ return total_offset;
+ target_x -= word_result->Width();
+ }
+ }
+ return total_offset;
+}
+
+Vector<ShapeResult::RunFontData> ShapeResultBuffer::GetRunFontData() const {
+ Vector<ShapeResult::RunFontData> font_data;
+ for (const auto& result : results_)
+ result->GetRunFontData(&font_data);
+ return font_data;
+}
+
+GlyphData ShapeResultBuffer::EmphasisMarkGlyphData(
+ const FontDescription& font_description) const {
+ for (const auto& result : results_) {
+ for (const auto& run : result->runs_) {
+ DCHECK(run->font_data_);
+ if (run->glyph_data_.IsEmpty())
+ continue;
+
+ return GlyphData(
+ run->glyph_data_[0].glyph,
+ run->font_data_->EmphasisMarkFontData(font_description).get(),
+ run->CanvasRotation());
+ }
+ }
+
+ return GlyphData();
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h
new file mode 100644
index 00000000000..af1b66e9703
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h
@@ -0,0 +1,77 @@
+// Copyright 2016 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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_BUFFER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_BUFFER_H_
+
+#include "base/memory/scoped_refptr.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+struct CharacterRange;
+class FontDescription;
+struct GlyphData;
+class ShapeResultBloberizer;
+class TextRun;
+
+class PLATFORM_EXPORT ShapeResultBuffer {
+ WTF_MAKE_NONCOPYABLE(ShapeResultBuffer);
+ STACK_ALLOCATED();
+
+ public:
+ ShapeResultBuffer() : has_vertical_offsets_(false) {}
+
+ void AppendResult(scoped_refptr<const ShapeResult> result) {
+ has_vertical_offsets_ |= result->HasVerticalOffsets();
+ results_.push_back(std::move(result));
+ }
+
+ bool HasVerticalOffsets() const { return has_vertical_offsets_; }
+
+ int OffsetForPosition(const TextRun&,
+ float target_x,
+ bool include_partial_glyphs) const;
+ CharacterRange GetCharacterRange(TextDirection,
+ float total_width,
+ unsigned from,
+ unsigned to) const;
+ Vector<CharacterRange> IndividualCharacterRanges(TextDirection,
+ float total_width) const;
+
+ static CharacterRange GetCharacterRange(scoped_refptr<const ShapeResult>,
+ TextDirection,
+ float total_width,
+ unsigned from,
+ unsigned to);
+
+ Vector<ShapeResult::RunFontData> GetRunFontData() const;
+
+ GlyphData EmphasisMarkGlyphData(const FontDescription&) const;
+
+ private:
+ friend class ShapeResultBloberizer;
+ static CharacterRange GetCharacterRangeInternal(
+ const Vector<scoped_refptr<const ShapeResult>, 64>&,
+ TextDirection,
+ float total_width,
+ unsigned from,
+ unsigned to);
+
+ static void AddRunInfoRanges(const ShapeResult::RunInfo&,
+ float offset,
+ Vector<CharacterRange>&);
+
+ // Empirically, cases where we get more than 50 ShapeResults are extremely
+ // rare.
+ Vector<scoped_refptr<const ShapeResult>, 64> results_;
+ bool has_vertical_offsets_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_BUFFER_H_
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
new file mode 100644
index 00000000000..d24ea32cf99
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2012 Google Inc. All rights reserved.
+ * Copyright (C) 2013 BlackBerry Limited. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_INLINE_HEADERS_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_INLINE_HEADERS_H_
+
+#include <hb.h>
+#include <memory>
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/noncopyable.h"
+
+namespace blink {
+
+class SimpleFontData;
+
+struct HarfBuzzRunGlyphData {
+ DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
+ uint16_t glyph;
+ uint16_t character_index;
+ float advance;
+ FloatSize offset;
+};
+
+struct ShapeResult::RunInfo {
+ USING_FAST_MALLOC(RunInfo);
+
+ public:
+ RunInfo(const SimpleFontData* font,
+ hb_direction_t dir,
+ CanvasRotationInVertical canvas_rotation,
+ hb_script_t script,
+ unsigned start_index,
+ unsigned num_glyphs,
+ unsigned num_characters)
+ : font_data_(const_cast<SimpleFontData*>(font)),
+ direction_(dir),
+ canvas_rotation_(canvas_rotation),
+ script_(script),
+ glyph_data_(num_glyphs),
+ start_index_(start_index),
+ num_characters_(num_characters),
+ width_(0.0f) {}
+
+ RunInfo(const RunInfo& other)
+ : font_data_(other.font_data_),
+ direction_(other.direction_),
+ canvas_rotation_(other.canvas_rotation_),
+ script_(other.script_),
+ glyph_data_(other.glyph_data_),
+ start_index_(other.start_index_),
+ num_characters_(other.num_characters_),
+ width_(other.width_) {}
+
+ bool Rtl() const { return HB_DIRECTION_IS_BACKWARD(direction_); }
+ bool IsHorizontal() const { return HB_DIRECTION_IS_HORIZONTAL(direction_); }
+ CanvasRotationInVertical CanvasRotation() const { return canvas_rotation_; }
+ unsigned NextSafeToBreakOffset(unsigned) const;
+ unsigned PreviousSafeToBreakOffset(unsigned) const;
+ float XPositionForVisualOffset(unsigned, AdjustMidCluster) const;
+ float XPositionForOffset(unsigned, AdjustMidCluster) const;
+ int CharacterIndexForXPosition(float, bool include_partial_glyphs) const;
+ void SetGlyphAndPositions(unsigned index,
+ uint16_t glyph_id,
+ float advance,
+ float offset_x,
+ float offset_y);
+
+ size_t GlyphToCharacterIndex(size_t i) const {
+ return start_index_ + glyph_data_[i].character_index;
+ }
+
+ // For memory reporting.
+ size_t ByteSize() const {
+ return sizeof(this) + glyph_data_.size() * sizeof(HarfBuzzRunGlyphData);
+ }
+
+ // Creates a new RunInfo instance representing a subset of the current run.
+ std::unique_ptr<RunInfo> CreateSubRun(unsigned start, unsigned end) {
+ DCHECK(end > start);
+ unsigned number_of_characters = std::min(end - start, num_characters_);
+
+ // This ends up looping over the glyphs twice if we don't know the glyph
+ // count up front. Once to count the number of glyphs and allocate the new
+ // RunInfo object and then a second time to copy the glyphs over.
+ // TODO: Compared to the cost of allocation and copying the extra loop is
+ // probably fine but we might want to try to eliminate it if we can.
+ unsigned number_of_glyphs;
+ if (start == 0 && end == num_characters_) {
+ number_of_glyphs = glyph_data_.size();
+ } else {
+ number_of_glyphs = 0;
+ ForEachGlyphInRange(
+ 0, start_index_ + start, start_index_ + end, 0,
+ [&](const HarfBuzzRunGlyphData&, float, uint16_t) -> bool {
+ number_of_glyphs++;
+ return true;
+ });
+ }
+
+ auto run = std::make_unique<RunInfo>(
+ font_data_.get(), direction_, canvas_rotation_, script_,
+ start_index_ + start, number_of_glyphs, number_of_characters);
+
+ unsigned sub_glyph_index = 0;
+ float total_advance = 0;
+ ForEachGlyphInRange(
+ 0, start_index_ + start, start_index_ + end, 0,
+ [&](const HarfBuzzRunGlyphData& glyph_data, float, uint16_t) -> bool {
+ HarfBuzzRunGlyphData& sub_glyph = run->glyph_data_[sub_glyph_index++];
+ sub_glyph.glyph = glyph_data.glyph;
+ sub_glyph.character_index = glyph_data.character_index - start;
+ sub_glyph.advance = glyph_data.advance;
+ sub_glyph.offset = glyph_data.offset;
+ total_advance += glyph_data.advance;
+ return true;
+ });
+
+ run->width_ = total_advance;
+ run->num_characters_ = number_of_characters;
+
+ for (unsigned i = 0; i < safe_break_offsets_.size(); i++) {
+ if (safe_break_offsets_[i] >= start && safe_break_offsets_[i] <= end)
+ run->safe_break_offsets_.push_back(safe_break_offsets_[i] - start);
+ }
+
+ return run;
+ }
+
+ // Iterates over, and applies the functor to all the glyphs in this run.
+ // Also tracks (and returns) a seeded total advance.
+ //
+ // Functor signature:
+ //
+ // bool func(const HarfBuzzRunGlyphData& glyphData, float totalAdvance)
+ //
+ // where the returned bool signals whether iteration should continue (true)
+ // or stop (false).
+ template <typename Func>
+ float ForEachGlyph(float initial_advance, Func func) const {
+ float total_advance = initial_advance;
+
+ for (const auto& glyph_data : glyph_data_) {
+ if (!func(glyph_data, total_advance))
+ break;
+ total_advance += glyph_data.advance;
+ }
+
+ return total_advance;
+ }
+
+ // Same as the above, except it only applies the functor to glyphs in the
+ // specified range, and stops after the range.
+ template <typename Func>
+ float ForEachGlyphInRange(float initial_advance,
+ unsigned from,
+ unsigned to,
+ unsigned index_offset,
+ Func func) const {
+ return ForEachGlyph(
+ initial_advance,
+ [&](const HarfBuzzRunGlyphData& glyph_data,
+ float total_advance) -> bool {
+ const uint16_t character_index =
+ start_index_ + glyph_data.character_index + index_offset;
+
+ if (character_index < from) {
+ // Glyph out-of-range; before the range (and must continue
+ // accumulating advance) in LTR.
+ return !Rtl();
+ }
+
+ if (character_index >= to) {
+ // Glyph out-of-range; before the range (and must continue
+ // accumulating advance) in RTL.
+ return Rtl();
+ }
+
+ // Glyph in range; apply functor.
+ return func(glyph_data, total_advance, character_index);
+ });
+ }
+
+ scoped_refptr<SimpleFontData> font_data_;
+ hb_direction_t direction_;
+ // For upright-in-vertical we need to tell the ShapeResultBloberizer to rotate
+ // the canvas back 90deg for this RunInfo.
+ CanvasRotationInVertical canvas_rotation_;
+ hb_script_t script_;
+ Vector<HarfBuzzRunGlyphData> glyph_data_;
+ // List of character indecies before which it's safe to break without
+ // reshaping.
+ Vector<uint16_t> safe_break_offsets_;
+ unsigned start_index_;
+ unsigned num_characters_;
+ float width_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_INLINE_HEADERS_H_
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.cc
new file mode 100644
index 00000000000..8e6a111da18
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.cc
@@ -0,0 +1,195 @@
+// Copyright 2016 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/shape_result_spacing.h"
+
+#include "third_party/blink/renderer/platform/fonts/font_description.h"
+#include "third_party/blink/renderer/platform/text/text_run.h"
+
+namespace blink {
+
+template <typename TextContainerType>
+ShapeResultSpacing<TextContainerType>::ShapeResultSpacing(
+ const TextContainerType& text)
+ : text_(text),
+ letter_spacing_(0),
+ word_spacing_(0),
+ expansion_(0),
+ expansion_per_opportunity_(0),
+ expansion_opportunity_count_(0),
+ text_justify_(TextJustify::kAuto),
+ has_spacing_(false),
+ normalize_space_(false),
+ allow_tabs_(false),
+ is_after_expansion_(false) {}
+
+template <typename TextContainerType>
+bool ShapeResultSpacing<TextContainerType>::SetSpacing(
+ const FontDescription& font_description) {
+ if (!font_description.LetterSpacing() && !font_description.WordSpacing()) {
+ has_spacing_ = false;
+ return false;
+ }
+
+ letter_spacing_ = font_description.LetterSpacing();
+ word_spacing_ = font_description.WordSpacing();
+ DCHECK(!normalize_space_);
+ allow_tabs_ = true;
+ has_spacing_ = true;
+ return true;
+}
+
+template <typename TextContainerType>
+void ShapeResultSpacing<TextContainerType>::SetExpansion(
+ float expansion,
+ TextDirection direction,
+ TextJustify text_justify,
+ bool allows_leading_expansion,
+ bool allows_trailing_expansion) {
+ DCHECK_GT(expansion, 0);
+ expansion_ = expansion;
+ ComputeExpansion(allows_leading_expansion, allows_trailing_expansion,
+ direction, text_justify);
+ has_spacing_ |= HasExpansion();
+}
+
+template <typename TextContainerType>
+void ShapeResultSpacing<TextContainerType>::SetSpacingAndExpansion(
+ const FontDescription& font_description) {
+ // Available only for TextRun since it has expansion data.
+ NOTREACHED();
+}
+
+template <>
+void ShapeResultSpacing<TextRun>::SetSpacingAndExpansion(
+ const FontDescription& font_description) {
+ letter_spacing_ = font_description.LetterSpacing();
+ word_spacing_ = font_description.WordSpacing();
+ expansion_ = text_.Expansion();
+ has_spacing_ = letter_spacing_ || word_spacing_ || expansion_;
+ if (!has_spacing_)
+ return;
+
+ normalize_space_ = text_.NormalizeSpace();
+ allow_tabs_ = text_.AllowTabs();
+
+ if (expansion_) {
+ ComputeExpansion(text_.AllowsLeadingExpansion(),
+ text_.AllowsTrailingExpansion(), text_.Direction(),
+ text_.GetTextJustify());
+ }
+}
+
+template <typename TextContainerType>
+void ShapeResultSpacing<TextContainerType>::ComputeExpansion(
+ bool allows_leading_expansion,
+ bool allows_trailing_expansion,
+ TextDirection direction,
+ TextJustify text_justify) {
+ DCHECK_GT(expansion_, 0);
+
+ text_justify_ = text_justify;
+ if (text_justify_ == TextJustify::kNone) {
+ expansion_opportunity_count_ = 0;
+ return;
+ }
+
+ is_after_expansion_ = !allows_leading_expansion;
+ bool is_after_expansion = is_after_expansion_;
+ if (text_.Is8Bit()) {
+ expansion_opportunity_count_ = Character::ExpansionOpportunityCount(
+ text_.Characters8(), text_.length(), direction, is_after_expansion,
+ text_justify_);
+ } else {
+ expansion_opportunity_count_ = Character::ExpansionOpportunityCount(
+ text_.Characters16(), text_.length(), direction, is_after_expansion,
+ text_justify_);
+ }
+ if (is_after_expansion && !allows_trailing_expansion) {
+ DCHECK_GT(expansion_opportunity_count_, 0u);
+ --expansion_opportunity_count_;
+ }
+
+ if (expansion_opportunity_count_)
+ expansion_per_opportunity_ = expansion_ / expansion_opportunity_count_;
+}
+
+template <typename TextContainerType>
+float ShapeResultSpacing<TextContainerType>::NextExpansion() {
+ if (!expansion_opportunity_count_) {
+ NOTREACHED();
+ return 0;
+ }
+
+ is_after_expansion_ = true;
+
+ if (!--expansion_opportunity_count_) {
+ float remaining = expansion_;
+ expansion_ = 0;
+ return remaining;
+ }
+
+ expansion_ -= expansion_per_opportunity_;
+ return expansion_per_opportunity_;
+}
+
+template <typename TextContainerType>
+float ShapeResultSpacing<TextContainerType>::ComputeSpacing(unsigned index,
+ float& offset) {
+ DCHECK(has_spacing_);
+ UChar32 character = text_[index];
+ bool treat_as_space =
+ (Character::TreatAsSpace(character) ||
+ (normalize_space_ &&
+ Character::IsNormalizedCanvasSpaceCharacter(character))) &&
+ (character != '\t' || !allow_tabs_);
+ if (treat_as_space && character != kNoBreakSpaceCharacter)
+ character = kSpaceCharacter;
+
+ float spacing = 0;
+ if (letter_spacing_ && !Character::TreatAsZeroWidthSpace(character))
+ spacing += letter_spacing_;
+
+ if (treat_as_space && (index || character == kNoBreakSpaceCharacter))
+ spacing += word_spacing_;
+
+ if (!HasExpansion())
+ return spacing;
+
+ if (treat_as_space)
+ return spacing + NextExpansion();
+
+ if (text_.Is8Bit() || text_justify_ != TextJustify::kAuto)
+ return spacing;
+
+ // isCJKIdeographOrSymbol() has expansion opportunities both before and
+ // after each character.
+ // http://www.w3.org/TR/jlreq/#line_adjustment
+ if (U16_IS_LEAD(character) && index + 1 < text_.length() &&
+ U16_IS_TRAIL(text_[index + 1]))
+ character = U16_GET_SUPPLEMENTARY(character, text_[index + 1]);
+ if (!Character::IsCJKIdeographOrSymbol(character)) {
+ is_after_expansion_ = false;
+ return spacing;
+ }
+
+ if (!is_after_expansion_) {
+ // Take the expansion opportunity before this ideograph.
+ float expand_before = NextExpansion();
+ if (expand_before) {
+ offset += expand_before;
+ spacing += expand_before;
+ }
+ if (!HasExpansion())
+ return spacing;
+ }
+
+ return spacing + NextExpansion();
+}
+
+// Instantiate the template class.
+template class ShapeResultSpacing<TextRun>;
+template class ShapeResultSpacing<String>;
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h
new file mode 100644
index 00000000000..391de4222b8
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h
@@ -0,0 +1,80 @@
+// Copyright 2016 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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_SPACING_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_SPACING_H_
+
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/text/character.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+
+namespace blink {
+
+class FontDescription;
+
+// A context object to apply letter-spacing, word-spacing, and justification to
+// ShapeResult.
+template <typename TextContainerType>
+class PLATFORM_EXPORT ShapeResultSpacing final {
+ STACK_ALLOCATED();
+
+ public:
+ explicit ShapeResultSpacing(const TextContainerType&);
+
+ const TextContainerType& Text() const { return text_; }
+ float LetterSpacing() const { return letter_spacing_; }
+ bool HasSpacing() const { return has_spacing_; }
+ bool HasExpansion() const { return expansion_opportunity_count_; }
+
+ // Set letter-spacing and word-spacing.
+ bool SetSpacing(const FontDescription&);
+
+ // Set the expansion for the justification.
+ void SetExpansion(float expansion,
+ TextDirection,
+ TextJustify,
+ bool allows_leading_expansion = false,
+ bool allows_trailing_expansion = false);
+
+ // Set letter-spacing, word-spacing, and justification.
+ // Available only for TextRun.
+ void SetSpacingAndExpansion(const FontDescription&);
+
+ // Compute the sum of all spacings for the specified |index|.
+ // The |index| is for the |TextContainerType| given in the constructor.
+ // For justification, this function must be called incrementally since it
+ // keeps states and counts consumed justification opportunities.
+ float ComputeSpacing(unsigned index, float& offset);
+
+ private:
+ bool IsAfterExpansion() const { return is_after_expansion_; }
+
+ void ComputeExpansion(bool allows_leading_expansion,
+ bool allows_trailing_expansion,
+ TextDirection,
+ TextJustify);
+
+ float NextExpansion();
+
+ const TextContainerType& text_;
+ float letter_spacing_;
+ float word_spacing_;
+ float expansion_;
+ float expansion_per_opportunity_;
+ unsigned expansion_opportunity_count_;
+ TextJustify text_justify_;
+ bool has_spacing_;
+ bool normalize_space_;
+ bool allow_tabs_;
+ bool is_after_expansion_;
+};
+
+// Forward declare so no implicit instantiations happen before the
+// first explicit instantiation (which would be a C++ violation).
+template <>
+void ShapeResultSpacing<TextRun>::SetSpacingAndExpansion(
+ const FontDescription&);
+} // namespace blink
+
+#endif
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.cc
new file mode 100644
index 00000000000..a0bef42a0bf
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.cc
@@ -0,0 +1,66 @@
+// Copyright 2015 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/shape_result_test_info.h"
+
+#include "third_party/blink/renderer/platform/fonts/font.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h"
+
+namespace blink {
+
+unsigned ShapeResultTestInfo::NumberOfRunsForTesting() const {
+ return runs_.size();
+}
+
+bool ShapeResultTestInfo::RunInfoForTesting(unsigned run_index,
+ unsigned& start_index,
+ unsigned& num_characters,
+ unsigned& num_glyphs,
+ hb_script_t& script) const {
+ if (run_index < runs_.size() && runs_[run_index]) {
+ start_index = runs_[run_index]->start_index_;
+ num_characters = runs_[run_index]->num_characters_;
+ num_glyphs = runs_[run_index]->glyph_data_.size();
+ script = runs_[run_index]->script_;
+ return true;
+ }
+ return false;
+}
+
+bool ShapeResultTestInfo::RunInfoForTesting(unsigned run_index,
+ unsigned& start_index,
+ unsigned& num_glyphs,
+ hb_script_t& script) const {
+ unsigned num_characters;
+ return RunInfoForTesting(run_index, start_index, num_characters, num_glyphs,
+ script);
+}
+
+uint16_t ShapeResultTestInfo::GlyphForTesting(unsigned run_index,
+ size_t glyph_index) const {
+ return runs_[run_index]->glyph_data_[glyph_index].glyph;
+}
+
+float ShapeResultTestInfo::AdvanceForTesting(unsigned run_index,
+ size_t glyph_index) const {
+ return runs_[run_index]->glyph_data_[glyph_index].advance;
+}
+
+SimpleFontData* ShapeResultTestInfo::FontDataForTesting(
+ unsigned run_index) const {
+ return runs_[run_index]->font_data_.get();
+}
+
+Vector<unsigned> ShapeResultTestInfo::CharacterIndexesForTesting() const {
+ Vector<unsigned> character_indexes;
+ for (const auto& run : runs_) {
+ for (const auto& glyph_data : run->glyph_data_) {
+ character_indexes.push_back(run->start_index_ +
+ glyph_data.character_index);
+ }
+ }
+ return character_indexes;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h
new file mode 100644
index 00000000000..4ac24e0755d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h
@@ -0,0 +1,71 @@
+// Copyright 2015 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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_TEST_INFO_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_TEST_INFO_H_
+
+#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.h"
+
+#include <hb.h>
+
+namespace blink {
+
+class PLATFORM_EXPORT ShapeResultTestInfo : public ShapeResult {
+ public:
+ unsigned NumberOfRunsForTesting() const;
+ bool RunInfoForTesting(unsigned run_index,
+ unsigned& start_index,
+ unsigned& num_glyphs,
+ hb_script_t&) const;
+ bool RunInfoForTesting(unsigned run_index,
+ unsigned& start_index,
+ unsigned& num_characters,
+ unsigned& num_glyphs,
+ hb_script_t&) const;
+ uint16_t GlyphForTesting(unsigned run_index, size_t glyph_index) const;
+ float AdvanceForTesting(unsigned run_index, size_t glyph_index) const;
+ SimpleFontData* FontDataForTesting(unsigned run_index) const;
+ Vector<unsigned> CharacterIndexesForTesting() const;
+};
+
+class PLATFORM_EXPORT ShapeResultBloberizerTestInfo {
+ public:
+ static const SimpleFontData* PendingRunFontData(
+ const ShapeResultBloberizer& bloberizer) {
+ return bloberizer.pending_font_data_;
+ }
+
+ static CanvasRotationInVertical PendingBlobRotation(
+ const ShapeResultBloberizer& bloberizer) {
+ return bloberizer.pending_canvas_rotation_;
+ }
+
+ static const Vector<Glyph, 1024>& PendingRunGlyphs(
+ const ShapeResultBloberizer& bloberizer) {
+ return bloberizer.pending_glyphs_;
+ }
+
+ static const Vector<float, 1024>& PendingRunOffsets(
+ const ShapeResultBloberizer& bloberizer) {
+ return bloberizer.pending_offsets_;
+ }
+
+ static bool HasPendingRunVerticalOffsets(
+ const ShapeResultBloberizer& bloberizer) {
+ return bloberizer.HasPendingVerticalOffsets();
+ }
+
+ static size_t PendingBlobRunCount(const ShapeResultBloberizer& bloberizer) {
+ return bloberizer.builder_run_count_;
+ }
+
+ static size_t CommittedBlobCount(const ShapeResultBloberizer& bloberizer) {
+ return bloberizer.blobs_.size();
+ }
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_TEST_INFO_H_
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc
new file mode 100644
index 00000000000..6454534bcc3
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc
@@ -0,0 +1,390 @@
+// Copyright 2017 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 "third_party/blink/renderer/platform/fonts/font.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h"
+#include "third_party/blink/renderer/platform/text/text_break_iterator.h"
+
+namespace blink {
+
+ShapingLineBreaker::ShapingLineBreaker(
+ const HarfBuzzShaper* shaper,
+ const Font* font,
+ const ShapeResult* result,
+ const LazyLineBreakIterator* break_iterator,
+ ShapeResultSpacing<String>* spacing,
+ const Hyphenation* hyphenation)
+ : shaper_(shaper),
+ font_(font),
+ result_(result),
+ break_iterator_(break_iterator),
+ spacing_(spacing),
+ hyphenation_(hyphenation),
+ is_soft_hyphen_enabled_(true) {
+ // ShapeResultSpacing is stateful when it has expansions. We may use it in
+ // arbitrary order that it cannot have expansions.
+ DCHECK(!spacing_ || !spacing_->HasExpansion());
+}
+
+namespace {
+
+// ShapingLineBreaker computes using visual positions. This function flips
+// logical advance to visual, or vice versa.
+LayoutUnit FlipRtl(LayoutUnit value, TextDirection direction) {
+ return IsLtr(direction) ? value : -value;
+}
+
+// Snaps a visual position to the line start direction.
+LayoutUnit SnapStart(float value, TextDirection direction) {
+ return IsLtr(direction) ? LayoutUnit::FromFloatFloor(value)
+ : LayoutUnit::FromFloatCeil(value);
+}
+
+// Snaps a visual position to the line end direction.
+LayoutUnit SnapEnd(float value, TextDirection direction) {
+ return IsLtr(direction) ? LayoutUnit::FromFloatCeil(value)
+ : LayoutUnit::FromFloatFloor(value);
+}
+
+bool IsAllSpaces(const String& text, unsigned start, unsigned end) {
+ return StringView(text, start, end - start)
+ .IsAllSpecialCharacters<LazyLineBreakIterator::IsBreakableSpace>();
+}
+
+bool ShouldHyphenate(const String& text, unsigned start, unsigned end) {
+ // Do not hyphenate the last word in a paragraph, except when it's a single
+ // word paragraph.
+ if (IsAllSpaces(text, end, text.length()))
+ return IsAllSpaces(text, 0, start);
+ return true;
+}
+
+} // namespace
+
+inline const String& ShapingLineBreaker::GetText() const {
+ return break_iterator_->GetString();
+}
+
+unsigned ShapingLineBreaker::Hyphenate(unsigned offset,
+ unsigned word_start,
+ unsigned word_end,
+ bool backwards) const {
+ DCHECK(hyphenation_);
+ DCHECK_GT(word_end, word_start);
+ DCHECK_GE(offset, word_start);
+ DCHECK_LE(offset, word_end);
+ unsigned word_len = word_end - word_start;
+ if (word_len <= Hyphenation::kMinimumSuffixLength)
+ return 0;
+
+ const String& text = GetText();
+ if (backwards) {
+ unsigned before_index = offset - word_start;
+ if (before_index <= Hyphenation::kMinimumPrefixLength)
+ return 0;
+ unsigned prefix_length = hyphenation_->LastHyphenLocation(
+ StringView(text, word_start, word_len), before_index);
+ DCHECK(!prefix_length || prefix_length < before_index);
+ return prefix_length;
+ } else {
+ unsigned after_index = offset - word_start;
+ if (word_len <= after_index + Hyphenation::kMinimumSuffixLength)
+ return 0;
+ unsigned prefix_length = hyphenation_->FirstHyphenLocation(
+ StringView(text, word_start, word_len), after_index);
+ DCHECK(!prefix_length || prefix_length > after_index);
+ return prefix_length;
+ }
+}
+
+unsigned ShapingLineBreaker::Hyphenate(unsigned offset,
+ unsigned start,
+ bool backwards,
+ bool* is_hyphenated) const {
+ DCHECK(is_hyphenated && !*is_hyphenated);
+ const String& text = GetText();
+ unsigned word_end = break_iterator_->NextBreakOpportunity(offset);
+ if (word_end == offset) {
+ DCHECK_EQ(offset, break_iterator_->PreviousBreakOpportunity(offset, start));
+ return word_end;
+ }
+ unsigned previous_break_opportunity =
+ break_iterator_->PreviousBreakOpportunity(offset, start);
+ unsigned word_start = previous_break_opportunity;
+ // Skip the leading spaces of this word because the break iterator breaks
+ // before spaces.
+ while (word_start < text.length() &&
+ LazyLineBreakIterator::IsBreakableSpace(text[word_start]))
+ word_start++;
+ if (offset >= word_start &&
+ ShouldHyphenate(text, previous_break_opportunity, word_end)) {
+ unsigned prefix_length = Hyphenate(offset, word_start, word_end, backwards);
+ if (prefix_length) {
+ *is_hyphenated = true;
+ return word_start + prefix_length;
+ }
+ }
+ return backwards ? previous_break_opportunity : word_end;
+}
+
+unsigned ShapingLineBreaker::PreviousBreakOpportunity(
+ unsigned offset,
+ unsigned start,
+ bool* is_hyphenated) const {
+ DCHECK(is_hyphenated && !*is_hyphenated);
+ if (UNLIKELY(!IsSoftHyphenEnabled())) {
+ const String& text = GetText();
+ for (;; offset--) {
+ offset = break_iterator_->PreviousBreakOpportunity(offset, start);
+ if (offset <= start || offset >= text.length() ||
+ text[offset - 1] != kSoftHyphenCharacter)
+ return offset;
+ }
+ }
+
+ if (UNLIKELY(hyphenation_))
+ return Hyphenate(offset, start, true, is_hyphenated);
+
+ return break_iterator_->PreviousBreakOpportunity(offset, start);
+}
+
+unsigned ShapingLineBreaker::NextBreakOpportunity(unsigned offset,
+ unsigned start,
+ bool* is_hyphenated) const {
+ DCHECK(is_hyphenated && !*is_hyphenated);
+ if (UNLIKELY(!IsSoftHyphenEnabled())) {
+ const String& text = GetText();
+ for (;; offset++) {
+ offset = break_iterator_->NextBreakOpportunity(offset);
+ if (offset >= text.length() || text[offset - 1] != kSoftHyphenCharacter)
+ return offset;
+ }
+ }
+
+ if (UNLIKELY(hyphenation_))
+ return Hyphenate(offset, start, false, is_hyphenated);
+
+ return break_iterator_->NextBreakOpportunity(offset);
+}
+
+inline scoped_refptr<ShapeResult> ShapingLineBreaker::Shape(TextDirection direction,
+ unsigned start,
+ unsigned end) {
+ if (!spacing_ || !spacing_->HasSpacing())
+ return shaper_->Shape(font_, direction, start, end);
+
+ scoped_refptr<ShapeResult> result = shaper_->Shape(font_, direction, start, end);
+ result->ApplySpacing(*spacing_);
+ return result;
+}
+
+// Shapes a line of text by finding a valid and appropriate break opportunity
+// based on the shaping results for the entire paragraph. Re-shapes the start
+// and end of the line as needed.
+//
+// Definitions:
+// Candidate break opportunity: Ideal point to break, disregarding line
+// breaking rules. May be in the middle of a word
+// or inside a ligature.
+// Valid break opportunity: A point where a break is allowed according to
+// the relevant breaking rules.
+// Safe-to-break: A point where a break may occur without
+// affecting the rendering or metrics of the
+// text. Breaking at safe-to-break point does not
+// require reshaping.
+//
+// For example:
+// Given the string "Line breaking example", an available space of 100px and a
+// mono-space font where each glyph is 10px wide.
+//
+// Line breaking example
+// | |
+// 0 100px
+//
+// The candidate (or ideal) break opportunity would be at an offset of 10 as
+// the break would happen at exactly 100px in that case.
+// The previous valid break opportunity though is at an offset of 5.
+// If we further assume that the font kerns with space then even though it's a
+// valid break opportunity reshaping is required as the combined width of the
+// two segments "Line " and "breaking" may be different from "Line breaking".
+scoped_refptr<ShapeResult> ShapingLineBreaker::ShapeLine(
+ unsigned start,
+ LayoutUnit available_space,
+ bool start_should_be_safe,
+ ShapingLineBreaker::Result* result_out) {
+ DCHECK_GE(available_space, LayoutUnit(0));
+ unsigned range_start = result_->StartIndexForResult();
+ unsigned range_end = result_->EndIndexForResult();
+ DCHECK_GE(start, range_start);
+ DCHECK_LT(start, range_end);
+ result_out->is_hyphenated = false;
+ const String& text = GetText();
+
+ // The start position in the original shape results.
+ float start_position_float = result_->PositionForOffset(start - range_start);
+ TextDirection direction = result_->Direction();
+ LayoutUnit start_position = SnapStart(start_position_float, direction);
+
+ // Find a candidate break opportunity by identifying the last offset before
+ // exceeding the available space and the determine the closest valid break
+ // preceding the candidate.
+ LayoutUnit end_position = SnapEnd(start_position_float, direction) +
+ FlipRtl(available_space, direction);
+ DCHECK_GE(FlipRtl(end_position - start_position, direction), LayoutUnit(0));
+ unsigned candidate_break =
+ result_->OffsetForPosition(end_position, false) + range_start;
+
+ unsigned first_safe =
+ start_should_be_safe ? result_->NextSafeToBreakOffset(start) : start;
+ DCHECK_GE(first_safe, start);
+ if (candidate_break >= range_end) {
+ // The |result_| does not have glyphs to fill the available space,
+ // and thus unable to compute. Return the result up to range_end.
+ DCHECK_EQ(candidate_break, range_end);
+ result_out->break_offset = range_end;
+ return ShapeToEnd(start, first_safe, range_end);
+ }
+
+ // candidate_break should be >= start, but rounding errors can chime in when
+ // comparing floats. See ShapeLineZeroAvailableWidth on Linux/Mac.
+ candidate_break = std::max(candidate_break, start);
+
+ unsigned break_opportunity = PreviousBreakOpportunity(
+ candidate_break, start, &result_out->is_hyphenated);
+ if (break_opportunity <= start) {
+ break_opportunity =
+ NextBreakOpportunity(std::max(candidate_break, start + 1), start,
+ &result_out->is_hyphenated);
+ // |range_end| may not be a break opportunity, but this function cannot
+ // measure beyond it.
+ if (break_opportunity >= range_end) {
+ result_out->break_offset = range_end;
+ return ShapeToEnd(start, first_safe, range_end);
+ }
+ }
+ DCHECK_GT(break_opportunity, start);
+
+ // If the start offset is not at a safe-to-break boundary the content between
+ // the start and the next safe-to-break boundary needs to be reshaped and the
+ // available space adjusted to take the reshaping into account.
+ scoped_refptr<ShapeResult> line_start_result;
+ if (first_safe != start) {
+ if (first_safe >= break_opportunity) {
+ // There is no safe-to-break, reshape the whole range.
+ result_out->break_offset = break_opportunity;
+ return Shape(direction, start, break_opportunity);
+ }
+ LayoutUnit original_width =
+ FlipRtl(SnapEnd(result_->PositionForOffset(first_safe - range_start),
+ direction) -
+ start_position,
+ direction);
+ line_start_result = Shape(direction, start, first_safe);
+ available_space += line_start_result->SnappedWidth() - original_width;
+ }
+
+ scoped_refptr<ShapeResult> line_end_result;
+ unsigned last_safe = break_opportunity;
+ while (break_opportunity > start) {
+ // If the previous valid break opportunity is not at a safe-to-break
+ // boundary reshape between the safe-to-break offset and the valid break
+ // offset. If the resulting width exceeds the available space the
+ // preceding boundary is tried until the available space is sufficient.
+ unsigned previous_safe =
+ std::max(result_->PreviousSafeToBreakOffset(break_opportunity), start);
+ DCHECK_LE(previous_safe, break_opportunity);
+ if (previous_safe != break_opportunity) {
+ LayoutUnit safe_position = SnapStart(
+ result_->PositionForOffset(previous_safe - range_start), direction);
+ while (break_opportunity > previous_safe && previous_safe >= start) {
+ DCHECK_LE(break_opportunity, range_end);
+ line_end_result = Shape(direction, previous_safe, break_opportunity);
+ if (line_end_result->SnappedWidth() <=
+ FlipRtl(end_position - safe_position, direction))
+ break;
+ // Doesn't fit after the reshape. Try previous break opportunity, or
+ // overflow if there were none.
+ bool is_previous_break_opportunity_hyphenated = false;
+ unsigned previous_break_opportunity =
+ PreviousBreakOpportunity(break_opportunity - 1, start,
+ &is_previous_break_opportunity_hyphenated);
+ if (previous_break_opportunity <= start)
+ break;
+ break_opportunity = previous_break_opportunity;
+ result_out->is_hyphenated = is_previous_break_opportunity_hyphenated;
+ line_end_result = nullptr;
+ }
+ }
+
+ if (break_opportunity > start) {
+ last_safe = previous_safe;
+ break;
+ }
+
+ // No suitable break opportunity, not exceeding the available space,
+ // found. Choose the next valid one even though it will overflow.
+ break_opportunity = NextBreakOpportunity(candidate_break, start,
+ &result_out->is_hyphenated);
+ // |range_end| may not be a break opportunity, but this function cannot
+ // measure beyond it.
+ break_opportunity = std::min(break_opportunity, range_end);
+ }
+
+ // Create shape results for the line by copying from the re-shaped result (if
+ // reshaping was needed) and the original shape results.
+ scoped_refptr<ShapeResult> line_result = ShapeResult::Create(font_, 0, direction);
+ unsigned max_length = std::numeric_limits<unsigned>::max();
+ if (line_start_result)
+ line_start_result->CopyRange(0, max_length, line_result.get());
+ if (last_safe > first_safe)
+ result_->CopyRange(first_safe, last_safe, line_result.get());
+ if (line_end_result)
+ line_end_result->CopyRange(last_safe, max_length, line_result.get());
+
+ DCHECK_GT(break_opportunity, start);
+ // TODO(layout-dev): This hits on Mac and Mac only for a number of tests in
+ // virtual/layout_ng/external/wpt/css/CSS2/floats-clear/.
+ // DCHECK_EQ(std::min(break_opportunity, range_end) - start,
+ // line_result->NumCharacters());
+
+ result_out->break_offset = break_opportunity;
+ if (!result_out->is_hyphenated &&
+ text[break_opportunity - 1] == kSoftHyphenCharacter)
+ result_out->is_hyphenated = true;
+ return line_result;
+}
+
+// Shape from the specified offset to the end of the ShapeResult.
+// If |start| is safe-to-break, this copies the subset of the result.
+scoped_refptr<ShapeResult> ShapingLineBreaker::ShapeToEnd(unsigned start,
+ unsigned first_safe,
+ unsigned range_end) {
+ DCHECK_GE(start, result_->StartIndexForResult());
+ DCHECK_LT(start, range_end);
+ DCHECK_GE(first_safe, start);
+ DCHECK_EQ(range_end, result_->EndIndexForResult());
+
+ // If |start| is safe-to-break, no reshape is needed.
+ if (first_safe == start) {
+ return result_->SubRange(start, range_end);
+ }
+
+ // If no safe-to-break offset is found in range, reshape the entire range.
+ TextDirection direction = result_->Direction();
+ if (first_safe >= range_end) {
+ return Shape(direction, start, range_end);
+ }
+
+ // Otherwise reshape to |first_safe|, then copy the rest.
+ scoped_refptr<ShapeResult> line_result = Shape(direction, start, first_safe);
+ result_->CopyRange(first_safe, range_end, line_result.get());
+ return line_result;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.h b/chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.h
new file mode 100644
index 00000000000..361527543ed
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.h
@@ -0,0 +1,116 @@
+// Copyright 2017 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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPING_LINE_BREAKER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPING_LINE_BREAKER_H_
+
+#include "third_party/blink/renderer/platform/layout_unit.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/text/text_direction.h"
+#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
+
+namespace blink {
+
+class Font;
+class ShapeResult;
+class HarfBuzzShaper;
+class Hyphenation;
+class LazyLineBreakIterator;
+enum class LineBreakType;
+template <typename TextContainerType>
+class ShapeResultSpacing;
+
+// Shapes a line of text by finding the ideal break position as indicated by the
+// available space and the shape results for the entire paragraph. Once an ideal
+// break position has been found the text is scanned backwards until a valid and
+// and appropriate break opportunity is identified. Unless the break opportunity
+// is at a safe-to-break boundary (as identified by HarfBuzz) the beginning and/
+// or end of the line is reshaped to account for differences caused by breaking.
+//
+// This allows for significantly faster and more efficient line breaking by only
+// reshaping when absolutely necessarily and by only evaluating likely candidate
+// break opportunities instead of measuring and evaluating all possible options.
+class PLATFORM_EXPORT ShapingLineBreaker final {
+ STACK_ALLOCATED();
+
+ public:
+ ShapingLineBreaker(const HarfBuzzShaper*,
+ const Font*,
+ const ShapeResult*,
+ const LazyLineBreakIterator*,
+ ShapeResultSpacing<String>* = nullptr,
+ const Hyphenation* = nullptr);
+ ~ShapingLineBreaker() = default;
+
+ // Represents details of the result of |ShapeLine()|.
+ struct Result {
+ STACK_ALLOCATED();
+
+ // Indicates the resulting break offset.
+ unsigned break_offset;
+
+ // True if the break is hyphenated, either by automatic hyphenation or
+ // soft-hyphen characters.
+ // The hyphen glyph is not included in the |ShapeResult|, and that appending
+ // a hyphen glyph may overflow the specified available space.
+ bool is_hyphenated;
+ };
+
+ // Shapes a line of text by finding a valid and appropriate break opportunity
+ // based on the shaping results for the entire paragraph.
+ // |start_should_be_safe| is true for the beginning of each wrapped line, but
+ // is false for subsequent ShapeResults.
+ scoped_refptr<ShapeResult> ShapeLine(unsigned start_offset,
+ LayoutUnit available_space,
+ bool start_should_be_safe,
+ Result* result_out);
+ scoped_refptr<ShapeResult> ShapeLine(unsigned start_offset,
+ LayoutUnit available_space,
+ Result* result_out) {
+ return ShapeLine(start_offset, available_space, true, result_out);
+ }
+
+ // Disable breaking at soft hyphens (U+00AD).
+ bool IsSoftHyphenEnabled() const { return is_soft_hyphen_enabled_; }
+ void DisableSoftHyphen() { is_soft_hyphen_enabled_ = false; }
+
+ private:
+ const String& GetText() const;
+
+ unsigned PreviousBreakOpportunity(unsigned offset,
+ unsigned start,
+ bool* is_hyphenated) const;
+ unsigned NextBreakOpportunity(unsigned offset,
+ unsigned start,
+ bool* is_hyphenated) const;
+ unsigned Hyphenate(unsigned offset,
+ unsigned start,
+ bool backwards,
+ bool* is_hyphenated) const;
+ unsigned Hyphenate(unsigned offset,
+ unsigned word_start,
+ unsigned word_end,
+ bool backwards) const;
+
+ scoped_refptr<ShapeResult> Shape(TextDirection, unsigned start, unsigned end);
+ scoped_refptr<ShapeResult> ShapeToEnd(unsigned start,
+ unsigned first_safe,
+ unsigned range_end);
+
+ const HarfBuzzShaper* shaper_;
+ const Font* font_;
+ const ShapeResult* result_;
+ const LazyLineBreakIterator* break_iterator_;
+ // TODO(kojii): ShapeResultSpacing is not const because it's stateful when it
+ // has expansions. Split spacing and expansions to make this const.
+ ShapeResultSpacing<String>* spacing_;
+ const Hyphenation* hyphenation_;
+ bool is_soft_hyphen_enabled_;
+
+ friend class ShapingLineBreakerTest;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPING_LINE_BREAKER_H_
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker_test.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker_test.cc
new file mode 100644
index 00000000000..24daffd79dc
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker_test.cc
@@ -0,0 +1,344 @@
+// Copyright 2017 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/gmock/include/gmock/gmock.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/shape_result_inline_headers.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h"
+#include "third_party/blink/renderer/platform/text/text_break_iterator.h"
+#include "third_party/blink/renderer/platform/text/text_run.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+namespace {
+
+scoped_refptr<ShapeResult> ShapeLine(ShapingLineBreaker* breaker,
+ unsigned start_offset,
+ LayoutUnit available_space,
+ unsigned* break_offset) {
+ ShapingLineBreaker::Result result;
+ scoped_refptr<ShapeResult> shape_result =
+ breaker->ShapeLine(start_offset, available_space, &result);
+ *break_offset = result.break_offset;
+ return shape_result;
+}
+
+} // namespace
+
+class ShapingLineBreakerTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ font_description.SetComputedSize(12.0);
+ font = Font(font_description);
+ font.Update(nullptr);
+ }
+
+ void TearDown() override {}
+
+ // Compute all break positions by |NextBreakOpportunity|.
+ Vector<unsigned> BreakPositionsByNext(const ShapingLineBreaker& breaker,
+ const String& string) {
+ Vector<unsigned> break_positions;
+ for (unsigned i = 0; i <= string.length(); i++) {
+ bool is_hyphenated = false;
+ unsigned next = breaker.NextBreakOpportunity(i, 0, &is_hyphenated);
+ if (break_positions.IsEmpty() || break_positions.back() != next)
+ break_positions.push_back(next);
+ }
+ return break_positions;
+ }
+
+ // Compute all break positions by |PreviousBreakOpportunity|.
+ Vector<unsigned> BreakPositionsByPrevious(const ShapingLineBreaker& breaker,
+ const String& string) {
+ Vector<unsigned> break_positions;
+ for (unsigned i = string.length(); i; i--) {
+ bool is_hyphenated = false;
+ unsigned previous =
+ breaker.PreviousBreakOpportunity(i, 0, &is_hyphenated);
+ if (previous &&
+ (break_positions.IsEmpty() || break_positions.back() != previous))
+ break_positions.push_back(previous);
+ }
+ break_positions.Reverse();
+ return break_positions;
+ }
+
+ FontCachePurgePreventer font_cache_purge_preventer;
+ FontDescription font_description;
+ Font font;
+ unsigned start_index = 0;
+ unsigned num_glyphs = 0;
+ hb_script_t script = HB_SCRIPT_INVALID;
+};
+
+TEST_F(ShapingLineBreakerTest, ShapeLineLatin) {
+ String string = To16Bit(
+ "Test run with multiple words and breaking "
+ "opportunities.",
+ 56);
+ LazyLineBreakIterator break_iterator(string, "en-US", LineBreakType::kNormal);
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(string.Characters16(), 56);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ // "Test run with multiple"
+ scoped_refptr<ShapeResult> first4 = shaper.Shape(&font, direction, 0, 22);
+ ASSERT_LT(first4->SnappedWidth(), result->SnappedWidth());
+
+ // "Test run with"
+ scoped_refptr<ShapeResult> first3 = shaper.Shape(&font, direction, 0, 13);
+ ASSERT_LT(first3->SnappedWidth(), first4->SnappedWidth());
+
+ // "Test run"
+ scoped_refptr<ShapeResult> first2 = shaper.Shape(&font, direction, 0, 8);
+ ASSERT_LT(first2->SnappedWidth(), first3->SnappedWidth());
+
+ // "Test"
+ scoped_refptr<ShapeResult> first1 = shaper.Shape(&font, direction, 0, 4);
+ ASSERT_LT(first1->SnappedWidth(), first2->SnappedWidth());
+
+ ShapingLineBreaker breaker(&shaper, &font, result.get(), &break_iterator);
+ scoped_refptr<ShapeResult> line;
+ unsigned break_offset = 0;
+
+ // Test the case where the entire string fits.
+ line = ShapeLine(&breaker, 0, result->SnappedWidth(), &break_offset);
+ EXPECT_EQ(56u, break_offset); // After the end of the string.
+ EXPECT_EQ(result->SnappedWidth(), line->SnappedWidth());
+
+ // Test cases where we break between words.
+ line = ShapeLine(&breaker, 0, first4->SnappedWidth(), &break_offset);
+ EXPECT_EQ(22u, break_offset); // Between "multiple" and " words"
+ EXPECT_EQ(first4->SnappedWidth(), line->SnappedWidth());
+
+ line = ShapeLine(&breaker, 0, first4->SnappedWidth() + 10, &break_offset);
+ EXPECT_EQ(22u, break_offset); // Between "multiple" and " words"
+ EXPECT_EQ(first4->SnappedWidth(), line->SnappedWidth());
+
+ line = ShapeLine(&breaker, 0, first4->SnappedWidth() - 1, &break_offset);
+ EXPECT_EQ(13u, break_offset); // Between "width" and "multiple"
+ EXPECT_EQ(first3->SnappedWidth(), line->SnappedWidth());
+
+ line = ShapeLine(&breaker, 0, first3->SnappedWidth(), &break_offset);
+ EXPECT_EQ(13u, break_offset); // Between "width" and "multiple"
+ EXPECT_EQ(first3->SnappedWidth(), line->SnappedWidth());
+
+ line = ShapeLine(&breaker, 0, first3->SnappedWidth() - 1, &break_offset);
+ EXPECT_EQ(8u, break_offset); // Between "run" and "width"
+ EXPECT_EQ(first2->SnappedWidth(), line->SnappedWidth());
+
+ line = ShapeLine(&breaker, 0, first2->SnappedWidth(), &break_offset);
+ EXPECT_EQ(8u, break_offset); // Between "run" and "width"
+ EXPECT_EQ(first2->SnappedWidth(), line->SnappedWidth());
+
+ line = ShapeLine(&breaker, 0, first2->SnappedWidth() - 1, &break_offset);
+ EXPECT_EQ(4u, break_offset); // Between "Test" and "run"
+ EXPECT_EQ(first1->SnappedWidth(), line->SnappedWidth());
+
+ line = ShapeLine(&breaker, 0, first1->SnappedWidth(), &break_offset);
+ EXPECT_EQ(4u, break_offset); // Between "Test" and "run"
+ EXPECT_EQ(first1->SnappedWidth(), line->SnappedWidth());
+
+ // Test the case where we cannot break earlier.
+ line = ShapeLine(&breaker, 0, first1->SnappedWidth() - 1, &break_offset);
+ EXPECT_EQ(4u, break_offset); // Between "Test" and "run"
+ EXPECT_EQ(first1->SnappedWidth(), line->SnappedWidth());
+}
+
+TEST_F(ShapingLineBreakerTest, ShapeLineLatinMultiLine) {
+ String string = To16Bit("Line breaking test case.", 24);
+ LazyLineBreakIterator break_iterator(string, "en-US", LineBreakType::kNormal);
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(string.Characters16(), 24);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+ scoped_refptr<ShapeResult> first = shaper.Shape(&font, direction, 0, 4);
+ scoped_refptr<ShapeResult> mid_third = shaper.Shape(&font, direction, 0, 16);
+
+ ShapingLineBreaker breaker(&shaper, &font, result.get(), &break_iterator);
+ unsigned break_offset = 0;
+
+ ShapeLine(&breaker, 0, result->SnappedWidth() - 1, &break_offset);
+ EXPECT_EQ(18u, break_offset);
+
+ ShapeLine(&breaker, 0, first->SnappedWidth(), &break_offset);
+ EXPECT_EQ(4u, break_offset);
+
+ ShapeLine(&breaker, 0, mid_third->SnappedWidth(), &break_offset);
+ EXPECT_EQ(13u, break_offset);
+
+ ShapeLine(&breaker, 13u, mid_third->SnappedWidth(), &break_offset);
+ EXPECT_EQ(24u, break_offset);
+}
+
+TEST_F(ShapingLineBreakerTest, ShapeLineLatinBreakAll) {
+ String string = To16Bit("Testing break type-break all.", 29);
+ LazyLineBreakIterator break_iterator(string, "en-US",
+ LineBreakType::kBreakAll);
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(string.Characters16(), 29);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+ scoped_refptr<ShapeResult> midpoint = shaper.Shape(&font, direction, 0, 16);
+
+ ShapingLineBreaker breaker(&shaper, &font, result.get(), &break_iterator);
+ scoped_refptr<ShapeResult> line;
+ unsigned break_offset = 0;
+
+ line = ShapeLine(&breaker, 0, midpoint->SnappedWidth(), &break_offset);
+ EXPECT_EQ(16u, break_offset);
+ EXPECT_EQ(midpoint->SnappedWidth(), line->SnappedWidth());
+
+ line = ShapeLine(&breaker, 16u, result->SnappedWidth(), &break_offset);
+ EXPECT_EQ(29u, break_offset);
+ EXPECT_GE(midpoint->SnappedWidth(), line->SnappedWidth());
+}
+
+TEST_F(ShapingLineBreakerTest, ShapeLineZeroAvailableWidth) {
+ String string(u"Testing overflow line break.");
+ LazyLineBreakIterator break_iterator(string, "en-US", LineBreakType::kNormal);
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+
+ ShapingLineBreaker breaker(&shaper, &font, result.get(), &break_iterator);
+ scoped_refptr<ShapeResult> line;
+ unsigned break_offset = 0;
+ LayoutUnit zero(0);
+
+ line = ShapeLine(&breaker, 0, zero, &break_offset);
+ EXPECT_EQ(7u, break_offset);
+
+ line = ShapeLine(&breaker, 7, zero, &break_offset);
+ EXPECT_EQ(16u, break_offset);
+
+ line = ShapeLine(&breaker, 16, zero, &break_offset);
+ EXPECT_EQ(21u, break_offset);
+
+ line = ShapeLine(&breaker, 21, zero, &break_offset);
+ EXPECT_EQ(28u, break_offset);
+}
+
+// Hits DCHECK at end of ShapingLineBreaker::ShapeLine, not clear if the test
+// is correct. Disabling for now.
+TEST_F(ShapingLineBreakerTest, DISABLED_ShapeLineArabicThaiHanLatin) {
+ UChar mixed_string[] = {0x628, 0x20, 0x64A, 0x629, 0x20,
+ 0xE20, 0x65E5, 0x62, 0};
+ LazyLineBreakIterator break_iterator(mixed_string, "ar_AE",
+ LineBreakType::kNormal);
+ TextDirection direction = TextDirection::kRtl;
+
+ HarfBuzzShaper shaper(mixed_string, 8);
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction);
+ scoped_refptr<ShapeResult> words[] = {shaper.Shape(&font, direction, 0, 1),
+ shaper.Shape(&font, direction, 2, 4),
+ shaper.Shape(&font, direction, 5, 6),
+ shaper.Shape(&font, direction, 6, 7),
+ shaper.Shape(&font, direction, 7, 8)};
+ const auto& longest_word = std::max_element(
+ std::begin(words), std::end(words),
+ [](const scoped_refptr<ShapeResult>& a, const scoped_refptr<ShapeResult>& b) {
+ return a->SnappedWidth() < b->SnappedWidth();
+ });
+ LayoutUnit longest_word_width = (*longest_word)->SnappedWidth();
+
+ ShapingLineBreaker breaker(&shaper, &font, result.get(), &break_iterator);
+ scoped_refptr<ShapeResult> line;
+ unsigned break_offset = 0;
+
+ ShapeLine(&breaker, 0, longest_word_width, &break_offset);
+ EXPECT_EQ(1u, break_offset);
+
+ ShapeLine(&breaker, 1, longest_word_width, &break_offset);
+ EXPECT_EQ(4u, break_offset);
+
+ ShapeLine(&breaker, 4, longest_word_width, &break_offset);
+ EXPECT_EQ(6u, break_offset);
+
+ ShapeLine(&breaker, 6, longest_word_width, &break_offset);
+ EXPECT_EQ(7u, break_offset);
+
+ ShapeLine(&breaker, 7, longest_word_width, &break_offset);
+ EXPECT_EQ(8u, break_offset);
+}
+
+TEST_F(ShapingLineBreakerTest, ShapeLineRangeEndMidWord) {
+ String string(u"Mid word");
+ LazyLineBreakIterator break_iterator(string, "en-US", LineBreakType::kNormal);
+ TextDirection direction = TextDirection::kLtr;
+
+ HarfBuzzShaper shaper(string.Characters16(), string.length());
+ scoped_refptr<ShapeResult> result = shaper.Shape(&font, direction, 0, 2);
+
+ ShapingLineBreaker breaker(&shaper, &font, result.get(), &break_iterator);
+ scoped_refptr<ShapeResult> line;
+ unsigned break_offset = 0;
+
+ line = ShapeLine(&breaker, 0, LayoutUnit::Max(), &break_offset);
+ EXPECT_EQ(2u, break_offset);
+ EXPECT_EQ(result->Width(), line->Width());
+}
+
+struct BreakOpportunityTestData {
+ const char16_t* string;
+ Vector<unsigned> break_positions;
+ Vector<unsigned> break_positions_with_soft_hyphen_disabled;
+};
+
+class BreakOpportunityTest
+ : public ShapingLineBreakerTest,
+ public testing::WithParamInterface<BreakOpportunityTestData> {};
+
+INSTANTIATE_TEST_CASE_P(
+ ShapingLineBreakerTest,
+ BreakOpportunityTest,
+ testing::Values(BreakOpportunityTestData{u"x y z", {1, 3, 5}},
+ BreakOpportunityTestData{u"y\xADz", {2, 3}, {3}},
+ BreakOpportunityTestData{u"\xADz", {1, 2}, {2}},
+ BreakOpportunityTestData{u"y\xAD", {2}, {2}},
+ BreakOpportunityTestData{u"\xAD\xADz", {2, 3}, {3}}));
+
+TEST_P(BreakOpportunityTest, Next) {
+ const BreakOpportunityTestData& data = GetParam();
+ String string(data.string);
+ LazyLineBreakIterator break_iterator(string);
+ ShapingLineBreaker breaker(nullptr, &font, nullptr, &break_iterator);
+ EXPECT_THAT(BreakPositionsByNext(breaker, string),
+ testing::ElementsAreArray(data.break_positions));
+
+ if (!data.break_positions_with_soft_hyphen_disabled.IsEmpty()) {
+ breaker.DisableSoftHyphen();
+ EXPECT_THAT(BreakPositionsByNext(breaker, string),
+ testing::ElementsAreArray(
+ data.break_positions_with_soft_hyphen_disabled));
+ }
+}
+
+TEST_P(BreakOpportunityTest, Previous) {
+ const BreakOpportunityTestData& data = GetParam();
+ String string(data.string);
+ LazyLineBreakIterator break_iterator(string);
+ ShapingLineBreaker breaker(nullptr, &font, nullptr, &break_iterator);
+ EXPECT_THAT(BreakPositionsByPrevious(breaker, string),
+ testing::ElementsAreArray(data.break_positions));
+
+ if (!data.break_positions_with_soft_hyphen_disabled.IsEmpty()) {
+ breaker.DisableSoftHyphen();
+ EXPECT_THAT(BreakPositionsByPrevious(breaker, string),
+ testing::ElementsAreArray(
+ data.break_positions_with_soft_hyphen_disabled));
+ }
+}
+
+} // namespace blink