blob: 1eda57c50a7d62359fed649864da4c35ecc8abbf [file] [log] [blame]
/*
* Copyright (C) 2000 Lars Knoll (knoll@kde.org)
* Copyright (C) 2003, 2004, 2006, 2007, 2008 Apple Inc. All right 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 BidiResolver_h
#define BidiResolver_h
#include "platform/text/BidiCharacterRun.h"
#include "platform/text/BidiContext.h"
#include "platform/text/BidiRunList.h"
#include "platform/text/TextDirection.h"
#include "wtf/Allocator.h"
#include "wtf/HashMap.h"
#include "wtf/Noncopyable.h"
#include "wtf/PassRefPtr.h"
#include "wtf/Vector.h"
namespace blink {
class LayoutObject;
template <class Iterator>
class MidpointState final {
DISALLOW_NEW();
public:
MidpointState() { reset(); }
void reset() {
m_numMidpoints = 0;
m_currentMidpoint = 0;
m_betweenMidpoints = false;
}
void startIgnoringSpaces(const Iterator& midpoint) {
ASSERT(!(m_numMidpoints % 2));
addMidpoint(midpoint);
}
void stopIgnoringSpaces(const Iterator& midpoint) {
ASSERT(m_numMidpoints % 2);
addMidpoint(midpoint);
}
// Adding a pair of midpoints before a character will split it out into a new
// line box.
void ensureCharacterGetsLineBox(Iterator& textParagraphSeparator) {
startIgnoringSpaces(Iterator(0, textParagraphSeparator.getLineLayoutItem(),
textParagraphSeparator.offset() - 1));
stopIgnoringSpaces(Iterator(0, textParagraphSeparator.getLineLayoutItem(),
textParagraphSeparator.offset()));
}
void checkMidpoints(Iterator& lBreak) {
// Check to see if our last midpoint is a start point beyond the line break.
// If so, shave it off the list, and shave off a trailing space if the
// previous end point doesn't preserve whitespace.
if (lBreak.getLineLayoutItem() && m_numMidpoints && !(m_numMidpoints % 2)) {
Iterator* midpointsIterator = m_midpoints.data();
Iterator& endpoint = midpointsIterator[m_numMidpoints - 2];
const Iterator& startpoint = midpointsIterator[m_numMidpoints - 1];
Iterator currpoint = endpoint;
while (!currpoint.atEnd() && currpoint != startpoint &&
currpoint != lBreak)
currpoint.increment();
if (currpoint == lBreak) {
// We hit the line break before the start point. Shave off the start
// point.
m_numMidpoints--;
if (endpoint.getLineLayoutItem().style()->collapseWhiteSpace() &&
endpoint.getLineLayoutItem().isText())
endpoint.setOffset(endpoint.offset() - 1);
}
}
}
Vector<Iterator>& midpoints() { return m_midpoints; }
const unsigned& numMidpoints() const { return m_numMidpoints; }
const unsigned& currentMidpoint() const { return m_currentMidpoint; }
void incrementCurrentMidpoint() { m_currentMidpoint++; }
const bool& betweenMidpoints() const { return m_betweenMidpoints; }
void setBetweenMidpoints(bool betweenMidpoint) {
m_betweenMidpoints = betweenMidpoint;
}
private:
// The goal is to reuse the line state across multiple
// lines so we just keep an array around for midpoints and never clear it
// across multiple lines. We track the number of items and position using the
// two other variables.
Vector<Iterator> m_midpoints;
unsigned m_numMidpoints;
unsigned m_currentMidpoint;
bool m_betweenMidpoints;
void addMidpoint(const Iterator& midpoint) {
if (m_midpoints.size() <= m_numMidpoints)
m_midpoints.grow(m_numMidpoints + 10);
Iterator* midpointsIterator = m_midpoints.data();
midpointsIterator[m_numMidpoints++] = midpoint;
}
};
// The BidiStatus at a given position (typically the end of a line) can
// be cached and then used to restart bidi resolution at that position.
struct BidiStatus final {
DISALLOW_NEW();
BidiStatus()
: eor(WTF::Unicode::OtherNeutral),
lastStrong(WTF::Unicode::OtherNeutral),
last(WTF::Unicode::OtherNeutral) {}
// Creates a BidiStatus representing a new paragraph root with a default
// direction. Uses TextDirection as it only has two possibilities instead of
// WTF::Unicode::Direction which has 19.
BidiStatus(TextDirection textDirection, bool isOverride) {
WTF::Unicode::CharDirection direction = textDirection == LTR
? WTF::Unicode::LeftToRight
: WTF::Unicode::RightToLeft;
eor = lastStrong = last = direction;
context = BidiContext::create(textDirection == LTR ? 0 : 1, direction,
isOverride);
}
BidiStatus(WTF::Unicode::CharDirection eorDir,
WTF::Unicode::CharDirection lastStrongDir,
WTF::Unicode::CharDirection lastDir,
PassRefPtr<BidiContext> bidiContext)
: eor(eorDir),
lastStrong(lastStrongDir),
last(lastDir),
context(bidiContext) {}
// Creates a BidiStatus for Isolates (RLI/LRI).
// The rule X5a ans X5b of UAX#9: http://unicode.org/reports/tr9/#X5a
static BidiStatus createForIsolate(TextDirection textDirection,
bool isOverride,
unsigned char level) {
WTF::Unicode::CharDirection direction;
if (textDirection == RTL) {
level = nextGreaterOddLevel(level);
direction = WTF::Unicode::RightToLeft;
} else {
level = nextGreaterEvenLevel(level);
direction = WTF::Unicode::LeftToRight;
}
RefPtr<BidiContext> context =
BidiContext::create(level, direction, isOverride, FromStyleOrDOM);
// This copies BidiStatus and may churn the ref on BidiContext.
// I doubt it matters.
return BidiStatus(direction, direction, direction, context.release());
}
WTF::Unicode::CharDirection eor;
WTF::Unicode::CharDirection lastStrong;
WTF::Unicode::CharDirection last;
RefPtr<BidiContext> context;
};
class BidiEmbedding final {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
BidiEmbedding(WTF::Unicode::CharDirection direction,
BidiEmbeddingSource source)
: m_direction(direction), m_source(source) {}
WTF::Unicode::CharDirection direction() const { return m_direction; }
BidiEmbeddingSource source() const { return m_source; }
private:
WTF::Unicode::CharDirection m_direction;
BidiEmbeddingSource m_source;
};
inline bool operator==(const BidiStatus& status1, const BidiStatus& status2) {
return status1.eor == status2.eor && status1.last == status2.last &&
status1.lastStrong == status2.lastStrong &&
*(status1.context) == *(status2.context);
}
inline bool operator!=(const BidiStatus& status1, const BidiStatus& status2) {
return !(status1 == status2);
}
enum VisualDirectionOverride {
NoVisualOverride,
VisualLeftToRightOverride,
VisualRightToLeftOverride
};
class NoIsolatedRun {};
// BidiResolver is WebKit's implementation of the Unicode Bidi Algorithm
// http://unicode.org/reports/tr9
template <class Iterator, class Run, class IsolatedRun = NoIsolatedRun>
class BidiResolver final {
DISALLOW_NEW();
WTF_MAKE_NONCOPYABLE(BidiResolver);
public:
BidiResolver()
: m_direction(WTF::Unicode::OtherNeutral),
m_reachedEndOfLine(false),
m_emptyRun(true),
m_nestedIsolateCount(0),
m_trailingSpaceRun(0) {}
#if ENABLE(ASSERT)
~BidiResolver();
#endif
const Iterator& position() const { return m_current; }
Iterator& position() { return m_current; }
void setPositionIgnoringNestedIsolates(const Iterator& position) {
m_current = position;
}
void setPosition(const Iterator& position, unsigned nestedIsolatedCount) {
m_current = position;
m_nestedIsolateCount = nestedIsolatedCount;
}
BidiContext* context() const { return m_status.context.get(); }
void setContext(PassRefPtr<BidiContext> c) { m_status.context = c; }
void setLastDir(WTF::Unicode::CharDirection lastDir) {
m_status.last = lastDir;
}
void setLastStrongDir(WTF::Unicode::CharDirection lastStrongDir) {
m_status.lastStrong = lastStrongDir;
}
void setEorDir(WTF::Unicode::CharDirection eorDir) { m_status.eor = eorDir; }
WTF::Unicode::CharDirection dir() const { return m_direction; }
void setDir(WTF::Unicode::CharDirection d) { m_direction = d; }
const BidiStatus& status() const { return m_status; }
void setStatus(const BidiStatus s) {
ASSERT(s.context);
m_status = s;
m_paragraphDirectionality =
s.context->dir() == WTF::Unicode::LeftToRight ? LTR : RTL;
}
MidpointState<Iterator>& midpointState() { return m_midpointState; }
// The current algorithm handles nested isolates one layer of nesting at a
// time. But when we layout each isolated span, we will walk into (and
// ignore) all child isolated spans.
void enterIsolate() { m_nestedIsolateCount++; }
void exitIsolate() {
ASSERT(m_nestedIsolateCount >= 1);
m_nestedIsolateCount--;
}
bool inIsolate() const { return m_nestedIsolateCount; }
void embed(WTF::Unicode::CharDirection, BidiEmbeddingSource);
bool commitExplicitEmbedding(BidiRunList<Run>&);
void createBidiRunsForLine(const Iterator& end,
VisualDirectionOverride = NoVisualOverride,
bool hardLineBreak = false,
bool reorderRuns = true);
BidiRunList<Run>& runs() { return m_runs; }
// FIXME: This used to be part of deleteRuns() but was a layering violation.
// It's unclear if this is still needed.
void markCurrentRunEmpty() { m_emptyRun = true; }
Vector<IsolatedRun>& isolatedRuns() { return m_isolatedRuns; }
bool isEndOfLine(const Iterator& end) {
return m_current == end || m_current.atEnd();
}
TextDirection determineParagraphDirectionality(
bool* hasStrongDirectionality = 0) {
bool breakOnParagraph = true;
return determineDirectionalityInternal(breakOnParagraph,
hasStrongDirectionality);
}
TextDirection determineDirectionality(bool* hasStrongDirectionality = 0) {
bool breakOnParagraph = false;
return determineDirectionalityInternal(breakOnParagraph,
hasStrongDirectionality);
}
void setMidpointStateForIsolatedRun(Run&, const MidpointState<Iterator>&);
MidpointState<Iterator> midpointStateForIsolatedRun(Run&);
Iterator endOfLine() const { return m_endOfLine; }
Run* trailingSpaceRun() const { return m_trailingSpaceRun; }
protected:
void increment() { m_current.increment(); }
// FIXME: Instead of InlineBidiResolvers subclassing this method, we should
// pass in some sort of Traits object which knows how to create runs for
// appending.
void appendRun(BidiRunList<Run>&);
Run* addTrailingRun(BidiRunList<Run>&,
int,
int,
Run*,
BidiContext*,
TextDirection) const {
return 0;
}
Iterator m_current;
// sor and eor are "start of run" and "end of run" respectively and correpond
// to abreviations used in UBA spec: http://unicode.org/reports/tr9/#BD7
Iterator m_sor; // Points to the first character in the current run.
Iterator m_eor; // Points to the last character in the current run.
Iterator m_last;
BidiStatus m_status;
WTF::Unicode::CharDirection m_direction;
// m_endOfRunAtEndOfLine is "the position last eor in the end of line"
Iterator m_endOfRunAtEndOfLine;
Iterator m_endOfLine;
bool m_reachedEndOfLine;
Iterator m_lastBeforeET; // Before a EuropeanNumberTerminator
bool m_emptyRun;
// FIXME: This should not belong to the resolver, but rather be passed
// into createBidiRunsForLine by the caller.
BidiRunList<Run> m_runs;
MidpointState<Iterator> m_midpointState;
unsigned m_nestedIsolateCount;
Vector<IsolatedRun> m_isolatedRuns;
Run* m_trailingSpaceRun;
TextDirection m_paragraphDirectionality;
private:
void raiseExplicitEmbeddingLevel(BidiRunList<Run>&,
WTF::Unicode::CharDirection from,
WTF::Unicode::CharDirection to);
void lowerExplicitEmbeddingLevel(BidiRunList<Run>&,
WTF::Unicode::CharDirection from);
void checkDirectionInLowerRaiseEmbeddingLevel();
void updateStatusLastFromCurrentDirection(WTF::Unicode::CharDirection);
void reorderRunsFromLevels(BidiRunList<Run>&) const;
bool needsToApplyL1Rule(BidiRunList<Run>&) { return false; }
int findFirstTrailingSpaceAtRun(Run*) { return 0; }
// http://www.unicode.org/reports/tr9/#L1
void applyL1Rule(BidiRunList<Run>&);
TextDirection determineDirectionalityInternal(bool breakOnParagraph,
bool* hasStrongDirectionality);
Vector<BidiEmbedding, 8> m_currentExplicitEmbeddingSequence;
HashMap<Run*, MidpointState<Iterator>> m_midpointStateForIsolatedRun;
};
#if ENABLE(ASSERT)
template <class Iterator, class Run, class IsolatedRun>
BidiResolver<Iterator, Run, IsolatedRun>::~BidiResolver() {
// The owner of this resolver should have handled the isolated runs.
ASSERT(m_isolatedRuns.isEmpty());
ASSERT(!m_runs.runCount());
}
#endif
template <class Iterator, class Run, class IsolatedRun>
void BidiResolver<Iterator, Run, IsolatedRun>::appendRun(
BidiRunList<Run>& runs) {
if (!m_emptyRun && !m_eor.atEnd()) {
unsigned startOffset = m_sor.offset();
unsigned endOffset = m_eor.offset();
if (!m_endOfRunAtEndOfLine.atEnd() &&
endOffset >= m_endOfRunAtEndOfLine.offset()) {
m_reachedEndOfLine = true;
endOffset = m_endOfRunAtEndOfLine.offset();
}
// m_eor and m_endOfRunAtEndOfLine are inclusive while BidiRun's stop is
// exclusive so offset needs to be increased by one.
endOffset += 1;
// Append BidiRun objects, at most 64K chars at a time, until all
// text between |startOffset| and |endOffset| is represented.
while (startOffset < endOffset) {
unsigned end = endOffset;
const int limit =
USHRT_MAX; // InlineTextBox stores text length as unsigned short.
if (end - startOffset > limit)
end = startOffset + limit;
runs.addRun(new Run(startOffset, end, context(), m_direction));
startOffset = end;
}
m_eor.increment();
m_sor = m_eor;
}
m_direction = WTF::Unicode::OtherNeutral;
m_status.eor = WTF::Unicode::OtherNeutral;
}
template <class Iterator, class Run, class IsolatedRun>
void BidiResolver<Iterator, Run, IsolatedRun>::embed(
WTF::Unicode::CharDirection dir,
BidiEmbeddingSource source) {
// Isolated spans compute base directionality during their own UBA run.
// Do not insert fake embed characters once we enter an isolated span.
ASSERT(!inIsolate());
using namespace WTF::Unicode;
ASSERT(dir == PopDirectionalFormat || dir == LeftToRightEmbedding ||
dir == LeftToRightOverride || dir == RightToLeftEmbedding ||
dir == RightToLeftOverride);
m_currentExplicitEmbeddingSequence.append(BidiEmbedding(dir, source));
}
template <class Iterator, class Run, class IsolatedRun>
void BidiResolver<Iterator, Run, IsolatedRun>::
checkDirectionInLowerRaiseEmbeddingLevel() {
using namespace WTF::Unicode;
ASSERT(m_status.eor != OtherNeutral || m_eor.atEnd());
ASSERT(m_status.last != NonSpacingMark && m_status.last != BoundaryNeutral &&
m_status.last != RightToLeftEmbedding &&
m_status.last != LeftToRightEmbedding &&
m_status.last != RightToLeftOverride &&
m_status.last != LeftToRightOverride &&
m_status.last != PopDirectionalFormat);
if (m_direction == OtherNeutral)
m_direction =
m_status.lastStrong == LeftToRight ? LeftToRight : RightToLeft;
}
template <class Iterator, class Run, class IsolatedRun>
void BidiResolver<Iterator, Run, IsolatedRun>::lowerExplicitEmbeddingLevel(
BidiRunList<Run>& runs,
WTF::Unicode::CharDirection from) {
using namespace WTF::Unicode;
if (!m_emptyRun && m_eor != m_last) {
checkDirectionInLowerRaiseEmbeddingLevel();
// bidi.sor ... bidi.eor ... bidi.last eor; need to append the
// bidi.sor-bidi.eor run or extend it through bidi.last
if (from == LeftToRight) {
// bidi.sor ... bidi.eor ... bidi.last L
if (m_status.eor == EuropeanNumber) {
if (m_status.lastStrong != LeftToRight) {
m_direction = EuropeanNumber;
appendRun(runs);
}
} else if (m_status.eor == ArabicNumber) {
m_direction = ArabicNumber;
appendRun(runs);
} else if (m_status.lastStrong != LeftToRight) {
appendRun(runs);
m_direction = LeftToRight;
}
} else if (m_status.eor == EuropeanNumber || m_status.eor == ArabicNumber ||
m_status.lastStrong == LeftToRight) {
appendRun(runs);
m_direction = RightToLeft;
}
m_eor = m_last;
}
appendRun(runs);
m_emptyRun = true;
// sor for the new run is determined by the higher level (rule X10)
setLastDir(from);
setLastStrongDir(from);
m_eor = Iterator();
}
template <class Iterator, class Run, class IsolatedRun>
void BidiResolver<Iterator, Run, IsolatedRun>::raiseExplicitEmbeddingLevel(
BidiRunList<Run>& runs,
WTF::Unicode::CharDirection from,
WTF::Unicode::CharDirection to) {
using namespace WTF::Unicode;
if (!m_emptyRun && m_eor != m_last) {
checkDirectionInLowerRaiseEmbeddingLevel();
// bidi.sor ... bidi.eor ... bidi.last eor; need to append the
// bidi.sor-bidi.eor run or extend it through bidi.last
if (to == LeftToRight) {
// bidi.sor ... bidi.eor ... bidi.last L
if (m_status.eor == EuropeanNumber) {
if (m_status.lastStrong != LeftToRight) {
m_direction = EuropeanNumber;
appendRun(runs);
}
} else if (m_status.eor == ArabicNumber) {
m_direction = ArabicNumber;
appendRun(runs);
} else if (m_status.lastStrong != LeftToRight && from == LeftToRight) {
appendRun(runs);
m_direction = LeftToRight;
}
} else if (m_status.eor == ArabicNumber ||
(m_status.eor == EuropeanNumber &&
(m_status.lastStrong != LeftToRight || from == RightToLeft)) ||
(m_status.eor != EuropeanNumber &&
m_status.lastStrong == LeftToRight && from == RightToLeft)) {
appendRun(runs);
m_direction = RightToLeft;
}
m_eor = m_last;
}
appendRun(runs);
m_emptyRun = true;
setLastDir(to);
setLastStrongDir(to);
m_eor = Iterator();
}
template <class Iterator, class Run, class IsolatedRun>
void BidiResolver<Iterator, Run, IsolatedRun>::applyL1Rule(
BidiRunList<Run>& runs) {
ASSERT(runs.runCount());
if (!needsToApplyL1Rule(runs))
return;
Run* trailingSpaceRun = runs.logicallyLastRun();
int firstSpace = findFirstTrailingSpaceAtRun(trailingSpaceRun);
if (firstSpace == trailingSpaceRun->stop())
return;
bool shouldReorder =
trailingSpaceRun !=
(m_paragraphDirectionality == LTR ? runs.lastRun() : runs.firstRun());
if (firstSpace != trailingSpaceRun->start()) {
BidiContext* baseContext = context();
while (BidiContext* parent = baseContext->parent())
baseContext = parent;
m_trailingSpaceRun = addTrailingRun(
runs, firstSpace, trailingSpaceRun->m_stop, trailingSpaceRun,
baseContext, m_paragraphDirectionality);
ASSERT(m_trailingSpaceRun);
trailingSpaceRun->m_stop = firstSpace;
return;
}
if (!shouldReorder) {
m_trailingSpaceRun = trailingSpaceRun;
return;
}
if (m_paragraphDirectionality == LTR) {
runs.moveRunToEnd(trailingSpaceRun);
trailingSpaceRun->m_level = 0;
} else {
runs.moveRunToBeginning(trailingSpaceRun);
trailingSpaceRun->m_level = 1;
}
m_trailingSpaceRun = trailingSpaceRun;
}
template <class Iterator, class Run, class IsolatedRun>
bool BidiResolver<Iterator, Run, IsolatedRun>::commitExplicitEmbedding(
BidiRunList<Run>& runs) {
// When we're "inIsolate()" we're resolving the parent context which
// ignores (skips over) the isolated content, including embedding levels.
// We should never accrue embedding levels while skipping over isolated
// content.
ASSERT(!inIsolate() || m_currentExplicitEmbeddingSequence.isEmpty());
using namespace WTF::Unicode;
unsigned char fromLevel = context()->level();
RefPtr<BidiContext> toContext = context();
for (size_t i = 0; i < m_currentExplicitEmbeddingSequence.size(); ++i) {
BidiEmbedding embedding = m_currentExplicitEmbeddingSequence[i];
if (embedding.direction() == PopDirectionalFormat) {
if (BidiContext* parentContext = toContext->parent())
toContext = parentContext;
} else {
CharDirection direction =
(embedding.direction() == RightToLeftEmbedding ||
embedding.direction() == RightToLeftOverride)
? RightToLeft
: LeftToRight;
bool override = embedding.direction() == LeftToRightOverride ||
embedding.direction() == RightToLeftOverride;
unsigned char level = toContext->level();
if (direction == RightToLeft)
level = nextGreaterOddLevel(level);
else
level = nextGreaterEvenLevel(level);
if (level < BidiContext::kMaxLevel)
toContext = BidiContext::create(level, direction, override,
embedding.source(), toContext.get());
}
}
unsigned char toLevel = toContext->level();
if (toLevel > fromLevel)
raiseExplicitEmbeddingLevel(runs, fromLevel % 2 ? RightToLeft : LeftToRight,
toLevel % 2 ? RightToLeft : LeftToRight);
else if (toLevel < fromLevel)
lowerExplicitEmbeddingLevel(runs,
fromLevel % 2 ? RightToLeft : LeftToRight);
setContext(toContext);
m_currentExplicitEmbeddingSequence.clear();
return fromLevel != toLevel;
}
template <class Iterator, class Run, class IsolatedRun>
inline void
BidiResolver<Iterator, Run, IsolatedRun>::updateStatusLastFromCurrentDirection(
WTF::Unicode::CharDirection dirCurrent) {
using namespace WTF::Unicode;
switch (dirCurrent) {
case EuropeanNumberTerminator:
if (m_status.last != EuropeanNumber)
m_status.last = EuropeanNumberTerminator;
break;
case EuropeanNumberSeparator:
case CommonNumberSeparator:
case SegmentSeparator:
case WhiteSpaceNeutral:
case OtherNeutral:
switch (m_status.last) {
case LeftToRight:
case RightToLeft:
case RightToLeftArabic:
case EuropeanNumber:
case ArabicNumber:
m_status.last = dirCurrent;
break;
default:
m_status.last = OtherNeutral;
}
break;
case NonSpacingMark:
case BoundaryNeutral:
case RightToLeftEmbedding:
case LeftToRightEmbedding:
case RightToLeftOverride:
case LeftToRightOverride:
case PopDirectionalFormat:
// ignore these
break;
case EuropeanNumber:
// fall through
default:
m_status.last = dirCurrent;
}
}
template <class Iterator, class Run, class IsolatedRun>
inline void BidiResolver<Iterator, Run, IsolatedRun>::reorderRunsFromLevels(
BidiRunList<Run>& runs) const {
unsigned char levelLow = BidiContext::kMaxLevel;
unsigned char levelHigh = 0;
for (Run* run = runs.firstRun(); run; run = run->next()) {
levelHigh = std::max(run->level(), levelHigh);
levelLow = std::min(run->level(), levelLow);
}
// This implements reordering of the line (L2 according to Bidi spec):
// http://unicode.org/reports/tr9/#L2
// L2. From the highest level found in the text to the lowest odd level on
// each line, reverse any contiguous sequence of characters that are at that
// level or higher.
// Reversing is only done up to the lowest odd level.
if (!(levelLow % 2))
levelLow++;
unsigned count = runs.runCount() - 1;
while (levelHigh >= levelLow) {
unsigned i = 0;
Run* run = runs.firstRun();
while (i < count) {
for (; i < count && run && run->level() < levelHigh; i++)
run = run->next();
unsigned start = i;
for (; i <= count && run && run->level() >= levelHigh; i++)
run = run->next();
unsigned end = i - 1;
runs.reverseRuns(start, end);
}
levelHigh--;
}
}
template <class Iterator, class Run, class IsolatedRun>
TextDirection
BidiResolver<Iterator, Run, IsolatedRun>::determineDirectionalityInternal(
bool breakOnParagraph,
bool* hasStrongDirectionality) {
while (!m_current.atEnd()) {
if (inIsolate()) {
increment();
continue;
}
if (breakOnParagraph && m_current.atParagraphSeparator())
break;
UChar32 current = m_current.current();
if (UNLIKELY(U16_IS_SURROGATE(current))) {
increment();
// If this not the high part of the surrogate pair, then drop it and move
// to the next.
if (!U16_IS_SURROGATE_LEAD(current))
continue;
UChar high = static_cast<UChar>(current);
if (m_current.atEnd())
continue;
UChar low = m_current.current();
// Verify the low part. If invalid, then assume an invalid surrogate pair
// and retry.
if (!U16_IS_TRAIL(low))
continue;
current = U16_GET_SUPPLEMENTARY(high, low);
}
WTF::Unicode::CharDirection charDirection =
WTF::Unicode::direction(current);
if (charDirection == WTF::Unicode::LeftToRight) {
if (hasStrongDirectionality)
*hasStrongDirectionality = true;
return LTR;
}
if (charDirection == WTF::Unicode::RightToLeft ||
charDirection == WTF::Unicode::RightToLeftArabic) {
if (hasStrongDirectionality)
*hasStrongDirectionality = true;
return RTL;
}
increment();
}
if (hasStrongDirectionality)
*hasStrongDirectionality = false;
return LTR;
}
inline TextDirection directionForCharacter(UChar32 character) {
WTF::Unicode::CharDirection charDirection =
WTF::Unicode::direction(character);
if (charDirection == WTF::Unicode::RightToLeft ||
charDirection == WTF::Unicode::RightToLeftArabic)
return RTL;
return LTR;
}
template <class Iterator, class Run, class IsolatedRun>
void BidiResolver<Iterator, Run, IsolatedRun>::createBidiRunsForLine(
const Iterator& end,
VisualDirectionOverride override,
bool hardLineBreak,
bool reorderRuns) {
using namespace WTF::Unicode;
ASSERT(m_direction == OtherNeutral);
m_trailingSpaceRun = 0;
m_endOfLine = end;
if (override != NoVisualOverride) {
m_emptyRun = false;
m_sor = m_current;
m_eor = Iterator();
while (m_current != end && !m_current.atEnd()) {
m_eor = m_current;
increment();
}
m_direction =
override == VisualLeftToRightOverride ? LeftToRight : RightToLeft;
appendRun(m_runs);
m_runs.setLogicallyLastRun(m_runs.lastRun());
if (override == VisualRightToLeftOverride && m_runs.runCount())
m_runs.reverseRuns(0, m_runs.runCount() - 1);
return;
}
m_emptyRun = true;
m_eor = Iterator();
m_last = m_current;
bool lastLineEnded = false;
BidiResolver<Iterator, Run, IsolatedRun> stateAtEnd;
while (true) {
if (inIsolate() && m_emptyRun) {
m_sor = m_current;
m_emptyRun = false;
}
if (!lastLineEnded && isEndOfLine(end)) {
if (m_emptyRun)
break;
stateAtEnd.m_status = m_status;
stateAtEnd.m_sor = m_sor;
stateAtEnd.m_eor = m_eor;
stateAtEnd.m_last = m_last;
stateAtEnd.m_reachedEndOfLine = m_reachedEndOfLine;
stateAtEnd.m_lastBeforeET = m_lastBeforeET;
stateAtEnd.m_emptyRun = m_emptyRun;
m_endOfRunAtEndOfLine = m_last;
lastLineEnded = true;
}
CharDirection dirCurrent;
if (lastLineEnded && (hardLineBreak || m_current.atEnd())) {
BidiContext* c = context();
if (hardLineBreak) {
// A deviation from the Unicode Bidi Algorithm in order to match
// WinIE and user expectations: hard line breaks reset bidi state
// coming from unicode bidi control characters, but not those from
// DOM nodes with specified directionality
stateAtEnd.setContext(c->copyStackRemovingUnicodeEmbeddingContexts());
dirCurrent = stateAtEnd.context()->dir();
stateAtEnd.setEorDir(dirCurrent);
stateAtEnd.setLastDir(dirCurrent);
stateAtEnd.setLastStrongDir(dirCurrent);
} else {
while (c->parent())
c = c->parent();
dirCurrent = c->dir();
}
} else {
dirCurrent = m_current.direction();
if (context()->override() && dirCurrent != RightToLeftEmbedding &&
dirCurrent != LeftToRightEmbedding &&
dirCurrent != RightToLeftOverride &&
dirCurrent != LeftToRightOverride &&
dirCurrent != PopDirectionalFormat)
dirCurrent = context()->dir();
else if (dirCurrent == NonSpacingMark)
dirCurrent = m_status.last;
}
// We ignore all character directionality while in unicode-bidi: isolate
// spans. We'll handle ordering the isolated characters in a second pass.
if (inIsolate())
dirCurrent = OtherNeutral;
ASSERT(m_status.eor != OtherNeutral || m_eor.atEnd());
switch (dirCurrent) {
// embedding and overrides (X1-X9 in the Bidi specs)
case RightToLeftEmbedding:
case LeftToRightEmbedding:
case RightToLeftOverride:
case LeftToRightOverride:
case PopDirectionalFormat:
embed(dirCurrent, FromUnicode);
commitExplicitEmbedding(m_runs);
break;
// strong types
case LeftToRight:
switch (m_status.last) {
case RightToLeft:
case RightToLeftArabic:
case EuropeanNumber:
case ArabicNumber:
if (m_status.last != EuropeanNumber ||
m_status.lastStrong != LeftToRight)
appendRun(m_runs);
break;
case LeftToRight:
break;
case EuropeanNumberSeparator:
case EuropeanNumberTerminator:
case CommonNumberSeparator:
case BoundaryNeutral:
case BlockSeparator:
case SegmentSeparator:
case WhiteSpaceNeutral:
case OtherNeutral:
if (m_status.eor == EuropeanNumber) {
if (m_status.lastStrong != LeftToRight) {
// the numbers need to be on a higher embedding level, so let's
// close that run
m_direction = EuropeanNumber;
appendRun(m_runs);
if (context()->dir() != LeftToRight) {
// the neutrals take the embedding direction, which is R
m_eor = m_last;
m_direction = RightToLeft;
appendRun(m_runs);
}
}
} else if (m_status.eor == ArabicNumber) {
// Arabic numbers are always on a higher embedding level, so let's
// close that run
m_direction = ArabicNumber;
appendRun(m_runs);
if (context()->dir() != LeftToRight) {
// the neutrals take the embedding direction, which is R
m_eor = m_last;
m_direction = RightToLeft;
appendRun(m_runs);
}
} else if (m_status.lastStrong != LeftToRight) {
// last stuff takes embedding dir
if (context()->dir() == RightToLeft) {
m_eor = m_last;
m_direction = RightToLeft;
}
appendRun(m_runs);
}
default:
break;
}
m_eor = m_current;
m_status.eor = LeftToRight;
m_status.lastStrong = LeftToRight;
m_direction = LeftToRight;
break;
case RightToLeftArabic:
case RightToLeft:
switch (m_status.last) {
case LeftToRight:
case EuropeanNumber:
case ArabicNumber:
appendRun(m_runs);
case RightToLeft:
case RightToLeftArabic:
break;
case EuropeanNumberSeparator:
case EuropeanNumberTerminator:
case CommonNumberSeparator:
case BoundaryNeutral:
case BlockSeparator:
case SegmentSeparator:
case WhiteSpaceNeutral:
case OtherNeutral:
if (m_status.eor == EuropeanNumber) {
if (m_status.lastStrong == LeftToRight &&
context()->dir() == LeftToRight)
m_eor = m_last;
appendRun(m_runs);
} else if (m_status.eor == ArabicNumber) {
appendRun(m_runs);
} else if (m_status.lastStrong == LeftToRight) {
if (context()->dir() == LeftToRight)
m_eor = m_last;
appendRun(m_runs);
}
default:
break;
}
m_eor = m_current;
m_status.eor = RightToLeft;
m_status.lastStrong = dirCurrent;
m_direction = RightToLeft;
break;
// weak types:
case EuropeanNumber:
if (m_status.lastStrong != RightToLeftArabic) {
// if last strong was AL change EN to AN
switch (m_status.last) {
case EuropeanNumber:
case LeftToRight:
break;
case RightToLeft:
case RightToLeftArabic:
case ArabicNumber:
m_eor = m_last;
appendRun(m_runs);
m_direction = EuropeanNumber;
break;
case EuropeanNumberSeparator:
case CommonNumberSeparator:
if (m_status.eor == EuropeanNumber)
break;
case EuropeanNumberTerminator:
case BoundaryNeutral:
case BlockSeparator:
case SegmentSeparator:
case WhiteSpaceNeutral:
case OtherNeutral:
if (m_status.eor == EuropeanNumber) {
if (m_status.lastStrong == RightToLeft) {
// ENs on both sides behave like Rs, so the neutrals should be
// R. Terminate the EN run.
appendRun(m_runs);
// Make an R run.
m_eor = m_status.last == EuropeanNumberTerminator
? m_lastBeforeET
: m_last;
m_direction = RightToLeft;
appendRun(m_runs);
// Begin a new EN run.
m_direction = EuropeanNumber;
}
} else if (m_status.eor == ArabicNumber) {
// Terminate the AN run.
appendRun(m_runs);
if (m_status.lastStrong == RightToLeft ||
context()->dir() == RightToLeft) {
// Make an R run.
m_eor = m_status.last == EuropeanNumberTerminator
? m_lastBeforeET
: m_last;
m_direction = RightToLeft;
appendRun(m_runs);
// Begin a new EN run.
m_direction = EuropeanNumber;
}
} else if (m_status.lastStrong == RightToLeft) {
// Extend the R run to include the neutrals.
m_eor = m_status.last == EuropeanNumberTerminator
? m_lastBeforeET
: m_last;
m_direction = RightToLeft;
appendRun(m_runs);
// Begin a new EN run.
m_direction = EuropeanNumber;
}
default:
break;
}
m_eor = m_current;
m_status.eor = EuropeanNumber;
if (m_direction == OtherNeutral)
m_direction = LeftToRight;
break;
}
case ArabicNumber:
dirCurrent = ArabicNumber;
switch (m_status.last) {
case LeftToRight:
if (context()->dir() == LeftToRight)
appendRun(m_runs);
break;
case ArabicNumber:
break;
case RightToLeft:
case RightToLeftArabic:
case EuropeanNumber:
m_eor = m_last;
appendRun(m_runs);
break;
case CommonNumberSeparator:
if (m_status.eor == ArabicNumber)
break;
case EuropeanNumberSeparator:
case EuropeanNumberTerminator:
case BoundaryNeutral:
case BlockSeparator:
case SegmentSeparator:
case WhiteSpaceNeutral:
case OtherNeutral:
if (m_status.eor == ArabicNumber ||
(m_status.eor == EuropeanNumber &&
(m_status.lastStrong == RightToLeft ||
context()->dir() == RightToLeft)) ||
(m_status.eor != EuropeanNumber &&
m_status.lastStrong == LeftToRight &&
context()->dir() == RightToLeft)) {
// Terminate the run before the neutrals.
appendRun(m_runs);
// Begin an R run for the neutrals.
m_direction = RightToLeft;
} else if (m_direction == OtherNeutral) {
m_direction = m_status.lastStrong == LeftToRight ? LeftToRight
: RightToLeft;
}
m_eor = m_last;
appendRun(m_runs);
default:
break;
}
m_eor = m_current;
m_status.eor = ArabicNumber;
if (m_direction == OtherNeutral)
m_direction = ArabicNumber;
break;
case EuropeanNumberSeparator:
case CommonNumberSeparator:
break;
case EuropeanNumberTerminator:
if (m_status.last == EuropeanNumber) {
dirCurrent = EuropeanNumber;
m_eor = m_current;
m_status.eor = dirCurrent;
} else if (m_status.last != EuropeanNumberTerminator) {
m_lastBeforeET = m_emptyRun ? m_eor : m_last;
}
break;
// boundary neutrals should be ignored
case BoundaryNeutral:
if (m_eor == m_last)
m_eor = m_current;
break;
// neutrals
case BlockSeparator:
// ### what do we do with newline and paragraph seperators that come to
// here?
break;
case SegmentSeparator:
// ### implement rule L1
break;
case WhiteSpaceNeutral:
break;
case OtherNeutral:
break;
default:
break;
}
if (lastLineEnded && m_eor == m_current) {
if (!m_reachedEndOfLine) {
m_eor = m_endOfRunAtEndOfLine;
switch (m_status.eor) {
case LeftToRight:
case RightToLeft:
case ArabicNumber:
m_direction = m_status.eor;
break;
case EuropeanNumber:
m_direction = m_status.lastStrong == LeftToRight ? LeftToRight
: EuropeanNumber;
break;
default:
ASSERT_NOT_REACHED();
}
appendRun(m_runs);
}
m_current = end;
m_status = stateAtEnd.m_status;
m_sor = stateAtEnd.m_sor;
m_eor = stateAtEnd.m_eor;
m_last = stateAtEnd.m_last;
m_reachedEndOfLine = stateAtEnd.m_reachedEndOfLine;
m_lastBeforeET = stateAtEnd.m_lastBeforeET;
m_emptyRun = stateAtEnd.m_emptyRun;
m_direction = OtherNeutral;
break;
}
updateStatusLastFromCurrentDirection(dirCurrent);
m_last = m_current;
if (m_emptyRun) {
m_sor = m_current;
m_emptyRun = false;
}
increment();
if (!m_currentExplicitEmbeddingSequence.isEmpty()) {
bool committed = commitExplicitEmbedding(m_runs);
if (committed && lastLineEnded) {
m_current = end;
m_status = stateAtEnd.m_status;
m_sor = stateAtEnd.m_sor;
m_eor = stateAtEnd.m_eor;
m_last = stateAtEnd.m_last;
m_reachedEndOfLine = stateAtEnd.m_reachedEndOfLine;
m_lastBeforeET = stateAtEnd.m_lastBeforeET;
m_emptyRun = stateAtEnd.m_emptyRun;
m_direction = OtherNeutral;
break;
}
}
}
m_runs.setLogicallyLastRun(m_runs.lastRun());
if (reorderRuns)
reorderRunsFromLevels(m_runs);
m_endOfRunAtEndOfLine = Iterator();
m_endOfLine = Iterator();
if (!hardLineBreak && m_runs.runCount())
applyL1Rule(m_runs);
}
template <class Iterator, class Run, class IsolatedRun>
void BidiResolver<Iterator, Run, IsolatedRun>::setMidpointStateForIsolatedRun(
Run& run,
const MidpointState<Iterator>& midpoint) {
ASSERT(!m_midpointStateForIsolatedRun.contains(&run));
m_midpointStateForIsolatedRun.add(&run, midpoint);
}
template <class Iterator, class Run, class IsolatedRun>
MidpointState<Iterator>
BidiResolver<Iterator, Run, IsolatedRun>::midpointStateForIsolatedRun(
Run& run) {
return m_midpointStateForIsolatedRun.take(&run);
}
} // namespace blink
#endif // BidiResolver_h