mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
1156 lines
39 KiB
C++
1156 lines
39 KiB
C++
/*
|
|
* 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 SKY_ENGINE_PLATFORM_TEXT_BIDIRESOLVER_H_
|
|
#define SKY_ENGINE_PLATFORM_TEXT_BIDIRESOLVER_H_
|
|
|
|
#include "flutter/sky/engine/platform/text/BidiCharacterRun.h"
|
|
#include "flutter/sky/engine/platform/text/BidiContext.h"
|
|
#include "flutter/sky/engine/platform/text/BidiRunList.h"
|
|
#include "flutter/sky/engine/platform/text/TextDirection.h"
|
|
#include "flutter/sky/engine/wtf/HashMap.h"
|
|
#include "flutter/sky/engine/wtf/Noncopyable.h"
|
|
#include "flutter/sky/engine/wtf/PassRefPtr.h"
|
|
#include "flutter/sky/engine/wtf/Vector.h"
|
|
|
|
namespace blink {
|
|
|
|
class RenderObject;
|
|
|
|
template <class Iterator>
|
|
class MidpointState {
|
|
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);
|
|
}
|
|
|
|
// When ignoring spaces, this needs to be called for objects that need line
|
|
// boxes such as RenderInlines or hard line breaks to ensure that they're not
|
|
// ignored.
|
|
void ensureLineBoxInsideIgnoredSpaces(RenderObject* renderer) {
|
|
Iterator midpoint(0, renderer, 0);
|
|
stopIgnoringSpaces(midpoint);
|
|
startIgnoringSpaces(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.object(),
|
|
textParagraphSeparator.offset() - 1));
|
|
stopIgnoringSpaces(Iterator(0, textParagraphSeparator.object(),
|
|
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.object() && 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.object()->style()->collapseWhiteSpace() &&
|
|
endpoint.object()->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 {
|
|
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::Direction 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::Direction eorDir,
|
|
WTF::Unicode::Direction lastStrongDir,
|
|
WTF::Unicode::Direction lastDir,
|
|
PassRefPtr<BidiContext> bidiContext)
|
|
: eor(eorDir),
|
|
lastStrong(lastStrongDir),
|
|
last(lastDir),
|
|
context(bidiContext) {}
|
|
|
|
WTF::Unicode::Direction eor;
|
|
WTF::Unicode::Direction lastStrong;
|
|
WTF::Unicode::Direction last;
|
|
RefPtr<BidiContext> context;
|
|
};
|
|
|
|
class BidiEmbedding {
|
|
public:
|
|
BidiEmbedding(WTF::Unicode::Direction direction, BidiEmbeddingSource source)
|
|
: m_direction(direction), m_source(source) {}
|
|
|
|
WTF::Unicode::Direction direction() const { return m_direction; }
|
|
BidiEmbeddingSource source() const { return m_source; }
|
|
|
|
private:
|
|
WTF::Unicode::Direction 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
|
|
};
|
|
|
|
// BidiResolver is WebKit's implementation of the Unicode Bidi Algorithm
|
|
// http://unicode.org/reports/tr9
|
|
template <class Iterator, class Run>
|
|
class BidiResolver {
|
|
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::Direction lastDir) { m_status.last = lastDir; }
|
|
void setLastStrongDir(WTF::Unicode::Direction lastStrongDir) {
|
|
m_status.lastStrong = lastStrongDir;
|
|
}
|
|
void setEorDir(WTF::Unicode::Direction eorDir) { m_status.eor = eorDir; }
|
|
|
|
WTF::Unicode::Direction dir() const { return m_direction; }
|
|
void setDir(WTF::Unicode::Direction 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::Direction, 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<Run*>& isolatedRuns() { return m_isolatedRuns; }
|
|
|
|
bool isEndOfLine(const Iterator& end) {
|
|
return m_current == end || m_current.atEnd();
|
|
}
|
|
|
|
TextDirection determineParagraphDirectionality(
|
|
bool* hasStrongDirectionality = 0);
|
|
|
|
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::Direction 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<Run*> m_isolatedRuns;
|
|
Run* m_trailingSpaceRun;
|
|
TextDirection m_paragraphDirectionality;
|
|
|
|
private:
|
|
void raiseExplicitEmbeddingLevel(BidiRunList<Run>&,
|
|
WTF::Unicode::Direction from,
|
|
WTF::Unicode::Direction to);
|
|
void lowerExplicitEmbeddingLevel(BidiRunList<Run>&,
|
|
WTF::Unicode::Direction from);
|
|
void checkDirectionInLowerRaiseEmbeddingLevel();
|
|
|
|
void updateStatusLastFromCurrentDirection(WTF::Unicode::Direction);
|
|
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>&);
|
|
|
|
Vector<BidiEmbedding, 8> m_currentExplicitEmbeddingSequence;
|
|
HashMap<Run*, MidpointState<Iterator>> m_midpointStateForIsolatedRun;
|
|
};
|
|
|
|
#if ENABLE(ASSERT)
|
|
template <class Iterator, class Run>
|
|
BidiResolver<Iterator, Run>::~BidiResolver() {
|
|
// The owner of this resolver should have handled the isolated runs.
|
|
ASSERT(m_isolatedRuns.isEmpty());
|
|
}
|
|
#endif
|
|
|
|
template <class Iterator, class Run>
|
|
void BidiResolver<Iterator, Run>::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();
|
|
}
|
|
|
|
if (endOffset >= startOffset)
|
|
runs.addRun(new Run(startOffset, endOffset + 1, context(), m_direction));
|
|
|
|
m_eor.increment();
|
|
m_sor = m_eor;
|
|
}
|
|
|
|
m_direction = WTF::Unicode::OtherNeutral;
|
|
m_status.eor = WTF::Unicode::OtherNeutral;
|
|
}
|
|
|
|
template <class Iterator, class Run>
|
|
void BidiResolver<Iterator, Run>::embed(WTF::Unicode::Direction 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>
|
|
void BidiResolver<Iterator, Run>::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>
|
|
void BidiResolver<Iterator, Run>::lowerExplicitEmbeddingLevel(
|
|
BidiRunList<Run>& runs,
|
|
WTF::Unicode::Direction 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>
|
|
void BidiResolver<Iterator, Run>::raiseExplicitEmbeddingLevel(
|
|
BidiRunList<Run>& runs,
|
|
WTF::Unicode::Direction from,
|
|
WTF::Unicode::Direction 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>
|
|
void BidiResolver<Iterator, Run>::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>
|
|
bool BidiResolver<Iterator, Run>::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 {
|
|
Direction 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>
|
|
inline void BidiResolver<Iterator, Run>::updateStatusLastFromCurrentDirection(
|
|
WTF::Unicode::Direction 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>
|
|
inline void BidiResolver<Iterator, Run>::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>
|
|
TextDirection BidiResolver<Iterator, Run>::determineParagraphDirectionality(
|
|
bool* hasStrongDirectionality) {
|
|
while (!m_current.atEnd()) {
|
|
if (inIsolate()) {
|
|
increment();
|
|
continue;
|
|
}
|
|
if (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::Direction 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;
|
|
}
|
|
|
|
template <class Iterator, class Run>
|
|
void BidiResolver<Iterator, Run>::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> 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;
|
|
}
|
|
Direction 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>
|
|
void BidiResolver<Iterator, Run>::setMidpointStateForIsolatedRun(
|
|
Run* run,
|
|
const MidpointState<Iterator>& midpoint) {
|
|
ASSERT(!m_midpointStateForIsolatedRun.contains(run));
|
|
m_midpointStateForIsolatedRun.add(run, midpoint);
|
|
}
|
|
|
|
template <class Iterator, class Run>
|
|
MidpointState<Iterator>
|
|
BidiResolver<Iterator, Run>::midpointStateForIsolatedRun(Run* run) {
|
|
return m_midpointStateForIsolatedRun.take(run);
|
|
}
|
|
|
|
} // namespace blink
|
|
|
|
#endif // SKY_ENGINE_PLATFORM_TEXT_BIDIRESOLVER_H_
|