blob: 9773418b7c7c32fe9bf0a1c24b3af87777c496fc [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/core/layout/ng/inline/ng_inline_item_segment.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h"
namespace blink {
namespace {
// Constants for PackSegmentData() and UnpackSegmentData().
//
// UScriptCode is -1 (USCRIPT_INVALID_CODE) to 177 as of ICU 60.
// This can be packed to 8 bits, by handling -1 separately.
static constexpr unsigned kScriptBits = 8;
static constexpr unsigned kFontFallbackPriorityBits = 2;
static constexpr unsigned kRenderOrientationBits = 1;
static constexpr unsigned kScriptMask = (1 << kScriptBits) - 1;
static constexpr unsigned kFontFallbackPriorityMask =
(1 << kFontFallbackPriorityBits) - 1;
static constexpr unsigned kRenderOrientationMask =
(1 << kRenderOrientationBits) - 1;
static_assert(NGInlineItemSegment::kSegmentDataBits ==
kScriptBits + kRenderOrientationBits +
kFontFallbackPriorityBits,
"kSegmentDataBits must be the sum of these bits");
unsigned SetRenderOrientation(
unsigned value,
OrientationIterator::RenderOrientation render_orientation) {
DCHECK_NE(render_orientation,
OrientationIterator::RenderOrientation::kOrientationInvalid);
return (value & ~kRenderOrientationMask) |
(render_orientation !=
OrientationIterator::RenderOrientation::kOrientationKeep);
}
} // namespace
NGInlineItemSegment::NGInlineItemSegment(
const RunSegmenter::RunSegmenterRange& range)
: end_offset_(range.end), segment_data_(PackSegmentData(range)) {}
NGInlineItemSegment::NGInlineItemSegment(unsigned end_offset,
const NGInlineItem& item)
: end_offset_(end_offset), segment_data_(item.SegmentData()) {}
unsigned NGInlineItemSegment::PackSegmentData(
const RunSegmenter::RunSegmenterRange& range) {
DCHECK(range.script == USCRIPT_INVALID_CODE ||
static_cast<unsigned>(range.script) <= kScriptMask);
DCHECK_LE(static_cast<unsigned>(range.font_fallback_priority),
kFontFallbackPriorityMask);
DCHECK_LE(static_cast<unsigned>(range.render_orientation),
kRenderOrientationMask);
unsigned value =
range.script != USCRIPT_INVALID_CODE ? range.script : kScriptMask;
value <<= kFontFallbackPriorityBits;
value |= static_cast<unsigned>(range.font_fallback_priority);
value <<= kRenderOrientationBits;
value |= range.render_orientation;
return value;
}
RunSegmenter::RunSegmenterRange NGInlineItemSegment::UnpackSegmentData(
unsigned start_offset,
unsigned end_offset,
unsigned value) {
unsigned render_orientation = value & kRenderOrientationMask;
value >>= kRenderOrientationBits;
unsigned font_fallback_priority = value & kFontFallbackPriorityMask;
value >>= kFontFallbackPriorityBits;
unsigned script = value & kScriptMask;
return RunSegmenter::RunSegmenterRange{
start_offset, end_offset,
script != kScriptMask ? static_cast<UScriptCode>(script)
: USCRIPT_INVALID_CODE,
static_cast<OrientationIterator::RenderOrientation>(render_orientation),
static_cast<FontFallbackPriority>(font_fallback_priority)};
}
RunSegmenter::RunSegmenterRange NGInlineItemSegment::ToRunSegmenterRange(
unsigned start_offset,
unsigned end_offset) const {
DCHECK_LT(start_offset, end_offset);
DCHECK_LT(start_offset, end_offset_);
return UnpackSegmentData(start_offset, std::min(end_offset, end_offset_),
segment_data_);
}
unsigned NGInlineItemSegments::OffsetForSegment(
const NGInlineItemSegment& segment) const {
return &segment == segments_.begin() ? 0 : std::prev(&segment)->EndOffset();
}
#if DCHECK_IS_ON()
void NGInlineItemSegments::CheckOffset(
unsigned offset,
const NGInlineItemSegment* segment) const {
DCHECK(segment >= segments_.begin() && segment < segments_.end());
DCHECK_GE(offset, OffsetForSegment(*segment));
DCHECK_LT(offset, segment->EndOffset());
}
#endif
NGInlineItemSegments::Iterator NGInlineItemSegments::Ranges(
unsigned start_offset,
unsigned end_offset,
unsigned item_index) const {
DCHECK_LT(start_offset, end_offset);
DCHECK_LE(end_offset, EndOffset());
// Find the first segment for |item_index|.
unsigned segment_index = items_to_segments_[item_index];
const NGInlineItemSegment* segment = &segments_[segment_index];
DCHECK_GE(start_offset, OffsetForSegment(*segment));
if (start_offset < segment->EndOffset())
return Iterator(start_offset, end_offset, segment);
// The item has multiple segments. Find the segments for |start_offset|.
const NGInlineItemSegment* end_segment =
item_index + 1 < items_to_segments_.size() ? &segments_[segment_index + 1]
: segments_.end();
segment =
std::upper_bound(segment, end_segment, start_offset,
[](unsigned offset, const NGInlineItemSegment& segment) {
return offset < segment.EndOffset();
});
CheckOffset(start_offset, segment);
return Iterator(start_offset, end_offset, segment);
}
void NGInlineItemSegments::ComputeSegments(
RunSegmenter* segmenter,
RunSegmenter::RunSegmenterRange* range) {
segments_.Shrink(0);
do {
segments_.emplace_back(*range);
} while (segmenter->Consume(range));
}
unsigned NGInlineItemSegments::AppendMixedFontOrientation(
const String& text_content,
unsigned start_offset,
unsigned end_offset,
unsigned segment_index) {
DCHECK_LT(start_offset, end_offset);
OrientationIterator iterator(text_content.Characters16() + start_offset,
end_offset - start_offset,
FontOrientation::kVerticalMixed);
unsigned original_start_offset = start_offset;
OrientationIterator::RenderOrientation orientation;
for (; iterator.Consume(&end_offset, &orientation);
start_offset = end_offset) {
end_offset += original_start_offset;
segment_index = PopulateItemsFromFontOrientation(
start_offset, end_offset, orientation, segment_index);
}
return segment_index;
}
unsigned NGInlineItemSegments::PopulateItemsFromFontOrientation(
unsigned start_offset,
unsigned end_offset,
OrientationIterator::RenderOrientation render_orientation,
unsigned segment_index) {
DCHECK_LT(start_offset, end_offset);
DCHECK_LE(end_offset, segments_.back().EndOffset());
while (start_offset >= segments_[segment_index].EndOffset())
++segment_index;
if (start_offset !=
(segment_index ? segments_[segment_index - 1].EndOffset() : 0u)) {
Split(segment_index, start_offset);
++segment_index;
}
for (;; ++segment_index) {
NGInlineItemSegment& segment = segments_[segment_index];
segment.segment_data_ =
SetRenderOrientation(segment.segment_data_, render_orientation);
if (end_offset == segment.EndOffset()) {
++segment_index;
break;
}
if (end_offset < segment.EndOffset()) {
Split(segment_index, end_offset);
++segment_index;
break;
}
}
return segment_index;
}
void NGInlineItemSegments::Split(unsigned index, unsigned offset) {
NGInlineItemSegment& segment = segments_[index];
DCHECK_LT(offset, segment.EndOffset());
unsigned end_offset = segment.EndOffset();
segment.end_offset_ = offset;
segments_.insert(index + 1,
NGInlineItemSegment(end_offset, segment.segment_data_));
}
void NGInlineItemSegments::ComputeItemIndex(const Vector<NGInlineItem>& items) {
DCHECK_EQ(items.back().EndOffset(), EndOffset());
unsigned segment_index = 0;
const NGInlineItemSegment* segment = segments_.begin();
unsigned item_index = 0;
items_to_segments_.resize(items.size());
for (const NGInlineItem& item : items) {
if (item.Length()) {
while (item.StartOffset() >= segment->EndOffset()) {
++segment_index;
DCHECK_LT(segment_index, segments_.size());
++segment;
}
}
items_to_segments_[item_index++] = segment_index;
}
}
scoped_refptr<ShapeResult> NGInlineItemSegments::ShapeText(
HarfBuzzShaper* shaper,
const Font* font,
TextDirection direction,
unsigned start_offset,
unsigned end_offset,
unsigned item_index) const {
scoped_refptr<ShapeResult> shape_result;
for (const RunSegmenter::RunSegmenterRange& range :
Ranges(start_offset, end_offset, item_index)) {
scoped_refptr<ShapeResult> segment_shape_result =
shaper->Shape(font, direction, range.start, range.end, &range);
if (!shape_result)
shape_result = std::move(segment_shape_result);
else
segment_shape_result->CopyRange(0, end_offset, shape_result.get());
}
DCHECK(shape_result);
DCHECK_EQ(shape_result->StartIndex(), start_offset);
DCHECK_EQ(shape_result->EndIndex(), end_offset);
return shape_result;
}
} // namespace blink