| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h" |
| |
| #include <iterator> |
| #include "base/containers/adapters.h" |
| #include "build/build_config.h" |
| #include "third_party/blink/renderer/platform/fonts/font.h" |
| #include "third_party/blink/renderer/platform/fonts/shaping/glyph_bounds_accumulator.h" |
| #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h" |
| |
| namespace blink { |
| |
| // Note: We allocate |RunInfoPart| in flexible array in |ShapeResultView|. |
| struct ShapeResultView::RunInfoPart { |
| public: |
| RunInfoPart(scoped_refptr<const ShapeResult::RunInfo> run, |
| ShapeResult::RunInfo::GlyphDataRange range, |
| unsigned start_index, |
| unsigned offset, |
| unsigned num_characters, |
| float width) |
| : run_(run), |
| range_(range), |
| start_index_(start_index), |
| offset_(offset), |
| num_characters_(num_characters), |
| width_(width) {} |
| |
| using const_iterator = const HarfBuzzRunGlyphData*; |
| const_iterator begin() const { return range_.begin; } |
| const_iterator end() const { return range_.end; } |
| using const_reverse_iterator = std::reverse_iterator<const_iterator>; |
| const_reverse_iterator rbegin() const { |
| return const_reverse_iterator(end()); |
| } |
| const_reverse_iterator rend() const { |
| return const_reverse_iterator(begin()); |
| } |
| const HarfBuzzRunGlyphData& GlyphAt(unsigned index) const { |
| return *(range_.begin + index); |
| } |
| template <bool has_non_zero_glyph_offsets> |
| ShapeResult::RunInfo::GlyphOffsetArray::iterator<has_non_zero_glyph_offsets> |
| GetGlyphOffsets() const { |
| return ShapeResult::RunInfo::GlyphOffsetArray::iterator< |
| has_non_zero_glyph_offsets>(range_); |
| } |
| bool HasGlyphOffsets() const { return range_.offsets; } |
| // The end character index of |this| without considering offsets in |
| // |ShapeResultView|. This is analogous to: |
| // GlyphAt(IsRtl() ? -1 : NumGlyphs()).character_index |
| // if such |HarfBuzzRunGlyphData| is available. |
| unsigned CharacterIndexOfEndGlyph() const { |
| return num_characters_ + offset_; |
| } |
| |
| bool IsLtr() const { return run_->IsLtr(); } |
| bool IsRtl() const { return run_->IsRtl(); } |
| bool IsHorizontal() const { return run_->IsHorizontal(); } |
| unsigned NumCharacters() const { return num_characters_; } |
| unsigned NumGlyphs() const { return range_.end - range_.begin; } |
| float Width() const { return width_; } |
| |
| unsigned PreviousSafeToBreakOffset(unsigned offset) const; |
| |
| // Common signatures with RunInfo, to templatize algorithms. |
| const ShapeResult::RunInfo* GetRunInfo() const { return run_.get(); } |
| const ShapeResult::RunInfo::GlyphDataRange& GetGlyphDataRange() const { |
| return range_; |
| } |
| ShapeResult::RunInfo::GlyphDataRange FindGlyphDataRange( |
| unsigned start_character_index, |
| unsigned end_character_index) const { |
| return GetGlyphDataRange().FindGlyphDataRange( |
| IsRtl(), start_character_index, end_character_index); |
| } |
| unsigned OffsetToRunStartIndex() const { return offset_; } |
| |
| // The helper function for implementing |CreateViewsForResult()| for |
| // handling iterating over |Vector<scoped_refptr<RunInfo>>| and |
| // |base::span<RunInfoPart>|. |
| const RunInfoPart* get() const { return this; } |
| |
| void ExpandRangeToIncludePartialGlyphs(unsigned offset, |
| unsigned* from, |
| unsigned* to) const { |
| DCHECK_GE(offset + start_index_, offset_); |
| unsigned part_offset = offset + start_index_ - offset_; |
| run_->ExpandRangeToIncludePartialGlyphs( |
| part_offset, reinterpret_cast<int*>(from), reinterpret_cast<int*>(to)); |
| } |
| |
| scoped_refptr<const ShapeResult::RunInfo> run_; |
| ShapeResult::RunInfo::GlyphDataRange range_; |
| |
| // Start index for partial run, adjusted to ensure that runs are continuous. |
| unsigned start_index_; |
| |
| // Offset relative to start index for the original run. |
| unsigned offset_; |
| |
| unsigned num_characters_; |
| float width_; |
| }; |
| |
| unsigned ShapeResultView::RunInfoPart::PreviousSafeToBreakOffset( |
| unsigned offset) const { |
| if (offset >= NumCharacters()) |
| return NumCharacters(); |
| offset += offset_; |
| if (IsLtr()) { |
| for (const auto& glyph : base::Reversed(*this)) { |
| if (glyph.safe_to_break_before && glyph.character_index <= offset) |
| return glyph.character_index - offset_; |
| } |
| } else { |
| for (const auto& glyph : *this) { |
| if (glyph.safe_to_break_before && glyph.character_index <= offset) |
| return glyph.character_index - offset_; |
| } |
| } |
| |
| // Next safe break is at the start of the run. |
| return 0; |
| } |
| |
| // The offset to add to |HarfBuzzRunGlyphData.character_index| to compute the |
| // character index of the source string. |
| unsigned ShapeResultView::CharacterIndexOffsetForGlyphData( |
| const RunInfoPart& part) const { |
| return part.start_index_ + char_index_offset_ - part.offset_; |
| } |
| |
| template <class ShapeResultType> |
| ShapeResultView::ShapeResultView(const ShapeResultType* other) |
| : primary_font_(other->primary_font_), |
| start_index_(0), |
| num_characters_(0), |
| num_glyphs_(0), |
| direction_(other->direction_), |
| has_vertical_offsets_(other->has_vertical_offsets_), |
| width_(0) {} |
| |
| ShapeResultView::~ShapeResultView() { |
| for (auto& part : Parts()) |
| part.~RunInfoPart(); |
| } |
| |
| scoped_refptr<ShapeResult> ShapeResultView::CreateShapeResult() const { |
| ShapeResult* new_result = |
| new ShapeResult(primary_font_, start_index_ + char_index_offset_, |
| num_characters_, Direction()); |
| new_result->runs_.ReserveCapacity(num_parts_); |
| for (const auto& part : RunsOrParts()) { |
| auto new_run = ShapeResult::RunInfo::Create( |
| part.run_->font_data_.get(), part.run_->direction_, |
| part.run_->canvas_rotation_, part.run_->script_, part.start_index_, |
| part.NumGlyphs(), part.num_characters_); |
| new_run->glyph_data_.CopyFromRange(part.range_); |
| for (HarfBuzzRunGlyphData& glyph_data : new_run->glyph_data_) { |
| glyph_data.character_index -= part.offset_; |
| } |
| |
| new_run->start_index_ += char_index_offset_; |
| new_run->width_ = part.width_; |
| new_run->num_characters_ = part.num_characters_; |
| new_result->runs_.push_back(std::move(new_run)); |
| } |
| |
| new_result->num_glyphs_ = num_glyphs_; |
| new_result->has_vertical_offsets_ = has_vertical_offsets_; |
| new_result->width_ = width_; |
| |
| return base::AdoptRef(new_result); |
| } |
| |
| template <class ShapeResultType> |
| void ShapeResultView::CreateViewsForResult(const ShapeResultType* other, |
| unsigned start_index, |
| unsigned end_index) { |
| // Compute the diff of index and the number of characters from the source |
| // ShapeResult and given offsets, because computing them from runs/parts can |
| // be inaccurate when all characters in a run/part are missing. |
| int index_diff = start_index_ + num_characters_ - |
| std::max(start_index, other->StartIndex()); |
| num_characters_ += std::min(end_index, other->EndIndex()) - |
| std::max(start_index, other->StartIndex()); |
| |
| RunInfoPart* part = Parts().data() + num_parts_; |
| for (const auto& run_or_part : other->RunsOrParts()) { |
| auto* const run = run_or_part.get(); |
| if (!run->GetRunInfo()) |
| continue; |
| // Compute start/end of the run, or of the part if ShapeResultView. |
| unsigned part_start = run->start_index_ + other->StartIndexOffsetForRun(); |
| if (other->IsRtl()) { |
| // Under RTL and multiple parts, A RunInfoPart may have an |
| // offset_ greater than start_index. In this case, run_start |
| // would result in an invalid negative value. |
| part_start = std::max(part_start, run->OffsetToRunStartIndex()); |
| } |
| unsigned run_end = part_start + run->num_characters_; |
| if (start_index < run_end && end_index > part_start) { |
| ShapeResult::RunInfo::GlyphDataRange range; |
| |
| // Adjust start/end to the character index of |RunInfo|. The start index |
| // of |RunInfo| could be different from |part_start| for ShapeResultView. |
| DCHECK_GE(part_start, run->OffsetToRunStartIndex()); |
| unsigned run_start = part_start - run->OffsetToRunStartIndex(); |
| unsigned adjusted_start = |
| start_index > run_start |
| ? std::max(start_index, part_start) - run_start |
| : 0; |
| unsigned adjusted_end = std::min(end_index, run_end) - run_start; |
| DCHECK(adjusted_end > adjusted_start); |
| unsigned part_characters = adjusted_end - adjusted_start; |
| float part_width; |
| |
| // Avoid O(log n) find operation if the entire run is in range. |
| if (part_start >= start_index && run_end <= end_index) { |
| range = run->GetGlyphDataRange(); |
| part_width = run->width_; |
| } else { |
| range = run->FindGlyphDataRange(adjusted_start, adjusted_end); |
| part_width = 0; |
| for (auto* glyph = range.begin; glyph != range.end; glyph++) |
| part_width += glyph->advance; |
| } |
| |
| // Adjust start_index for runs to be continuous. |
| unsigned part_start_index = run_start + adjusted_start + index_diff; |
| unsigned part_offset = adjusted_start; |
| new (part) RunInfoPart(run->GetRunInfo(), range, part_start_index, |
| part_offset, part_characters, part_width); |
| ++part; |
| |
| num_glyphs_ += range.end - range.begin; |
| width_ += part_width; |
| } |
| } |
| num_parts_ = static_cast<wtf_size_t>(std::distance(Parts().data(), part)); |
| } |
| |
| scoped_refptr<ShapeResultView> ShapeResultView::Create(const Segment* segments, |
| size_t segment_count) { |
| DCHECK_GT(segment_count, 0u); |
| #if DCHECK_IS_ON() |
| for (unsigned i = 0; i < segment_count; ++i) { |
| DCHECK((segments[i].result || segments[i].view) && |
| (!segments[i].result || !segments[i].view)); |
| } |
| #endif |
| wtf_size_t num_parts = 0; |
| for (auto& segment : base::span<const Segment>(segments, segment_count)) { |
| num_parts += segment.result ? segment.result->RunsOrParts().size() |
| : segment.view->RunsOrParts().size(); |
| } |
| static_assert(sizeof(ShapeResultView) % alignof(RunInfoPart) == 0, |
| "We have RunInfoPart as flexible array in ShapeResultView"); |
| const size_t byte_size = |
| sizeof(ShapeResultView) + sizeof(RunInfoPart) * num_parts; |
| void* buffer = ::WTF::Partitions::FastMalloc( |
| byte_size, ::WTF::GetStringWithTypeName<ShapeResultView>()); |
| ShapeResultView* out = segments[0].result |
| ? new (buffer) ShapeResultView(segments[0].result) |
| : new (buffer) ShapeResultView(segments[0].view); |
| out->AddSegments(segments, segment_count); |
| return base::AdoptRef(out); |
| } |
| |
| scoped_refptr<ShapeResultView> ShapeResultView::Create( |
| const ShapeResult* result, |
| unsigned start_index, |
| unsigned end_index) { |
| Segment segment = {result, start_index, end_index}; |
| return Create(&segment, 1); |
| } |
| |
| scoped_refptr<ShapeResultView> ShapeResultView::Create( |
| const ShapeResultView* result, |
| unsigned start_index, |
| unsigned end_index) { |
| Segment segment = {result, start_index, end_index}; |
| return Create(&segment, 1); |
| } |
| |
| scoped_refptr<ShapeResultView> ShapeResultView::Create( |
| const ShapeResult* result) { |
| // This specialization is an optimization to allow the bounding box to be |
| // re-used. |
| const wtf_size_t num_parts = result->RunsOrParts().size(); |
| static_assert(sizeof(ShapeResultView) % alignof(RunInfoPart) == 0, |
| "We have RunInfoPart as flexible array in ShapeResultView"); |
| const size_t byte_size = |
| sizeof(ShapeResultView) + sizeof(RunInfoPart) * num_parts; |
| void* buffer = ::WTF::Partitions::FastMalloc( |
| byte_size, ::WTF::GetStringWithTypeName<ShapeResultView>()); |
| ShapeResultView* out = new (buffer) ShapeResultView(result); |
| out->char_index_offset_ = result->StartIndex(); |
| if (out->IsLtr()) { |
| out->start_index_ = 0; |
| } else { |
| out->start_index_ = out->char_index_offset_; |
| out->char_index_offset_ = 0; |
| } |
| out->CreateViewsForResult(result, 0, std::numeric_limits<unsigned>::max()); |
| out->has_vertical_offsets_ = result->has_vertical_offsets_; |
| return base::AdoptRef(out); |
| } |
| |
| void ShapeResultView::AddSegments(const Segment* segments, |
| size_t segment_count) { |
| // This method assumes that no parts have been added yet. |
| DCHECK_EQ(num_parts_, 0u); |
| |
| // Segments are in logical order, runs and parts are in visual order. Iterate |
| // over segments back-to-front for RTL. |
| DCHECK_GT(segment_count, 0u); |
| unsigned last_segment_index = segment_count - 1; |
| |
| // Compute start index offset for the overall run. This is added to the start |
| // index of each glyph to ensure consistency with ShapeResult::SubRange |
| char_index_offset_ = segments[0].result ? segments[0].result->StartIndex() |
| : segments[0].view->StartIndex(); |
| char_index_offset_ = std::max(char_index_offset_, segments[0].start_index); |
| if (IsLtr()) { // Left-to-right |
| start_index_ = 0; |
| } else { // Right to left |
| start_index_ = char_index_offset_; |
| char_index_offset_ = 0; |
| } |
| |
| for (unsigned i = 0; i < segment_count; i++) { |
| const Segment& segment = segments[IsRtl() ? last_segment_index - i : i]; |
| if (segment.result) { |
| DCHECK_EQ(segment.result->Direction(), Direction()); |
| CreateViewsForResult(segment.result, segment.start_index, |
| segment.end_index); |
| has_vertical_offsets_ |= segment.result->has_vertical_offsets_; |
| } else if (segment.view) { |
| DCHECK_EQ(segment.view->Direction(), Direction()); |
| CreateViewsForResult(segment.view, segment.start_index, |
| segment.end_index); |
| has_vertical_offsets_ |= segment.view->has_vertical_offsets_; |
| } else { |
| NOTREACHED(); |
| } |
| } |
| } |
| |
| unsigned ShapeResultView::PreviousSafeToBreakOffset(unsigned index) const { |
| for (auto it = RunsOrParts().rbegin(); it != RunsOrParts().rend(); ++it) { |
| const auto& part = *it; |
| unsigned run_start = part.start_index_; |
| if (index >= run_start) { |
| unsigned offset = index - run_start; |
| if (offset <= part.num_characters_) { |
| return part.PreviousSafeToBreakOffset(offset) + run_start; |
| } |
| if (IsLtr()) { |
| return run_start + part.num_characters_; |
| } |
| } else if (IsRtl()) { |
| if (it == RunsOrParts().rbegin()) |
| return part.start_index_; |
| const auto& previous_run = *--it; |
| return previous_run.start_index_ + previous_run.num_characters_; |
| } |
| } |
| |
| return StartIndex(); |
| } |
| |
| void ShapeResultView::GetRunFontData( |
| Vector<ShapeResult::RunFontData>* font_data) const { |
| for (const auto& part : RunsOrParts()) { |
| font_data->push_back(ShapeResult::RunFontData( |
| {part.run_->font_data_.get(), part.end() - part.begin()})); |
| } |
| } |
| |
| void ShapeResultView::FallbackFonts( |
| HashSet<const SimpleFontData*>* fallback) const { |
| DCHECK(fallback); |
| DCHECK(primary_font_); |
| for (const auto& part : RunsOrParts()) { |
| if (part.run_->font_data_ && part.run_->font_data_ != primary_font_) { |
| fallback->insert(part.run_->font_data_.get()); |
| } |
| } |
| } |
| |
| template <bool has_non_zero_glyph_offsets> |
| float ShapeResultView::ForEachGlyphImpl(float initial_advance, |
| GlyphCallback glyph_callback, |
| void* context, |
| const RunInfoPart& part) const { |
| auto glyph_offsets = part.GetGlyphOffsets<has_non_zero_glyph_offsets>(); |
| const auto& run = part.run_; |
| auto total_advance = initial_advance; |
| bool is_horizontal = HB_DIRECTION_IS_HORIZONTAL(run->direction_); |
| const SimpleFontData* font_data = run->font_data_.get(); |
| const unsigned character_index_offset_for_glyph_data = |
| CharacterIndexOffsetForGlyphData(part); |
| for (const auto& glyph_data : part) { |
| unsigned character_index = |
| glyph_data.character_index + character_index_offset_for_glyph_data; |
| glyph_callback(context, character_index, glyph_data.glyph, *glyph_offsets, |
| total_advance, is_horizontal, run->canvas_rotation_, |
| font_data); |
| total_advance += glyph_data.advance; |
| ++glyph_offsets; |
| } |
| return total_advance; |
| } |
| |
| float ShapeResultView::ForEachGlyph(float initial_advance, |
| GlyphCallback glyph_callback, |
| void* context) const { |
| auto total_advance = initial_advance; |
| for (const auto& part : RunsOrParts()) { |
| if (part.HasGlyphOffsets()) { |
| total_advance = |
| ForEachGlyphImpl<true>(total_advance, glyph_callback, context, part); |
| } else { |
| total_advance = |
| ForEachGlyphImpl<false>(total_advance, glyph_callback, context, part); |
| } |
| } |
| return total_advance; |
| } |
| |
| template <bool has_non_zero_glyph_offsets> |
| float ShapeResultView::ForEachGlyphImpl(float initial_advance, |
| unsigned from, |
| unsigned to, |
| unsigned index_offset, |
| GlyphCallback glyph_callback, |
| void* context, |
| const RunInfoPart& part) const { |
| auto glyph_offsets = part.GetGlyphOffsets<has_non_zero_glyph_offsets>(); |
| auto total_advance = initial_advance; |
| const auto& run = part.run_; |
| bool is_horizontal = HB_DIRECTION_IS_HORIZONTAL(run->direction_); |
| const SimpleFontData* font_data = run->font_data_.get(); |
| const unsigned character_index_offset_for_glyph_data = |
| CharacterIndexOffsetForGlyphData(part); |
| if (run->IsLtr()) { // Left-to-right |
| for (const auto& glyph_data : part) { |
| unsigned character_index = |
| glyph_data.character_index + character_index_offset_for_glyph_data; |
| if (character_index >= to) |
| break; |
| if (character_index >= from) { |
| glyph_callback(context, character_index, glyph_data.glyph, |
| *glyph_offsets, total_advance, is_horizontal, |
| run->canvas_rotation_, font_data); |
| } |
| total_advance += glyph_data.advance; |
| ++glyph_offsets; |
| } |
| |
| } else { // Right-to-left |
| for (const auto& glyph_data : part) { |
| unsigned character_index = |
| glyph_data.character_index + character_index_offset_for_glyph_data; |
| if (character_index < from) |
| break; |
| if (character_index < to) { |
| glyph_callback(context, character_index, glyph_data.glyph, |
| *glyph_offsets, total_advance, is_horizontal, |
| run->canvas_rotation_, font_data); |
| } |
| total_advance += glyph_data.advance; |
| ++glyph_offsets; |
| } |
| } |
| return total_advance; |
| } |
| |
| float ShapeResultView::ForEachGlyph(float initial_advance, |
| unsigned from, |
| unsigned to, |
| unsigned index_offset, |
| GlyphCallback glyph_callback, |
| void* context) const { |
| auto total_advance = initial_advance; |
| |
| for (const auto& part : Parts()) { |
| if (part.HasGlyphOffsets()) { |
| total_advance = ForEachGlyphImpl<true>( |
| total_advance, from, to, index_offset, glyph_callback, context, part); |
| } else { |
| total_advance = ForEachGlyphImpl<false>( |
| total_advance, from, to, index_offset, glyph_callback, context, part); |
| } |
| } |
| return total_advance; |
| } |
| |
| float ShapeResultView::ForEachGraphemeClusters(const StringView& text, |
| float initial_advance, |
| unsigned from, |
| unsigned to, |
| unsigned index_offset, |
| GraphemeClusterCallback callback, |
| void* context) const { |
| unsigned run_offset = index_offset; |
| float advance_so_far = initial_advance; |
| |
| for (const auto& part : RunsOrParts()) { |
| if (!part.NumGlyphs()) |
| continue; |
| |
| const auto& run = part.run_; |
| unsigned graphemes_in_cluster = 1; |
| float cluster_advance = 0; |
| bool rtl = Direction() == TextDirection::kRtl; |
| |
| // 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. |
| const unsigned character_index_offset_for_glyph_data = |
| CharacterIndexOffsetForGlyphData(part) + run_offset; |
| uint16_t cluster_start = |
| static_cast<uint16_t>(rtl ? part.CharacterIndexOfEndGlyph() + |
| character_index_offset_for_glyph_data |
| : part.GlyphAt(0).character_index + |
| character_index_offset_for_glyph_data); |
| |
| const unsigned num_glyphs = part.NumGlyphs(); |
| for (unsigned i = 0; i < num_glyphs; ++i) { |
| const HarfBuzzRunGlyphData& glyph_data = part.GlyphAt(i); |
| uint16_t current_character_index = |
| glyph_data.character_index + character_index_offset_for_glyph_data; |
| |
| bool is_run_end = (i + 1 == num_glyphs); |
| bool is_cluster_end = |
| is_run_end || (part.GlyphAt(i + 1).character_index + |
| character_index_offset_for_glyph_data != |
| current_character_index); |
| |
| if ((rtl && current_character_index >= to) || |
| (!rtl && current_character_index < from)) { |
| advance_so_far += glyph_data.advance; |
| rtl ? --cluster_start : ++cluster_start; |
| continue; |
| } |
| |
| cluster_advance += glyph_data.advance; |
| |
| if (text.Is8Bit()) { |
| callback(context, current_character_index, advance_so_far, 1, |
| glyph_data.advance, run->canvas_rotation_); |
| |
| advance_so_far += glyph_data.advance; |
| } else if (is_cluster_end) { |
| uint16_t cluster_end; |
| if (rtl) { |
| cluster_end = current_character_index; |
| } else { |
| cluster_end = static_cast<uint16_t>( |
| is_run_end ? part.CharacterIndexOfEndGlyph() + |
| character_index_offset_for_glyph_data |
| : part.GlyphAt(i + 1).character_index + |
| character_index_offset_for_glyph_data); |
| } |
| graphemes_in_cluster = ShapeResult::CountGraphemesInCluster( |
| text.Span16(), cluster_start, cluster_end); |
| if (!graphemes_in_cluster || !cluster_advance) |
| continue; |
| |
| callback(context, current_character_index, advance_so_far, |
| graphemes_in_cluster, cluster_advance, run->canvas_rotation_); |
| advance_so_far += cluster_advance; |
| |
| cluster_start = cluster_end; |
| cluster_advance = 0; |
| } |
| } |
| } |
| return advance_so_far; |
| } |
| |
| template <bool is_horizontal_run, bool has_non_zero_glyph_offsets> |
| void ShapeResultView::ComputePartInkBounds( |
| const ShapeResultView::RunInfoPart& part, |
| float run_advance, |
| FloatRect* ink_bounds) const { |
| // Get glyph bounds from Skia. It's a lot faster if we give it list of glyph |
| // IDs rather than calling it for each glyph. |
| // TODO(kojii): MacOS does not benefit from batching the Skia request due to |
| // https://bugs.chromium.org/p/skia/issues/detail?id=5328, and the cost to |
| // prepare batching, which is normally much less than the benefit of |
| // batching, is not ignorable unfortunately. |
| auto glyph_offsets = part.GetGlyphOffsets<has_non_zero_glyph_offsets>(); |
| const SimpleFontData& current_font_data = *part.run_->font_data_; |
| unsigned num_glyphs = part.NumGlyphs(); |
| #if !defined(OS_MAC) |
| Vector<Glyph, 256> glyphs(num_glyphs); |
| unsigned i = 0; |
| for (const auto& glyph_data : part) |
| glyphs[i++] = glyph_data.glyph; |
| Vector<SkRect, 256> bounds_list(num_glyphs); |
| current_font_data.BoundsForGlyphs(glyphs, &bounds_list); |
| #endif |
| |
| GlyphBoundsAccumulator bounds(run_advance); |
| for (unsigned j = 0; j < num_glyphs; ++j) { |
| const HarfBuzzRunGlyphData& glyph_data = part.GlyphAt(j); |
| #if defined(OS_MAC) |
| FloatRect glyph_bounds = current_font_data.BoundsForGlyph(glyph_data.glyph); |
| #else |
| FloatRect glyph_bounds(bounds_list[j]); |
| #endif |
| bounds.Unite<is_horizontal_run>(glyph_bounds, *glyph_offsets); |
| bounds.origin += glyph_data.advance; |
| ++glyph_offsets; |
| } |
| |
| if (!is_horizontal_run) |
| bounds.ConvertVerticalRunToLogical(current_font_data.GetFontMetrics()); |
| ink_bounds->Unite(bounds.bounds); |
| } |
| |
| FloatRect ShapeResultView::ComputeInkBounds() const { |
| FloatRect ink_bounds; |
| |
| float run_advance = 0.0f; |
| for (const auto& part : Parts()) { |
| if (part.HasGlyphOffsets()) { |
| if (part.IsHorizontal()) { |
| ComputePartInkBounds<true, true>(part, run_advance, &ink_bounds); |
| } else { |
| ComputePartInkBounds<false, true>(part, run_advance, &ink_bounds); |
| } |
| } else { |
| if (part.IsHorizontal()) { |
| ComputePartInkBounds<true, false>(part, run_advance, &ink_bounds); |
| } else { |
| ComputePartInkBounds<false, false>(part, run_advance, &ink_bounds); |
| } |
| } |
| run_advance += part.Width(); |
| } |
| |
| return ink_bounds; |
| } |
| |
| void ShapeResultView::ExpandRangeToIncludePartialGlyphs(unsigned* from, |
| unsigned* to) const { |
| unsigned accumulated_offset = char_index_offset_; |
| for (const auto& part : Parts()) { |
| part.ExpandRangeToIncludePartialGlyphs(accumulated_offset, from, to); |
| accumulated_offset += part.NumCharacters(); |
| } |
| } |
| |
| } // namespace blink |