blob: ef45be4064a198de3e13b08589377a23a320d472 [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.
*
*/
#pragma once
#include "BidiRun.h"
#include "RenderBlockFlow.h"
#include "RenderChildIterator.h"
#include "RenderInline.h"
#include "RenderText.h"
#include <wtf/StdLibExtras.h>
namespace WebCore {
struct BidiIsolatedRun {
BidiIsolatedRun(RenderObject& object, unsigned position, RenderElement& root, BidiRun& runToReplace)
: object(object)
, root(root)
, runToReplace(runToReplace)
, position(position)
{
}
RenderObject& object;
RenderElement& root;
BidiRun& runToReplace;
unsigned position;
};
// This class is used to RenderInline subtrees, stepping by character within the
// text children. LegacyInlineIterator will use next to find the next RenderText
// optionally notifying a BidiResolver every time it steps into/out of a RenderInline.
class LegacyInlineIterator {
public:
LegacyInlineIterator()
{
}
LegacyInlineIterator(RenderElement* root, RenderObject* o, unsigned p)
: m_root(root)
, m_renderer(o)
, m_pos(p)
, m_refersToEndOfPreviousNode(false)
{
}
void clear()
{
setRenderer(nullptr);
setOffset(0);
setNextBreakablePosition(std::numeric_limits<unsigned>::max());
}
void moveToStartOf(RenderObject& object)
{
moveTo(object, 0);
}
void moveTo(RenderObject& object, unsigned offset, std::optional<unsigned> nextBreak = std::optional<unsigned>())
{
setRenderer(&object);
setOffset(offset);
setNextBreakablePosition(nextBreak);
}
RenderObject* renderer() const { return m_renderer; }
void setRenderer(RenderObject* renderer) { m_renderer = renderer; }
unsigned offset() const { return m_pos; }
void setOffset(unsigned position);
RenderElement* root() const { return m_root; }
std::optional<unsigned> nextBreakablePosition() const { return m_nextBreakablePosition; }
void setNextBreakablePosition(std::optional<unsigned> position) { m_nextBreakablePosition = position; }
bool refersToEndOfPreviousNode() const { return m_refersToEndOfPreviousNode; }
void setRefersToEndOfPreviousNode();
void fastIncrementInTextNode();
void incrementByCodePointInTextNode();
void increment(InlineBidiResolver* = nullptr);
void fastDecrement();
bool atEnd() const;
bool atTextParagraphSeparator() const
{
return is<RenderText>(m_renderer) && m_renderer->preservesNewline() && downcast<RenderText>(*m_renderer).characterAt(m_pos) == '\n';
}
bool atParagraphSeparator() const
{
return (m_renderer && m_renderer->isBR()) || atTextParagraphSeparator();
}
UChar current() const;
UChar previousInSameNode() const;
ALWAYS_INLINE UCharDirection direction() const;
private:
UChar characterAt(unsigned) const;
UCharDirection surrogateTextDirection(UChar currentCodeUnit) const;
RenderElement* m_root { nullptr };
RenderObject* m_renderer { nullptr };
std::optional<unsigned> m_nextBreakablePosition;
unsigned m_pos { 0 };
// There are a couple places where we want to decrement an LegacyInlineIterator.
// Usually this take the form of decrementing m_pos; however, m_pos might be 0.
// However, we shouldn't ever need to decrement an LegacyInlineIterator more than
// once, so rather than implementing a decrement() function which traverses
// nodes, we can simply keep track of this state and handle it.
bool m_refersToEndOfPreviousNode { false };
};
inline bool operator==(const LegacyInlineIterator& it1, const LegacyInlineIterator& it2)
{
return it1.offset() == it2.offset() && it1.renderer() == it2.renderer();
}
inline bool operator!=(const LegacyInlineIterator& it1, const LegacyInlineIterator& it2)
{
return it1.offset() != it2.offset() || it1.renderer() != it2.renderer();
}
static inline UCharDirection embedCharFromDirection(TextDirection direction, UnicodeBidi unicodeBidi)
{
if (unicodeBidi == UnicodeBidi::Embed)
return direction == TextDirection::RTL ? U_RIGHT_TO_LEFT_EMBEDDING : U_LEFT_TO_RIGHT_EMBEDDING;
return direction == TextDirection::RTL ? U_RIGHT_TO_LEFT_OVERRIDE : U_LEFT_TO_RIGHT_OVERRIDE;
}
template <class Observer>
static inline void notifyObserverEnteredObject(Observer* observer, RenderObject* object)
{
if (!observer || !object || !object->isRenderInline())
return;
const RenderStyle& style = object->style();
auto unicodeBidi = style.unicodeBidi();
if (unicodeBidi == UnicodeBidi::Normal) {
// http://dev.w3.org/csswg/css3-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 (isIsolated(unicodeBidi)) {
// Make sure that explicit embeddings are committed before we enter the isolated content.
observer->commitExplicitEmbedding();
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(), unicodeBidi), FromStyleOrDOM);
}
template <class Observer>
static inline void notifyObserverWillExitObject(Observer* observer, RenderObject* object)
{
if (!observer || !object || !object->isRenderInline())
return;
auto unicodeBidi = object->style().unicodeBidi();
if (unicodeBidi == UnicodeBidi::Normal)
return; // Nothing to do for unicode-bidi: normal
if (isIsolated(unicodeBidi)) {
observer->exitIsolate();
return;
}
// Otherwise we pop any embed/override character we added when we opened this tag.
if (!observer->inIsolate())
observer->embed(U_POP_DIRECTIONAL_FORMAT, FromStyleOrDOM);
}
static inline bool isIteratorTarget(RenderObject* object)
{
ASSERT(object); // The iterator will of course return 0, but its not an expected argument to this function.
return object->isTextOrLineBreak() || object->isFloating() || object->isOutOfFlowPositioned() || object->isReplacedOrInlineBlock();
}
template <class Observer>
static inline RenderObject* nextInlineRendererSkippingEmpty(RenderElement& root, RenderObject* current, Observer* observer)
{
RenderObject* next = nullptr;
while (current) {
next = nullptr;
if (!isIteratorTarget(current)) {
next = downcast<RenderElement>(*current).firstChild();
notifyObserverEnteredObject(observer, next);
}
if (!next) {
while (current && current != &root) {
notifyObserverWillExitObject(observer, current);
next = current->nextSibling();
if (next) {
notifyObserverEnteredObject(observer, next);
break;
}
current = current->parent();
}
}
if (!next)
break;
if (isIteratorTarget(next) || (is<RenderInline>(*next) && isEmptyInline(downcast<RenderInline>(*next))))
break;
current = next;
}
return next;
}
// This makes callers cleaner as they don't have to specify a type for the observer when not providing one.
static inline RenderObject* nextInlineRendererSkippingEmpty(RenderElement& root, RenderObject* current)
{
InlineBidiResolver* observer = nullptr;
return nextInlineRendererSkippingEmpty(root, current, observer);
}
static inline RenderObject* firstInlineRendererSkippingEmpty(RenderElement& root, InlineBidiResolver* resolver = nullptr)
{
RenderObject* renderer = root.firstChild();
if (!renderer)
return nullptr;
if (is<RenderInline>(*renderer)) {
notifyObserverEnteredObject(resolver, renderer);
if (!isEmptyInline(downcast<RenderInline>(*renderer)))
renderer = nextInlineRendererSkippingEmpty(root, renderer, resolver);
else {
// Never skip empty inlines.
if (resolver)
resolver->commitExplicitEmbedding();
return renderer;
}
}
// FIXME: Unify this with the next call above.
if (renderer && !isIteratorTarget(renderer))
renderer = nextInlineRendererSkippingEmpty(root, renderer, resolver);
if (resolver)
resolver->commitExplicitEmbedding();
return renderer;
}
inline void LegacyInlineIterator::fastIncrementInTextNode()
{
ASSERT(m_renderer);
ASSERT(m_pos <= downcast<RenderText>(*m_renderer).text().length());
++m_pos;
}
inline void LegacyInlineIterator::incrementByCodePointInTextNode()
{
ASSERT(m_renderer);
const auto& text = downcast<RenderText>(*m_renderer).text();
ASSERT(m_pos < text.length());
if (text.is8Bit()) {
++m_pos;
return;
}
UChar32 character;
U16_NEXT(text.characters16(), m_pos, text.length(), character);
}
inline void LegacyInlineIterator::setOffset(unsigned position)
{
ASSERT(position <= UINT_MAX - 10); // Sanity check
m_pos = position;
}
inline void LegacyInlineIterator::setRefersToEndOfPreviousNode()
{
ASSERT(!m_pos);
ASSERT(!m_refersToEndOfPreviousNode);
m_refersToEndOfPreviousNode = true;
}
inline void LegacyInlineIterator::increment(InlineBidiResolver* resolver)
{
if (!m_renderer)
return;
if (is<RenderText>(*m_renderer)) {
fastIncrementInTextNode();
if (m_pos < downcast<RenderText>(*m_renderer).text().length())
return;
}
// next can return nullptr
RenderObject* next = nextInlineRendererSkippingEmpty(*m_root, m_renderer, resolver);
if (next)
moveToStartOf(*next);
else
clear();
}
inline void LegacyInlineIterator::fastDecrement()
{
ASSERT(!refersToEndOfPreviousNode());
if (m_pos)
setOffset(m_pos - 1);
else
setRefersToEndOfPreviousNode();
}
inline bool LegacyInlineIterator::atEnd() const
{
return !m_renderer;
}
inline UChar LegacyInlineIterator::characterAt(unsigned index) const
{
if (!is<RenderText>(m_renderer))
return 0;
return downcast<RenderText>(*m_renderer).characterAt(index);
}
inline UChar LegacyInlineIterator::current() const
{
return characterAt(m_pos);
}
inline UChar LegacyInlineIterator::previousInSameNode() const
{
return characterAt(m_pos - 1);
}
ALWAYS_INLINE UCharDirection LegacyInlineIterator::direction() const
{
if (UNLIKELY(!m_renderer))
return U_OTHER_NEUTRAL;
if (LIKELY(is<RenderText>(*m_renderer))) {
UChar codeUnit = downcast<RenderText>(*m_renderer).characterAt(m_pos);
if (LIKELY(U16_IS_SINGLE(codeUnit)))
return u_charDirection(codeUnit);
return surrogateTextDirection(codeUnit);
}
if (m_renderer->isListMarker())
return m_renderer->style().isLeftToRightDirection() ? U_LEFT_TO_RIGHT : U_RIGHT_TO_LEFT;
return U_OTHER_NEUTRAL;
}
template<>
inline void InlineBidiResolver::incrementInternal()
{
m_current.increment(this);
}
static inline bool isIsolatedInline(RenderObject& object)
{
return object.isRenderInline() && isIsolated(object.style().unicodeBidi());
}
static inline RenderObject* highestContainingIsolateWithinRoot(RenderObject& initialObject, RenderObject* root)
{
RenderObject* containingIsolateObject = nullptr;
for (RenderObject* object = &initialObject; object && object != root; object = object->parent()) {
if (isIsolatedInline(*object))
containingIsolateObject = object;
}
return containingIsolateObject;
}
static inline unsigned numberOfIsolateAncestors(const LegacyInlineIterator& iter)
{
unsigned count = 0;
typedef RenderObject* RenderObjectPtr;
for (RenderObjectPtr object = iter.renderer(), root = iter.root(); object && object != root; object = object->parent()) {
if (isIsolatedInline(*object))
count++;
}
return count;
}
// FIXME: This belongs on InlineBidiResolver, except it's a template specialization
// of BidiResolver which knows nothing about RenderObjects.
static inline void addPlaceholderRunForIsolatedInline(InlineBidiResolver& resolver, RenderObject& obj, unsigned pos, RenderElement& root)
{
std::unique_ptr<BidiRun> isolatedRun = makeUnique<BidiRun>(pos, pos, obj, resolver.context(), resolver.dir());
// 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.setWhitespaceCollapsingTransitionForIsolatedRun(*isolatedRun, resolver.whitespaceCollapsingState().currentTransition());
resolver.isolatedRuns().append(BidiIsolatedRun(obj, pos, root, *isolatedRun));
resolver.runs().appendRun(WTFMove(isolatedRun));
}
class IsolateTracker {
public:
explicit IsolateTracker(unsigned nestedIsolateCount)
: m_nestedIsolateCount(nestedIsolateCount)
, m_haveAddedFakeRunForRootIsolate(false)
{
}
void enterIsolate() { m_nestedIsolateCount++; }
void exitIsolate()
{
ASSERT(m_nestedIsolateCount >= 1);
m_nestedIsolateCount--;
if (!inIsolate())
m_haveAddedFakeRunForRootIsolate = false;
}
bool inIsolate() const { return m_nestedIsolateCount; }
// We don't care if we encounter bidi directional overrides.
void embed(UCharDirection, BidiEmbeddingSource) { }
void commitExplicitEmbedding() { }
void addFakeRunIfNecessary(RenderObject& obj, unsigned pos, unsigned end, RenderElement& 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 (RenderBlock::shouldSkipCreatingRunsForObject(obj))
return;
if (!m_haveAddedFakeRunForRootIsolate) {
// 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.
addPlaceholderRunForIsolatedInline(resolver, obj, pos, root);
}
m_haveAddedFakeRunForRootIsolate = true;
LegacyLineLayout::appendRunsForObject(nullptr, pos, end, obj, resolver);
}
private:
unsigned m_nestedIsolateCount;
bool m_haveAddedFakeRunForRootIsolate;
};
template<>
inline void InlineBidiResolver::appendRunInternal()
{
if (!m_emptyRun && !m_eor.atEnd() && !m_reachedEndOfLine) {
// 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 this->inIsolate() instead of walking up the render tree?
IsolateTracker isolateTracker(numberOfIsolateAncestors(m_sor));
int start = m_sor.offset();
RenderObject* obj = m_sor.renderer();
while (obj && obj != m_eor.renderer() && obj != endOfLine.renderer()) {
if (isolateTracker.inIsolate())
isolateTracker.addFakeRunIfNecessary(*obj, start, obj->length(), *m_sor.root(), *this);
else
LegacyLineLayout::appendRunsForObject(&m_runs, start, obj->length(), *obj, *this);
// FIXME: start/obj should be an LegacyInlineIterator instead of two separate variables.
start = 0;
obj = nextInlineRendererSkippingEmpty(*m_sor.root(), obj, &isolateTracker);
}
if (obj) {
unsigned pos = obj == m_eor.renderer() ? m_eor.offset() : UINT_MAX;
if (obj == endOfLine.renderer() && endOfLine.offset() <= pos) {
m_reachedEndOfLine = true;
pos = endOfLine.offset();
}
// It's OK to add runs for zero-length RenderObjects, just don't make the run larger than it should be
int end = obj->length() ? pos + 1 : 0;
if (isolateTracker.inIsolate())
isolateTracker.addFakeRunIfNecessary(*obj, start, obj->length(), *m_sor.root(), *this);
else
LegacyLineLayout::appendRunsForObject(&m_runs, start, end, *obj, *this);
}
m_eor.increment();
m_sor = m_eor;
}
m_direction = U_OTHER_NEUTRAL;
m_status.eor = U_OTHER_NEUTRAL;
}
template<>
inline bool InlineBidiResolver::needsContinuePastEndInternal() const
{
// We don't collect runs beyond the endOfLine renderer. Stop traversing when the iterator moves to the next renderer to prevent O(n^2).
return m_current.renderer() == endOfLine.renderer();
}
} // namespace WebCore