blob: c41eeae4d4cd510f022d30411b90c0658425afa1 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/fonts/shaping/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