/* * 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