blob: ef198a493ca7d70be027bcb3d8b319db520c4d16 [file] [log] [blame]
/*
* Copyright (C) 2000 Lars Knoll (knoll@kde.org)
* Copyright (C) 2003, 2004, 2006, 2007, 2008, 2009, 2010 Apple Inc.
* All right reserved.
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LINE_INLINE_ITERATOR_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LINE_INLINE_ITERATOR_H_
#include "third_party/blink/renderer/core/layout/api/line_layout_block_flow.h"
#include "third_party/blink/renderer/core/layout/api/line_layout_inline.h"
#include "third_party/blink/renderer/core/layout/api/line_layout_text.h"
#include "third_party/blink/renderer/core/layout/bidi_run.h"
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
namespace blink {
struct BidiIsolatedRun {
BidiIsolatedRun(LineLayoutItem object,
unsigned position,
LineLayoutItem& root,
BidiRun& run_to_replace,
unsigned char level)
: object(object),
root(root),
run_to_replace(run_to_replace),
position(position),
level(level) {}
LineLayoutItem object;
LineLayoutItem root;
BidiRun& run_to_replace;
unsigned position;
unsigned char level;
};
// This class is used to LayoutInline subtrees, stepping by character within the
// text children. InlineIterator will use bidiNext to find the next LayoutText
// optionally notifying a BidiResolver every time it steps into/out of a
// LayoutInline.
class InlineIterator {
DISALLOW_NEW();
public:
enum IncrementRule {
kFastIncrementInIsolatedLayout,
kFastIncrementInTextNode
};
InlineIterator()
: root_(nullptr),
line_layout_item_(nullptr),
next_breakable_position_(-1),
pos_(0) {}
InlineIterator(LineLayoutItem root, LineLayoutItem o, unsigned p)
: root_(root),
line_layout_item_(o),
next_breakable_position_(-1),
pos_(p) {}
void Clear() { MoveTo(nullptr, 0); }
void MoveToStartOf(LineLayoutItem object) { MoveTo(object, 0); }
void MoveTo(LineLayoutItem object, unsigned offset, int next_break = -1) {
line_layout_item_ = object;
pos_ = offset;
next_breakable_position_ = next_break;
}
LineLayoutItem GetLineLayoutItem() const { return line_layout_item_; }
void SetLineLayoutItem(LineLayoutItem line_layout_item) {
line_layout_item_ = line_layout_item;
}
int NextBreakablePosition() const { return next_breakable_position_; }
void SetNextBreakablePosition(int position) {
next_breakable_position_ = position;
}
unsigned Offset() const { return pos_; }
void SetOffset(unsigned position) { pos_ = position; }
LineLayoutItem Root() const { return root_; }
void FastIncrementInTextNode();
void Increment(InlineBidiResolver* = nullptr,
IncrementRule = kFastIncrementInTextNode);
bool AtEnd() const;
inline bool AtTextParagraphSeparator() const {
return line_layout_item_ && line_layout_item_.PreservesNewline() &&
line_layout_item_.IsText() &&
LineLayoutText(line_layout_item_).TextLength() &&
!LineLayoutText(line_layout_item_).IsWordBreak() &&
LineLayoutText(line_layout_item_).CharacterAt(pos_) == '\n';
}
inline bool AtParagraphSeparator() const {
return (line_layout_item_ && line_layout_item_.IsBR()) ||
AtTextParagraphSeparator();
}
UChar CharacterAt(unsigned) const;
UChar Current() const;
UChar PreviousInSameNode() const;
UChar32 CodepointAt(unsigned) const;
UChar32 CurrentCodepoint() const;
ALWAYS_INLINE WTF::unicode::CharDirection Direction() const;
private:
LineLayoutItem root_;
LineLayoutItem line_layout_item_;
int next_breakable_position_;
unsigned pos_;
};
inline bool operator==(const InlineIterator& it1, const InlineIterator& it2) {
return it1.Offset() == it2.Offset() &&
it1.GetLineLayoutItem() == it2.GetLineLayoutItem();
}
inline bool operator!=(const InlineIterator& it1, const InlineIterator& it2) {
return it1.Offset() != it2.Offset() ||
it1.GetLineLayoutItem() != it2.GetLineLayoutItem();
}
static inline WTF::unicode::CharDirection EmbedCharFromDirection(
TextDirection dir,
UnicodeBidi unicode_bidi) {
if (unicode_bidi == UnicodeBidi::kEmbed) {
return dir == TextDirection::kRtl ? WTF::unicode::kRightToLeftEmbedding
: WTF::unicode::kLeftToRightEmbedding;
}
return dir == TextDirection::kRtl ? WTF::unicode::kRightToLeftOverride
: WTF::unicode::kLeftToRightOverride;
}
static inline bool TreatAsIsolated(const ComputedStyle& style) {
return IsIsolated(style.GetUnicodeBidi()) &&
style.RtlOrdering() == EOrder::kLogical;
}
template <class Observer>
static inline void NotifyObserverEnteredObject(Observer* observer,
LineLayoutItem object) {
if (!observer || !object || !object.IsLayoutInline())
return;
const ComputedStyle& style = object.StyleRef();
UnicodeBidi unicode_bidi = style.GetUnicodeBidi();
if (unicode_bidi == UnicodeBidi::kNormal) {
// https://drafts.csswg.org/css-writing-modes/#unicode-bidi
// "The element does not open an additional level of embedding with respect
// to the bidirectional algorithm."
// Thus we ignore any possible dir= attribute on the span.
return;
}
if (TreatAsIsolated(style)) {
// Make sure that explicit embeddings are committed before we enter the
// isolated content.
observer->CommitExplicitEmbedding(observer->Runs());
observer->EnterIsolate();
// Embedding/Override characters implied by dir= will be handled when
// we process the isolated span, not when laying out the "parent" run.
return;
}
if (!observer->InIsolate())
observer->Embed(EmbedCharFromDirection(style.Direction(), unicode_bidi),
kFromStyleOrDOM);
}
template <class Observer>
static inline void NotifyObserverWillExitObject(Observer* observer,
LineLayoutItem object) {
if (!observer || !object || !object.IsLayoutInline())
return;
UnicodeBidi unicode_bidi = object.StyleRef().GetUnicodeBidi();
if (unicode_bidi == UnicodeBidi::kNormal)
return; // Nothing to do for unicode-bidi: normal
if (TreatAsIsolated(object.StyleRef())) {
observer->ExitIsolate();
return;
}
// Otherwise we pop any embed/override character we added when we opened this
// tag.
if (!observer->InIsolate())
observer->Embed(WTF::unicode::kPopDirectionalFormat, kFromStyleOrDOM);
}
static inline bool IsIteratorTarget(LineLayoutItem object) {
// The iterator will of course return 0, but its not an expected argument to
// this function.
DCHECK(object);
return object.IsText() || object.IsFloating() ||
object.IsOutOfFlowPositioned() || object.IsAtomicInlineLevel();
}
// This enum is only used for bidiNextShared()
enum EmptyInlineBehavior {
kSkipEmptyInlines,
kIncludeEmptyInlines,
};
static bool IsEmptyInline(LineLayoutItem object) {
if (!object.IsLayoutInline())
return false;
for (LineLayoutItem curr = LineLayoutInline(object).FirstChild(); curr;
curr = curr.NextSibling()) {
if (curr.IsFloatingOrOutOfFlowPositioned())
continue;
if (curr.IsText() && LineLayoutText(curr).IsAllCollapsibleWhitespace())
continue;
if (!IsEmptyInline(curr))
return false;
}
return true;
}
// FIXME: This function is misleadingly named. It has little to do with bidi.
// This function will iterate over inlines within a block, optionally notifying
// a bidi resolver as it enters/exits inlines (so it can push/pop embedding
// levels).
template <class Observer>
static inline LineLayoutItem BidiNextShared(
LineLayoutItem root,
LineLayoutItem current,
Observer* observer = 0,
EmptyInlineBehavior empty_inline_behavior = kSkipEmptyInlines,
bool* end_of_inline_ptr = nullptr) {
LineLayoutItem next = nullptr;
// oldEndOfInline denotes if when we last stopped iterating if we were at the
// end of an inline.
bool old_end_of_inline = end_of_inline_ptr ? *end_of_inline_ptr : false;
bool end_of_inline = false;
while (current) {
next = nullptr;
if (!old_end_of_inline && !IsIteratorTarget(current)) {
next = current.SlowFirstChild();
NotifyObserverEnteredObject(observer, next);
}
// We hit this when either current has no children, or when current is not a
// layoutObject we care about.
if (!next) {
// If it is a layoutObject we care about, and we're doing our inline-walk,
// return it.
if (empty_inline_behavior == kIncludeEmptyInlines && !old_end_of_inline &&
current.IsLayoutInline()) {
next = current;
end_of_inline = true;
break;
}
while (current && current != root) {
NotifyObserverWillExitObject(observer, current);
next = current.NextSibling();
if (next) {
NotifyObserverEnteredObject(observer, next);
break;
}
current = current.Parent();
if (empty_inline_behavior == kIncludeEmptyInlines && current &&
current != root && current.IsLayoutInline()) {
next = current;
end_of_inline = true;
break;
}
}
}
if (!next)
break;
if (IsIteratorTarget(next) ||
((empty_inline_behavior == kIncludeEmptyInlines ||
IsEmptyInline(next)) // Always return EMPTY inlines.
&& next.IsLayoutInline()))
break;
current = next;
}
if (end_of_inline_ptr)
*end_of_inline_ptr = end_of_inline;
return next;
}
template <class Observer>
static inline LineLayoutItem BidiNextSkippingEmptyInlines(
LineLayoutItem root,
LineLayoutItem current,
Observer* observer) {
// TODO(rhogan): Rename this caller. It's used for a detailed walk of every
// object in an inline flow, for example during line layout.
// We always return empty inlines in bidiNextShared, which gives lie to the
// bidiNext[Skipping|Including]EmptyInlines naming scheme we use to call it.
// bidiNextSkippingEmptyInlines is the less fussy of the two callers, it will
// always try to advance and will return what it finds if it's a line layout
// object in isIteratorTarget or if it's an empty LayoutInline. If the
// LayoutInline has content, it will advance past the start of the LayoutLine
// and try to return one of its children. The SkipEmptyInlines callers never
// care about endOfInlinePtr.
return BidiNextShared(root, current, observer, kSkipEmptyInlines);
}
// This makes callers cleaner as they don't have to specify a type for the
// observer when not providing one.
static inline LineLayoutItem BidiNextSkippingEmptyInlines(
LineLayoutItem root,
LineLayoutItem current) {
InlineBidiResolver* observer = nullptr;
return BidiNextSkippingEmptyInlines(root, current, observer);
}
static inline LineLayoutItem BidiNextIncludingEmptyInlines(
LineLayoutItem root,
LineLayoutItem current,
bool* end_of_inline_ptr = nullptr) {
// TODO(rhogan): Rename this caller. It's used for quick and dirty walks of
// inline children by InlineWalker, which isn't interested in the contents of
// inlines. Use cases include dirtying objects or simplified layout that
// leaves lineboxes intact. bidiNextIncludingEmptyInlines will return if the
// iterator is at the start of a LayoutInline (even if it hasn't advanced yet)
// unless it previously stopped at the start of the same LayoutInline the last
// time it tried to iterate.
// If it finds itself inside a LayoutInline that doesn't have anything in
// isIteratorTarget it will return the enclosing LayoutInline.
InlineBidiResolver* observer =
nullptr; // Callers who include empty inlines, never use an observer.
return BidiNextShared(root, current, observer, kIncludeEmptyInlines,
end_of_inline_ptr);
}
static inline LineLayoutItem BidiFirstSkippingEmptyInlines(
LineLayoutBlockFlow root,
BidiRunList<BidiRun>& runs,
InlineBidiResolver* resolver = nullptr) {
LineLayoutItem o = root.FirstChild();
if (!o)
return nullptr;
if (o.IsLayoutInline()) {
NotifyObserverEnteredObject(resolver, o);
if (!IsEmptyInline(o)) {
o = BidiNextSkippingEmptyInlines(root, o, resolver);
} else {
// Never skip empty inlines.
if (resolver)
resolver->CommitExplicitEmbedding(runs);
return o;
}
}
// FIXME: Unify this with the bidiNext call above.
if (o && !IsIteratorTarget(o))
o = BidiNextSkippingEmptyInlines(root, o, resolver);
if (resolver)
resolver->CommitExplicitEmbedding(runs);
return o;
}
// FIXME: This method needs to be renamed when bidiNext finds a good name.
static inline LineLayoutItem BidiFirstIncludingEmptyInlines(
LineLayoutBlockFlow root) {
LineLayoutItem o = root.FirstChild();
// If either there are no children to walk, or the first one is correct
// then just return it.
if (!o || o.IsLayoutInline() || IsIteratorTarget(o))
return o;
return BidiNextIncludingEmptyInlines(root, o);
}
inline void InlineIterator::FastIncrementInTextNode() {
DCHECK(line_layout_item_);
DCHECK(line_layout_item_.IsText());
DCHECK_LE(pos_, LineLayoutText(line_layout_item_).TextLength());
if (pos_ < INT_MAX)
pos_++;
}
// FIXME: This is used by LayoutBlockFlow for simplified layout, and has nothing
// to do with bidi it shouldn't use functions called bidiFirst and bidiNext.
class InlineWalker {
STACK_ALLOCATED();
public:
InlineWalker(LineLayoutBlockFlow root)
: root_(root), current_(nullptr), at_end_of_inline_(false) {
// FIXME: This class should be taught how to do the SkipEmptyInlines
// codepath as well.
current_ = BidiFirstIncludingEmptyInlines(root_);
}
LineLayoutBlockFlow Root() { return root_; }
LineLayoutItem Current() { return current_; }
bool AtEndOfInline() { return at_end_of_inline_; }
bool AtEnd() const { return !current_; }
LineLayoutItem Advance() {
// FIXME: Support SkipEmptyInlines and observer parameters.
current_ =
BidiNextIncludingEmptyInlines(root_, current_, &at_end_of_inline_);
return current_;
}
private:
LineLayoutBlockFlow root_;
LineLayoutItem current_;
bool at_end_of_inline_;
};
static inline bool EndOfLineHasIsolatedObjectAncestor(
const InlineIterator& isolated_iterator,
const InlineIterator& ancestor_itertor) {
if (!isolated_iterator.GetLineLayoutItem() ||
!TreatAsIsolated(isolated_iterator.GetLineLayoutItem().StyleRef()))
return false;
LineLayoutItem inner_isolated_object = isolated_iterator.GetLineLayoutItem();
while (inner_isolated_object &&
inner_isolated_object != isolated_iterator.Root()) {
if (inner_isolated_object == ancestor_itertor.GetLineLayoutItem())
return true;
inner_isolated_object = inner_isolated_object.Parent();
}
return false;
}
inline void InlineIterator::Increment(InlineBidiResolver* resolver,
IncrementRule rule) {
if (!line_layout_item_)
return;
if (rule == kFastIncrementInIsolatedLayout && resolver &&
resolver->InIsolate() &&
!EndOfLineHasIsolatedObjectAncestor(resolver->EndOfLine(),
resolver->GetPosition())) {
MoveTo(BidiNextSkippingEmptyInlines(root_, line_layout_item_, resolver), 0);
return;
}
if (line_layout_item_.IsText()) {
FastIncrementInTextNode();
if (pos_ < LineLayoutText(line_layout_item_).TextLength())
return;
}
// bidiNext can return 0, so use moveTo instead of moveToStartOf
MoveTo(BidiNextSkippingEmptyInlines(root_, line_layout_item_, resolver), 0);
}
inline bool InlineIterator::AtEnd() const {
return !line_layout_item_;
}
inline UChar InlineIterator::CharacterAt(unsigned index) const {
if (!line_layout_item_ || !line_layout_item_.IsText())
return 0;
return LineLayoutText(line_layout_item_).CharacterAt(index);
}
inline UChar InlineIterator::Current() const {
return CharacterAt(pos_);
}
inline UChar InlineIterator::PreviousInSameNode() const {
if (!pos_)
return 0;
return CharacterAt(pos_ - 1);
}
inline UChar32 InlineIterator::CodepointAt(unsigned index) const {
if (!line_layout_item_ || !line_layout_item_.IsText())
return 0;
return LineLayoutText(line_layout_item_).CodepointAt(index);
}
inline UChar32 InlineIterator::CurrentCodepoint() const {
return CodepointAt(pos_);
}
ALWAYS_INLINE WTF::unicode::CharDirection InlineIterator::Direction() const {
if (UChar32 c = CurrentCodepoint())
return WTF::unicode::Direction(c);
if (line_layout_item_ && line_layout_item_.IsListMarker()) {
return line_layout_item_.StyleRef().IsLeftToRightDirection()
? WTF::unicode::kLeftToRight
: WTF::unicode::kRightToLeft;
}
return WTF::unicode::kOtherNeutral;
}
template <>
inline void InlineBidiResolver::Increment() {
current_.Increment(this, InlineIterator::kFastIncrementInIsolatedLayout);
}
template <>
inline bool InlineBidiResolver::IsEndOfLine(const InlineIterator& end) {
bool in_end_of_line =
current_ == end || current_.AtEnd() ||
(InIsolate() && current_.GetLineLayoutItem() == end.GetLineLayoutItem());
if (InIsolate() && in_end_of_line) {
current_.MoveTo(current_.GetLineLayoutItem(), end.Offset(),
current_.NextBreakablePosition());
last_ = current_;
UpdateStatusLastFromCurrentDirection(WTF::unicode::kOtherNeutral);
}
return in_end_of_line;
}
static inline bool IsCollapsibleSpace(UChar character,
LineLayoutText layout_text) {
if (character == ' ' || character == '\t' ||
character == kSoftHyphenCharacter)
return true;
if (character == '\n')
return !layout_text.StyleRef().PreserveNewline();
return false;
}
template <typename CharacterType>
static inline int FindFirstTrailingSpace(LineLayoutText last_text,
const CharacterType* characters,
int start,
int stop) {
int first_space = stop;
while (first_space > start) {
UChar current = characters[first_space - 1];
if (!IsCollapsibleSpace(current, last_text))
break;
first_space--;
}
return first_space;
}
template <>
inline int InlineBidiResolver::FindFirstTrailingSpaceAtRun(BidiRun* run) {
DCHECK(run);
LineLayoutItem last_object = LineLayoutItem(run->line_layout_item_);
if (!last_object.IsText())
return run->stop_;
LineLayoutText last_text(last_object);
int first_space;
if (last_text.Is8Bit())
first_space = FindFirstTrailingSpace(last_text, last_text.Characters8(),
run->Start(), run->Stop());
else
first_space = FindFirstTrailingSpace(last_text, last_text.Characters16(),
run->Start(), run->Stop());
return first_space;
}
template <>
inline BidiRun* InlineBidiResolver::AddTrailingRun(
BidiRunList<BidiRun>& runs,
int start,
int stop,
BidiRun* run,
BidiContext* context,
TextDirection direction) const {
DCHECK(context);
BidiRun* new_trailing_run = new BidiRun(
context->Override(), context->Level(), start, stop,
run->line_layout_item_, WTF::unicode::kOtherNeutral, context->Dir());
if (direction == TextDirection::kLtr)
runs.AddRun(new_trailing_run);
else
runs.PrependRun(new_trailing_run);
return new_trailing_run;
}
template <>
inline bool InlineBidiResolver::NeedsTrailingSpace(BidiRunList<BidiRun>& runs) {
if (needs_trailing_space_)
return true;
const ComputedStyle& style =
runs.LogicallyLastRun()->line_layout_item_.StyleRef();
if (style.BreakOnlyAfterWhiteSpace() && style.AutoWrap())
return true;
return false;
}
static inline bool IsIsolatedInline(LineLayoutItem object) {
DCHECK(object);
return object.IsLayoutInline() && TreatAsIsolated(object.StyleRef());
}
static inline LineLayoutItem HighestContainingIsolateWithinRoot(
LineLayoutItem object,
LineLayoutItem root) {
DCHECK(object);
LineLayoutItem containing_isolate_obj(nullptr);
while (object && object != root) {
if (IsIsolatedInline(object))
containing_isolate_obj = LineLayoutItem(object);
object = object.Parent();
DCHECK(object);
}
return containing_isolate_obj;
}
static inline unsigned NumberOfIsolateAncestors(const InlineIterator& iter) {
LineLayoutItem object = iter.GetLineLayoutItem();
if (!object)
return 0;
unsigned count = 0;
while (object && object != iter.Root()) {
if (IsIsolatedInline(object))
count++;
object = object.Parent();
}
return count;
}
// FIXME: This belongs on InlineBidiResolver, except it's a template
// specialization of BidiResolver which knows nothing about LayoutObjects.
static inline BidiRun* AddPlaceholderRunForIsolatedInline(
InlineBidiResolver& resolver,
LineLayoutItem obj,
unsigned pos,
LineLayoutItem root) {
DCHECK(obj);
BidiRun* isolated_run =
new BidiRun(resolver.Context()->Override(), resolver.Context()->Level(),
pos, pos, obj, resolver.Dir(), resolver.Context()->Dir());
resolver.Runs().AddRun(isolated_run);
// FIXME: isolatedRuns() could be a hash of object->run and then we could
// cheaply ASSERT here that we didn't create multiple objects for the same
// inline.
resolver.IsolatedRuns().push_back(BidiIsolatedRun(
obj, pos, root, *isolated_run, resolver.Context()->Level()));
return isolated_run;
}
static inline BidiRun* CreateRun(int start,
int end,
LineLayoutItem obj,
InlineBidiResolver& resolver) {
return new BidiRun(resolver.Context()->Override(),
resolver.Context()->Level(), start, end, obj,
resolver.Dir(), resolver.Context()->Dir());
}
enum AppendRunBehavior { kAppendingFakeRun, kAppendingRunsForObject };
class IsolateTracker {
STACK_ALLOCATED();
public:
explicit IsolateTracker(BidiRunList<BidiRun>& runs,
unsigned nested_isolate_count)
: nested_isolate_count_(nested_isolate_count),
have_added_fake_run_for_root_isolate_(false),
runs_(runs) {}
void SetMidpointStateForRootIsolate(const LineMidpointState& midpoint_state) {
midpoint_state_for_root_isolate_ = midpoint_state;
}
void EnterIsolate() { nested_isolate_count_++; }
void ExitIsolate() {
DCHECK_GE(nested_isolate_count_, 1u);
nested_isolate_count_--;
if (!InIsolate())
have_added_fake_run_for_root_isolate_ = false;
}
bool InIsolate() const { return nested_isolate_count_; }
// We don't care if we encounter bidi directional overrides.
void Embed(WTF::unicode::CharDirection, BidiEmbeddingSource) {}
void CommitExplicitEmbedding(BidiRunList<BidiRun>&) {}
BidiRunList<BidiRun>& Runs() { return runs_; }
void AddFakeRunIfNecessary(LineLayoutItem obj,
unsigned pos,
unsigned end,
LineLayoutItem root,
InlineBidiResolver& resolver) {
// We only need to add a fake run for a given isolated span once during each
// call to createBidiRunsForLine. We'll be called for every span inside the
// isolated span so we just ignore subsequent calls.
// We also avoid creating a fake run until we hit a child that warrants one,
// e.g. we skip floats.
if (LayoutBlockFlow::ShouldSkipCreatingRunsForObject(obj))
return;
if (!have_added_fake_run_for_root_isolate_) {
BidiRun* run =
AddPlaceholderRunForIsolatedInline(resolver, obj, pos, root);
resolver.SetMidpointStateForIsolatedRun(*run,
midpoint_state_for_root_isolate_);
have_added_fake_run_for_root_isolate_ = true;
}
// obj and pos together denote a single position in the inline, from which
// the parsing of the isolate will start.
// We don't need to mark the end of the run because this is implicit: it is
// either endOfLine or the end of the isolate, when we call
// createBidiRunsForLine it will stop at whichever comes first.
}
private:
unsigned nested_isolate_count_;
bool have_added_fake_run_for_root_isolate_;
LineMidpointState midpoint_state_for_root_isolate_;
BidiRunList<BidiRun>& runs_;
};
static void inline AppendRunObjectIfNecessary(LineLayoutItem obj,
unsigned start,
unsigned end,
LineLayoutItem root,
InlineBidiResolver& resolver,
AppendRunBehavior behavior,
IsolateTracker& tracker) {
// Trailing space code creates empty BidiRun objects, start == end, so
// that case needs to be handled specifically.
bool add_empty_run = (end == start);
// Append BidiRun objects, at most 64K chars at a time, until all
// text between |start| and |end| is represented.
while (end > start || add_empty_run) {
add_empty_run = false;
const int kLimit =
USHRT_MAX; // InlineTextBox stores text length as uint16_t.
unsigned limited_end = end;
if (end - start > kLimit)
limited_end = start + kLimit;
if (behavior == kAppendingFakeRun)
tracker.AddFakeRunIfNecessary(obj, start, limited_end, root, resolver);
else
resolver.Runs().AddRun(CreateRun(start, limited_end, obj, resolver));
start = limited_end;
}
}
static void AdjustMidpointsAndAppendRunsForObjectIfNeeded(
LineLayoutItem obj,
unsigned start,
unsigned end,
LineLayoutItem root,
InlineBidiResolver& resolver,
AppendRunBehavior behavior,
IsolateTracker& tracker) {
if (start > end || LayoutBlockFlow::ShouldSkipCreatingRunsForObject(obj))
return;
for (;;) {
LineMidpointState& line_midpoint_state = resolver.GetMidpointState();
bool have_next_midpoint = (line_midpoint_state.CurrentMidpoint() <
line_midpoint_state.NumMidpoints());
InlineIterator next_midpoint;
if (have_next_midpoint)
next_midpoint = line_midpoint_state
.Midpoints()[line_midpoint_state.CurrentMidpoint()];
if (line_midpoint_state.BetweenMidpoints()) {
if (!(have_next_midpoint && next_midpoint.GetLineLayoutItem() == obj))
return;
// This is a new start point. Stop ignoring objects and
// adjust our start.
line_midpoint_state.SetBetweenMidpoints(false);
start = next_midpoint.Offset();
line_midpoint_state.IncrementCurrentMidpoint();
if (start < end)
continue;
} else {
if (!have_next_midpoint || (obj != next_midpoint.GetLineLayoutItem())) {
AppendRunObjectIfNecessary(obj, start, end, root, resolver, behavior,
tracker);
return;
}
// An end midpoint has been encountered within our object. We
// need to go ahead and append a run with our endpoint.
if (next_midpoint.Offset() + 1 <= end) {
line_midpoint_state.SetBetweenMidpoints(true);
line_midpoint_state.IncrementCurrentMidpoint();
// UINT_MAX means stop at the object and don't include any of it.
if (next_midpoint.Offset() != UINT_MAX) {
if (next_midpoint.Offset() + 1 > start)
AppendRunObjectIfNecessary(obj, start, next_midpoint.Offset() + 1,
root, resolver, behavior, tracker);
start = next_midpoint.Offset() + 1;
continue;
}
} else {
AppendRunObjectIfNecessary(obj, start, end, root, resolver, behavior,
tracker);
}
}
return;
}
}
static inline void AddFakeRunIfNecessary(LineLayoutItem obj,
unsigned start,
unsigned end,
LineLayoutItem root,
InlineBidiResolver& resolver,
IsolateTracker& tracker) {
tracker.SetMidpointStateForRootIsolate(resolver.GetMidpointState());
AdjustMidpointsAndAppendRunsForObjectIfNeeded(
obj, start, obj.length(), root, resolver, kAppendingFakeRun, tracker);
}
template <>
inline void InlineBidiResolver::AppendRun(BidiRunList<BidiRun>& runs) {
if (!empty_run_ && !eor_.AtEnd() && !reached_end_of_line_) {
// Keep track of when we enter/leave "unicode-bidi: isolate" inlines.
// Initialize our state depending on if we're starting in the middle of such
// an inline.
// FIXME: Could this initialize from InIsolate() instead of walking up
// the layout tree?
IsolateTracker isolate_tracker(runs, NumberOfIsolateAncestors(sor_));
int start = sor_.Offset();
LineLayoutItem obj = sor_.GetLineLayoutItem();
while (obj && obj != eor_.GetLineLayoutItem() &&
obj != end_of_run_at_end_of_line_.GetLineLayoutItem()) {
if (isolate_tracker.InIsolate())
AddFakeRunIfNecessary(obj, start, obj.length(), sor_.Root(), *this,
isolate_tracker);
else
AdjustMidpointsAndAppendRunsForObjectIfNeeded(
obj, start, obj.length(), sor_.Root(), *this,
kAppendingRunsForObject, isolate_tracker);
// FIXME: start/obj should be an InlineIterator instead of two separate
// variables.
start = 0;
obj = BidiNextSkippingEmptyInlines(sor_.Root(), obj, &isolate_tracker);
}
bool is_end_of_line =
obj == end_of_line_.GetLineLayoutItem() && !end_of_line_.Offset();
if (obj && !is_end_of_line) {
unsigned pos = obj == eor_.GetLineLayoutItem() ? eor_.Offset() : INT_MAX;
if (obj == end_of_run_at_end_of_line_.GetLineLayoutItem() &&
end_of_run_at_end_of_line_.Offset() <= pos) {
reached_end_of_line_ = true;
pos = end_of_run_at_end_of_line_.Offset();
}
// It's OK to add runs for zero-length LayoutObjects, just don't make the
// run larger than it should be
int end = obj.length() ? pos + 1 : 0;
if (isolate_tracker.InIsolate())
AddFakeRunIfNecessary(obj, start, end, sor_.Root(), *this,
isolate_tracker);
else
AdjustMidpointsAndAppendRunsForObjectIfNeeded(
obj, start, end, sor_.Root(), *this, kAppendingRunsForObject,
isolate_tracker);
}
if (is_end_of_line)
reached_end_of_line_ = true;
// If isolate_tracker is InIsolate, the next |start of run| can not be the
// current isolated layoutObject.
if (isolate_tracker.InIsolate())
eor_.MoveTo(
BidiNextSkippingEmptyInlines(eor_.Root(), eor_.GetLineLayoutItem()),
0);
else
eor_.Increment();
sor_ = eor_;
}
direction_ = WTF::unicode::kOtherNeutral;
status_.eor = WTF::unicode::kOtherNeutral;
}
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LINE_INLINE_ITERATOR_H_