/*
* 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 InlineIterator_h
#define InlineIterator_h
#include "BidiRun.h"
#include "RenderBlock.h"
#include "RenderText.h"
#include <wtf/AlwaysInline.h>
#include <wtf/StdLibExtras.h>
namespace WebCore {
class InlineIterator {
public:
InlineIterator()
: m_root(0)
, m_obj(0)
, m_pos(0)
, m_nextBreakablePosition(-1)
{
}
InlineIterator(RenderObject* root, RenderObject* o, unsigned p)
: m_root(root)
, m_obj(o)
, m_pos(p)
, m_nextBreakablePosition(-1)
{
}
void clear() { moveTo(0, 0); }
void moveToStartOf(RenderObject* object)
{
moveTo(object, 0);
}
void moveTo(RenderObject* object, unsigned offset, int nextBreak = -1)
{
m_obj = object;
m_pos = offset;
m_nextBreakablePosition = nextBreak;
}
RenderObject* root() const { return m_root; }
void increment(InlineBidiResolver* = 0);
bool atEnd() const;
inline bool atTextParagraphSeparator()
{
return m_obj && m_obj->preservesNewline() && m_obj->isText() && toRenderText(m_obj)->textLength()
&& !toRenderText(m_obj)->isWordBreak() && toRenderText(m_obj)->characters()[m_pos] == '\n';
}
inline bool atParagraphSeparator()
{
return (m_obj && m_obj->isBR()) || atTextParagraphSeparator();
}
UChar current() const;
ALWAYS_INLINE WTF::Unicode::Direction direction() const;
private:
RenderObject* m_root;
// FIXME: These should be private.
public:
RenderObject* m_obj;
unsigned m_pos;
int m_nextBreakablePosition;
};
inline bool operator==(const InlineIterator& it1, const InlineIterator& it2)
{
return it1.m_pos == it2.m_pos && it1.m_obj == it2.m_obj;
}
inline bool operator!=(const InlineIterator& it1, const InlineIterator& it2)
{
return it1.m_pos != it2.m_pos || it1.m_obj != it2.m_obj;
}
static inline WTF::Unicode::Direction embedCharFromDirection(TextDirection dir, EUnicodeBidi unicodeBidi)
{
using namespace WTF::Unicode;
if (unicodeBidi == Embed)
return dir == RTL ? RightToLeftEmbedding : LeftToRightEmbedding;
return dir == RTL ? RightToLeftOverride : LeftToRightOverride;
}
static inline void notifyResolverEnteredObject(InlineBidiResolver* resolver, RenderObject* object)
{
if (!resolver || !object || !object->isRenderInline())
return;
RenderStyle* style = object->style();
EUnicodeBidi unicodeBidi = style->unicodeBidi();
if (unicodeBidi == UBNormal)
return;
resolver->embed(embedCharFromDirection(style->direction(), unicodeBidi), FromStyleOrDOM);
}
static inline void notifyResolverWillExitObject(InlineBidiResolver* resolver, RenderObject* object)
{
if (!resolver || !object || !object->isRenderInline())
return;
if (object->style()->unicodeBidi() == UBNormal)
return;
resolver->embed(WTF::Unicode::PopDirectionalFormat, FromStyleOrDOM);
}
// 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).
static inline RenderObject* bidiNext(RenderObject* root, RenderObject* current, InlineBidiResolver* resolver = 0, bool skipInlines = true, bool* endOfInlinePtr = 0)
{
RenderObject* next = 0;
bool oldEndOfInline = endOfInlinePtr ? *endOfInlinePtr : false;
bool endOfInline = false;
while (current) {
next = 0;
if (!oldEndOfInline && !current->isFloating() && !current->isReplaced() && !current->isPositioned() && !current->isText()) {
next = current->firstChild();
notifyResolverEnteredObject(resolver, next);
}
if (!next) {
if (!skipInlines && !oldEndOfInline && current->isRenderInline()) {
next = current;
endOfInline = true;
break;
}
while (current && current != root) {
notifyResolverWillExitObject(resolver, current);
next = current->nextSibling();
if (next) {
notifyResolverEnteredObject(resolver, next);
break;
}
current = current->parent();
if (!skipInlines && current && current != root && current->isRenderInline()) {
next = current;
endOfInline = true;
break;
}
}
}
if (!next)
break;
if (next->isText() || next->isFloating() || next->isReplaced() || next->isPositioned()
|| ((!skipInlines || !next->firstChild()) // Always return EMPTY inlines.
&& next->isRenderInline()))
break;
current = next;
}
if (endOfInlinePtr)
*endOfInlinePtr = endOfInline;
return next;
}
static inline RenderObject* bidiFirst(RenderObject* root, InlineBidiResolver* resolver, bool skipInlines = true)
{
if (!root->firstChild())
return 0;
RenderObject* o = root->firstChild();
if (o->isRenderInline()) {
notifyResolverEnteredObject(resolver, o);
if (skipInlines && o->firstChild())
o = bidiNext(root, o, resolver, skipInlines);
else {
// Never skip empty inlines.
if (resolver)
resolver->commitExplicitEmbedding();
return o;
}
}
if (o && !o->isText() && !o->isReplaced() && !o->isFloating() && !o->isPositioned())
o = bidiNext(root, o, resolver, skipInlines);
if (resolver)
resolver->commitExplicitEmbedding();
return o;
}
inline void InlineIterator::increment(InlineBidiResolver* resolver)
{
if (!m_obj)
return;
if (m_obj->isText()) {
m_pos++;
if (m_pos < toRenderText(m_obj)->textLength())
return;
}
// bidiNext can return 0, so use moveTo instead of moveToStartOf
moveTo(bidiNext(m_root, m_obj, resolver), 0);
}
inline bool InlineIterator::atEnd() const
{
return !m_obj;
}
inline UChar InlineIterator::current() const
{
if (!m_obj || !m_obj->isText())
return 0;
RenderText* text = toRenderText(m_obj);
if (m_pos >= text->textLength())
return 0;
return text->characters()[m_pos];
}
ALWAYS_INLINE WTF::Unicode::Direction InlineIterator::direction() const
{
if (UChar c = current())
return WTF::Unicode::direction(c);
if (m_obj && m_obj->isListMarker())
return m_obj->style()->isLeftToRightDirection() ? WTF::Unicode::LeftToRight : WTF::Unicode::RightToLeft;
return WTF::Unicode::OtherNeutral;
}
template<>
inline void InlineBidiResolver::increment()
{
m_current.increment(this);
}
template <>
inline void InlineBidiResolver::appendRun()
{
if (!m_emptyRun && !m_eor.atEnd()) {
int start = m_sor.m_pos;
RenderObject* obj = m_sor.m_obj;
while (obj && obj != m_eor.m_obj && obj != endOfLine.m_obj) {
RenderBlock::appendRunsForObject(m_runs, start, obj->length(), obj, *this);
start = 0;
obj = bidiNext(m_sor.root(), obj);
}
if (obj) {
unsigned pos = obj == m_eor.m_obj ? m_eor.m_pos : UINT_MAX;
if (obj == endOfLine.m_obj && endOfLine.m_pos <= pos) {
m_reachedEndOfLine = true;
pos = endOfLine.m_pos;
}
// 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;
RenderBlock::appendRunsForObject(m_runs, start, end, obj, *this);
}
m_eor.increment();
m_sor = m_eor;
}
m_direction = WTF::Unicode::OtherNeutral;
m_status.eor = WTF::Unicode::OtherNeutral;
}
}
#endif // InlineIterator_h