gecko-dev/layout/base/nsBidiPresUtils.cpp

1350 строки
44 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Communicator client code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Uri Bernstein <uriber@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifdef IBMBIDI
#include "nsBidiPresUtils.h"
#include "nsITextContent.h"
#include "nsTextFragment.h"
#include "nsLayoutAtoms.h"
#include "nsPresContext.h"
#include "nsIRenderingContext.h"
#include "nsIServiceManager.h"
#include "nsFrameManager.h"
#include "nsBidiFrames.h"
#include "nsBidiUtils.h"
#include "nsCSSFrameConstructor.h"
#include "nsHTMLContainerFrame.h"
static const PRUnichar kSpace = 0x0020;
static const PRUnichar kLineSeparator = 0x2028;
static const PRUnichar kObjectSubstitute = 0xFFFC;
static const PRUnichar kLRE = 0x202A;
static const PRUnichar kRLE = 0x202B;
static const PRUnichar kLRO = 0x202D;
static const PRUnichar kRLO = 0x202E;
static const PRUnichar kPDF = 0x202C;
static const PRUnichar ALEF = 0x05D0;
#define CHAR_IS_HEBREW(c) ((0x0590 <= (c)) && ((c)<= 0x05FF))
// Note: The above code are moved from gfx/src/windows/nsRenderingContextWin.cpp
nsIFrame*
NS_NewDirectionalFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUnichar aChar);
nsBidiPresUtils::nsBidiPresUtils() : mArraySize(8),
mIndexMap(nsnull),
mLevels(nsnull),
mSuccess(NS_ERROR_FAILURE),
mBidiEngine(nsnull)
{
mBidiEngine = new nsBidi();
if (mBidiEngine && mContentToFrameIndex.Init()) {
mSuccess = NS_OK;
}
}
nsBidiPresUtils::~nsBidiPresUtils()
{
if (mLevels) {
delete[] mLevels;
}
if (mIndexMap) {
delete[] mIndexMap;
}
delete mBidiEngine;
}
PRBool
nsBidiPresUtils::IsSuccessful() const
{
return NS_SUCCEEDED(mSuccess);
}
/* Some helper methods for Resolve() */
static nsresult
SplitInlineAncestors(nsPresContext* aPresContext,
nsIFrame* aFrame)
{
nsIPresShell *presShell = aPresContext->PresShell();
nsIFrame* frame = aFrame;
nsIFrame* parent = aFrame->GetParent();
nsIFrame* newFrame = aFrame->GetNextSibling();
nsIFrame* newParent;
while (nsLayoutAtoms::inlineFrame == parent->GetType() ||
nsLayoutAtoms::positionedInlineFrame == parent->GetType()) {
nsIFrame* grandparent = parent->GetParent();
NS_ASSERTION(grandparent, "Couldn't get parent's parent in nsBidiPresUtils::SplitInlineAncestors");
nsresult rv = presShell->FrameConstructor()->
CreateContinuingFrame(aPresContext, parent, grandparent, &newParent, PR_FALSE);
if (NS_FAILED(rv)) {
return rv;
}
// The new parent adopts the new frame
frame->SetNextSibling(nsnull);
rv = newParent->InsertFrames(nsLayoutAtoms::nextBidi, nsnull, newFrame);
if (NS_FAILED(rv)) {
return rv;
}
// Reparent views as necessary
rv = nsHTMLContainerFrame::ReparentFrameViewList(aPresContext, newFrame, parent, newParent);
if (NS_FAILED(rv)) {
return rv;
}
// The list name nsLayoutAtoms::nextBidi would indicate we don't want reflow
rv = grandparent->InsertFrames(nsLayoutAtoms::nextBidi, parent, newParent);
if (NS_FAILED(rv)) {
return rv;
}
frame = parent;
newFrame = newParent;
parent = grandparent;
}
return NS_OK;
}
static nsresult
CreateBidiContinuation(nsPresContext* aPresContext,
nsIFrame* aFrame,
nsIFrame** aNewFrame)
{
NS_PRECONDITION(aNewFrame, "null OUT ptr");
NS_PRECONDITION(aFrame, "null ptr");
*aNewFrame = nsnull;
nsIPresShell *presShell = aPresContext->PresShell();
NS_ASSERTION(presShell, "PresShell must be set on PresContext before calling nsBidiPresUtils::CreateBidiContinuation");
nsIFrame* parent = aFrame->GetParent();
NS_ASSERTION(parent, "Couldn't get frame parent in nsBidiPresUtils::CreateBidiContinuation");
nsresult rv = presShell->FrameConstructor()->
CreateContinuingFrame(aPresContext, aFrame, parent, aNewFrame, PR_FALSE);
if (NS_FAILED(rv)) {
return rv;
}
// The list name nsLayoutAtoms::nextBidi would indicate we don't want reflow
rv = parent->InsertFrames(nsLayoutAtoms::nextBidi, aFrame, *aNewFrame);
if (NS_FAILED(rv)) {
return rv;
}
// Split inline ancestor frames
rv = SplitInlineAncestors(aPresContext, aFrame);
if (NS_FAILED(rv)) {
return rv;
}
return NS_OK;
}
/*
* Overview of the implementation of Resolve():
*
* Walk through the descendants of aBlockFrame and build:
* * mLogicalArray: an nsVoidArray of nsIFrame* pointers in logical order
* * mBuffer: an nsAutoString containing a representation of
* the content of the frames.
* In the case of text frames, this is the actual text context of the
* frames, but some other elements are represented in a symbolic form which
* will make the Unicode Bidi Algorithm give the correct results.
* Bidi embeddings and overrides set by CSS or <bdo> elements are
* represented by the corresponding Unicode control characters.
* <br> elements are represented by U+2028 LINE SEPARATOR
* Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
* CHARACTER
*
* Then pass mBuffer to the Bidi engine for resolving of embedding levels
* by nsBidi::SetPara() and division into directional runs by
* nsBidi::CountRuns().
*
* Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
* correlate them with the frames indexed in mLogicalArray, setting the
* baseLevel, embeddingLevel, and charType properties according to the results
* returned by the Bidi engine and CalculateCharType().
*
* The rendering layer requires each text frame to contain text in only one
* direction and of only one character type, so we may need to call
* EnsureBidiContinuation() to split frames. We may also need to call
* RemoveBidiContinuation() to convert frames created by
* EnsureBidiContinuation() in previous reflows into fluid continuations.
*/
nsresult
nsBidiPresUtils::Resolve(nsPresContext* aPresContext,
nsBlockFrame* aBlockFrame,
nsIFrame* aFirstChild,
PRBool aIsVisualFormControl)
{
mLogicalFrames.Clear();
mContentToFrameIndex.Clear();
nsIPresShell* shell = aPresContext->PresShell();
nsStyleContext* styleContext = aBlockFrame->GetStyleContext();
// handle bidi-override being set on the block itself before calling
// InitLogicalArray.
const nsStyleVisibility* vis = aBlockFrame->GetStyleVisibility();
const nsStyleTextReset* text = aBlockFrame->GetStyleTextReset();
if (text->mUnicodeBidi == NS_STYLE_UNICODE_BIDI_OVERRIDE) {
nsIFrame *directionalFrame = nsnull;
if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
directionalFrame = NS_NewDirectionalFrame(shell, styleContext, kRLO);
}
else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) {
directionalFrame = NS_NewDirectionalFrame(shell, styleContext, kLRO);
}
if (directionalFrame) {
mLogicalFrames.AppendElement(directionalFrame);
}
}
mSuccess = InitLogicalArray(aPresContext, aFirstChild, nsnull, PR_TRUE);
if (text->mUnicodeBidi == NS_STYLE_UNICODE_BIDI_OVERRIDE) {
nsIFrame* directionalFrame = NS_NewDirectionalFrame(shell, styleContext, kPDF);
if (directionalFrame) {
mLogicalFrames.AppendElement(directionalFrame);
}
}
if (NS_FAILED(mSuccess) ) {
return mSuccess;
}
CreateBlockBuffer(aPresContext);
PRInt32 bufferLength = mBuffer.Length();
if (bufferLength < 1) {
mSuccess = NS_OK;
return mSuccess;
}
PRInt32 runCount;
PRUint8 embeddingLevel;
nsBidiLevel paraLevel = embeddingLevel =
(NS_STYLE_DIRECTION_RTL == vis->mDirection)
? NSBIDI_RTL : NSBIDI_LTR;
mSuccess = mBidiEngine->SetPara(mBuffer.get(), bufferLength, paraLevel, nsnull);
if (NS_FAILED(mSuccess) ) {
return mSuccess;
}
PRBool isVisual;
if (aIsVisualFormControl) {
isVisual = PR_FALSE;
} else {
isVisual = aPresContext->IsVisualMode();
}
mSuccess = mBidiEngine->CountRuns(&runCount);
if (NS_FAILED(mSuccess) ) {
return mSuccess;
}
PRInt32 runLength = 0;
PRInt32 fragmentLength = 0;
PRInt32 temp;
PRInt32 frameIndex = -1;
PRInt32 frameCount = mLogicalFrames.Count();
PRInt32 contentOffset = 0; // offset within current frame
PRInt32 lineOffset = 0; // offset within mBuffer
PRInt32 logicalLimit = 0;
PRInt32 numRun = -1;
PRUint8 charType;
PRUint8 prevType = eCharType_LeftToRight;
PRBool isTextFrame = PR_FALSE;
nsIFrame* frame = nsnull;
nsIFrame* nextBidi;
nsIContent* content = nsnull;
nsCOMPtr<nsITextContent> textContent;
const nsTextFragment* fragment;
nsIAtom* frameType = nsnull;
nsPropertyTable *propTable = aPresContext->PropertyTable();
nsBlockFrame::line_iterator line = aBlockFrame->begin_lines();
nsBlockFrame::line_iterator endLines = aBlockFrame->end_lines();
for (; ;) {
if (fragmentLength <= 0) {
if (++frameIndex >= frameCount) {
break;
}
contentOffset = 0;
frame = (nsIFrame*) (mLogicalFrames[frameIndex]);
frameType = frame->GetType();
if (nsLayoutAtoms::directionalFrame != frameType) {
// Advance the line iterator to the line containing frame
nsIFrame* child = frame;
nsIFrame* parent = child->GetParent();
while (parent && parent != aBlockFrame) {
child = parent;
parent = child->GetParent();
}
NS_ASSERTION (parent, "frame is not a descendent of aBlockFrame");
while (line != endLines && !line->Contains(child)) {
++line;
}
NS_ASSERTION (line != endLines, "frame not found on any line");
}
if (nsLayoutAtoms::textFrame == frameType) {
content = frame->GetContent();
if (!content) {
mSuccess = NS_OK;
break;
}
textContent = do_QueryInterface(content, &mSuccess);
if (NS_FAILED(mSuccess) || (!textContent) ) {
break;
}
fragment = textContent->Text();
if (!fragment) {
mSuccess = NS_ERROR_FAILURE;
break;
}
fragmentLength = fragment->GetLength();
isTextFrame = PR_TRUE;
} // if text frame
else {
isTextFrame = PR_FALSE;
fragmentLength = 1;
}
} // if (fragmentLength <= 0)
if (runLength <= 0) {
if (++numRun >= runCount) {
break;
}
lineOffset = logicalLimit;
if (NS_FAILED(mBidiEngine->GetLogicalRun(
lineOffset, &logicalLimit, &embeddingLevel) ) ) {
break;
}
runLength = logicalLimit - lineOffset;
if (isVisual) {
embeddingLevel = paraLevel;
}
} // if (runLength <= 0)
if (nsLayoutAtoms::directionalFrame == frameType) {
frame->Destroy();
frame = nsnull;
++lineOffset;
}
else {
propTable->SetProperty(frame, nsLayoutAtoms::embeddingLevel,
NS_INT32_TO_PTR(embeddingLevel), nsnull, nsnull);
propTable->SetProperty(frame, nsLayoutAtoms::baseLevel,
NS_INT32_TO_PTR(paraLevel), nsnull, nsnull);
if (isTextFrame) {
PRInt32 typeLimit = PR_MIN(logicalLimit, lineOffset + fragmentLength);
CalculateCharType(lineOffset, typeLimit, logicalLimit, runLength,
runCount, charType, prevType);
// IBMBIDI - Egypt - Start
propTable->SetProperty(frame, nsLayoutAtoms::charType,
NS_INT32_TO_PTR(charType), nsnull, nsnull);
// IBMBIDI - Egypt - End
if ( (runLength > 0) && (runLength < fragmentLength) ) {
if (!EnsureBidiContinuation(aPresContext, frame,
&nextBidi, frameIndex) ) {
break;
}
line->MarkDirty();
frame->AdjustOffsetsForBidi(contentOffset, contentOffset + runLength);
frame = nextBidi;
contentOffset += runLength;
} // if (runLength < fragmentLength)
else {
frame->AdjustOffsetsForBidi(contentOffset, contentOffset + fragmentLength);
PRInt32 newIndex = 0;
mContentToFrameIndex.Get(content, &newIndex);
if (newIndex > frameIndex) {
RemoveBidiContinuation(aPresContext, frame,
frameIndex, newIndex, temp);
line->MarkDirty();
runLength -= temp;
fragmentLength -= temp;
lineOffset += temp;
frameIndex = newIndex;
}
}
} // isTextFrame
else {
++lineOffset;
}
} // not directionalFrame
temp = runLength;
runLength -= fragmentLength;
fragmentLength -= temp;
// If the frame is at the end of a run, split all ancestor inlines that need splitting.
if (frame && fragmentLength <= 0 && runLength <= 0) {
// As long as we're on the last sibling, the parent doesn't have to be split.
nsIFrame* child = frame;
nsIFrame* parent = frame->GetParent();
while (parent &&
(nsLayoutAtoms::inlineFrame == parent->GetType() ||
nsLayoutAtoms::positionedInlineFrame == parent->GetType()) &&
!child->GetNextSibling()) {
child = parent;
parent = child->GetParent();
}
if (parent &&
(nsLayoutAtoms::inlineFrame == parent->GetType() ||
nsLayoutAtoms::positionedInlineFrame == parent->GetType()))
SplitInlineAncestors(aPresContext, child);
}
} // for
return mSuccess;
}
// Should this frame be treated as a leaf (e.g. when building mLogicalArray)?
PRBool IsBidiLeaf(nsIFrame* aFrame) {
nsIAtom* frameType = aFrame->GetType();
nsIFrame* kid = aFrame->GetFirstChild(nsnull);
return !kid
|| aFrame->GetStyleDisplay()->IsBlockLevel()
|| !(nsLayoutAtoms::inlineFrame == frameType
|| nsLayoutAtoms::positionedInlineFrame == frameType
|| nsLayoutAtoms::letterFrame == frameType
|| nsLayoutAtoms::blockFrame == frameType);
}
nsresult
nsBidiPresUtils::InitLogicalArray(nsPresContext* aPresContext,
nsIFrame* aCurrentFrame,
nsIFrame* aNextInFlow,
PRBool aAddMarkers)
{
nsIFrame* frame;
nsIFrame* directionalFrame;
nsresult res = NS_OK;
nsIPresShell* shell = aPresContext->PresShell();
nsStyleContext* styleContext;
for (frame = aCurrentFrame;
frame && frame != aNextInFlow;
frame = frame->GetNextSibling()) {
directionalFrame = nsnull;
const nsStyleDisplay* display = frame->GetStyleDisplay();
if (aAddMarkers && !display->IsBlockLevel() ) {
const nsStyleVisibility* vis = frame->GetStyleVisibility();
const nsStyleTextReset* text = frame->GetStyleTextReset();
switch (text->mUnicodeBidi) {
case NS_STYLE_UNICODE_BIDI_NORMAL:
break;
case NS_STYLE_UNICODE_BIDI_EMBED:
styleContext = frame->GetStyleContext();
if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
directionalFrame = NS_NewDirectionalFrame(shell, styleContext, kRLE);
}
else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) {
directionalFrame = NS_NewDirectionalFrame(shell, styleContext, kLRE);
}
break;
case NS_STYLE_UNICODE_BIDI_OVERRIDE:
styleContext = frame->GetStyleContext();
if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
directionalFrame = NS_NewDirectionalFrame(shell, styleContext, kRLO);
}
else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) {
directionalFrame = NS_NewDirectionalFrame(shell, styleContext, kLRO);
}
break;
}
// Create a directional frame before the first frame of an
// element specifying embedding or override
if (directionalFrame && !frame->GetPrevContinuation()) {
mLogicalFrames.AppendElement(directionalFrame);
}
}
if (IsBidiLeaf(frame)) {
/* Bidi leaf frame: add the frame to the mLogicalFrames array,
* and add its index to the mContentToFrameIndex hashtable. This
* will be used in RemoveBidiContinuation() to identify the last
* frame in the array with a given content.
*/
nsIContent* content = frame->GetContent();
if (content) {
mContentToFrameIndex.Put(content, mLogicalFrames.Count());
}
mLogicalFrames.AppendElement(frame);
}
else {
nsIFrame* kid = frame->GetFirstChild(nsnull);
res = InitLogicalArray(aPresContext, kid, aNextInFlow, aAddMarkers);
}
// If the element is attributed by dir, indicate direction pop (add PDF frame)
if (directionalFrame) {
directionalFrame = NS_NewDirectionalFrame(shell, styleContext, kPDF);
// Create a directional frame after the last frame of an
// element specifying embedding or override
if (directionalFrame && !frame->GetNextContinuation()) {
mLogicalFrames.AppendElement(directionalFrame);
}
}
} // for
return res;
}
void
nsBidiPresUtils::CreateBlockBuffer(nsPresContext* aPresContext)
{
mBuffer.SetLength(0);
nsIFrame* frame;
nsIContent* prevContent = nsnull;
nsCOMPtr<nsITextContent> textContent;
PRUint32 i;
PRUint32 count = mLogicalFrames.Count();
for (i = 0; i < count; i++) {
frame = (nsIFrame*) (mLogicalFrames[i]);
nsIAtom* frameType = frame->GetType();
if (nsLayoutAtoms::textFrame == frameType) {
nsIContent* content = frame->GetContent();
if (!content) {
mSuccess = NS_OK;
break;
}
if (content == prevContent) {
continue;
}
prevContent = content;
textContent = do_QueryInterface(content, &mSuccess);
if ( (NS_FAILED(mSuccess) ) || (!textContent) ) {
break;
}
textContent->Text()->AppendTo(mBuffer);
}
else if (nsLayoutAtoms::brFrame == frameType) { // break frame
// Append line separator
mBuffer.Append( (PRUnichar) kLineSeparator);
}
else if (nsLayoutAtoms::directionalFrame == frameType) {
nsDirectionalFrame* dirFrame;
frame->QueryInterface(NS_GET_IID(nsDirectionalFrame),
(void**) &dirFrame);
mBuffer.Append(dirFrame->GetChar() );
}
else { // not text frame
// See the Unicode Bidi Algorithm:
// "...inline objects (such as graphics) are treated as if they are ... U+FFFC"
mBuffer.Append( (PRUnichar) kObjectSubstitute);
}
}
// XXX: TODO: Handle preformatted text ('\n')
mBuffer.ReplaceChar("\t\r\n", kSpace);
}
void
nsBidiPresUtils::ReorderFrames(nsPresContext* aPresContext,
nsIRenderingContext* aRendContext,
nsIFrame* aFirstFrameOnLine,
PRInt32 aNumFramesOnLine)
{
InitLogicalArrayFromLine(aFirstFrameOnLine, aNumFramesOnLine);
PRBool isReordered;
PRBool hasRTLFrames;
Reorder(isReordered, hasRTLFrames);
RepositionInlineFrames(aPresContext, aRendContext, aFirstFrameOnLine, isReordered);
}
nsresult
nsBidiPresUtils::Reorder(PRBool& aReordered, PRBool& aHasRTLFrames)
{
aReordered = PR_FALSE;
aHasRTLFrames = PR_FALSE;
PRInt32 count = mLogicalFrames.Count();
if (mArraySize < count) {
mArraySize = count << 1;
if (mLevels) {
delete[] mLevels;
mLevels = nsnull;
}
if (mIndexMap) {
delete[] mIndexMap;
mIndexMap = nsnull;
}
}
if (!mLevels) {
mLevels = new PRUint8[mArraySize];
if (!mLevels) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
memset(mLevels, 0, sizeof(PRUint8) * mArraySize);
nsIFrame* frame;
PRInt32 i;
for (i = 0; i < count; i++) {
frame = (nsIFrame*) (mLogicalFrames[i]);
mLevels[i] = GetFrameEmbeddingLevel(frame);
if (mLevels[i] & 1) {
aHasRTLFrames = PR_TRUE;
}
}
if (!mIndexMap) {
mIndexMap = new PRInt32[mArraySize];
}
if (!mIndexMap) {
mSuccess = NS_ERROR_OUT_OF_MEMORY;
}
else {
memset(mIndexMap, 0, sizeof(PRUint32) * mArraySize);
mSuccess = mBidiEngine->ReorderVisual(mLevels, count, mIndexMap);
if (NS_SUCCEEDED(mSuccess) ) {
mVisualFrames.Clear();
for (i = 0; i < count; i++) {
mVisualFrames.AppendElement(mLogicalFrames[mIndexMap[i]]);
if (i != mIndexMap[i]) {
aReordered = PR_TRUE;
}
}
} // NS_SUCCEEDED(mSuccess)
} // indexMap
if (NS_FAILED(mSuccess) ) {
aReordered = PR_FALSE;
}
return mSuccess;
}
nsBidiLevel
nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame)
{
nsIFrame* firstLeaf = aFrame;
while (!IsBidiLeaf(firstLeaf)) {
firstLeaf = firstLeaf->GetFirstChild(nsnull);
}
return NS_GET_EMBEDDING_LEVEL(firstLeaf);
}
nsBidiLevel
nsBidiPresUtils::GetFrameBaseLevel(nsIFrame* aFrame)
{
nsIFrame* firstLeaf = aFrame;
while (!IsBidiLeaf(firstLeaf)) {
firstLeaf = firstLeaf->GetFirstChild(nsnull);
}
return NS_GET_BASE_LEVEL(firstLeaf);
}
static void
ReverseChildFramesPositioning(nsIFrame* aFirstChild)
{
if (!aFirstChild)
return;
// Get the right edge of the last sibling
nsIFrame* lastSibling;
for (lastSibling = aFirstChild; lastSibling->GetNextSibling(); lastSibling = lastSibling->GetNextSibling())
;
nscoord right = lastSibling->GetRect().XMost();
nsIFrame* frame;
for (frame = aFirstChild; frame; frame = frame->GetNextSibling()) {
right -= frame->GetRect().width;
frame->SetPosition(nsPoint(right, frame->GetPosition().y));
if (!IsBidiLeaf(frame))
ReverseChildFramesPositioning(frame->GetFirstChild(nsnull));
}
}
void
nsBidiPresUtils::RepositionInlineFrames(nsPresContext* aPresContext,
nsIRenderingContext* aRendContext,
nsIFrame* aFirstChild,
PRBool aReordered) const
{
PRInt32 count = mVisualFrames.Count();
nscoord left = aFirstChild->GetPosition().x;
nsIFrame* frame;
for (PRInt32 i = 0; i < count; i++) {
frame = (nsIFrame*) (mVisualFrames[i]);
if (aReordered) // Don't touch positioning if no reordering was done, to avoid killing margins.
frame->SetPosition(nsPoint(left, frame->GetPosition().y));
left += frame->GetRect().width;
// If this is an odd-level frame, reverse the positioning of its childern
if ((mLevels[mIndexMap[i]] & 1) && !IsBidiLeaf(frame))
ReverseChildFramesPositioning(frame->GetFirstChild(nsnull));
} // for
}
void
nsBidiPresUtils::InitLogicalArrayFromLine(nsIFrame* aFirstFrameOnLine,
PRInt32 aNumFramesOnLine) {
mLogicalFrames.Clear();
for (nsIFrame* frame = aFirstFrameOnLine;
frame && aNumFramesOnLine--;
frame = frame->GetNextSibling()) {
mLogicalFrames.AppendElement(frame);
}
}
PRBool
nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine,
PRInt32 aNumFramesOnLine,
nsIFrame** aFirstVisual,
nsIFrame** aLastVisual)
{
InitLogicalArrayFromLine(aFirstFrameOnLine, aNumFramesOnLine);
PRBool isReordered;
PRBool hasRTLFrames;
Reorder(isReordered, hasRTLFrames);
PRInt32 count = mLogicalFrames.Count();
if (aFirstVisual) {
*aFirstVisual = (nsIFrame*)mVisualFrames[0];
}
if (aLastVisual) {
*aLastVisual = (nsIFrame*)mVisualFrames[count-1];
}
// If there's an RTL frame, assume the line is reordered
return isReordered || hasRTLFrames;
}
nsIFrame*
nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame,
nsIFrame* aFirstFrameOnLine,
PRInt32 aNumFramesOnLine)
{
InitLogicalArrayFromLine(aFirstFrameOnLine, aNumFramesOnLine);
PRBool isReordered;
PRBool hasRTLFrames;
Reorder(isReordered, hasRTLFrames);
PRInt32 count = mVisualFrames.Count();
if (aFrame == nsnull)
return (nsIFrame*)mVisualFrames[0];
for (PRInt32 i = 0; i < count - 1; i++) {
if ((nsIFrame*)mVisualFrames[i] == aFrame) {
return (nsIFrame*)mVisualFrames[i+1];
}
}
return nsnull;
}
nsIFrame*
nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame* aFrame,
nsIFrame* aFirstFrameOnLine,
PRInt32 aNumFramesOnLine)
{
InitLogicalArrayFromLine(aFirstFrameOnLine, aNumFramesOnLine);
PRBool isReordered;
PRBool hasRTLFrames;
Reorder(isReordered, hasRTLFrames);
PRInt32 count = mVisualFrames.Count();
if (aFrame == nsnull)
return (nsIFrame*)mVisualFrames[count-1];
for (PRInt32 i = 1; i < count; i++) {
if ((nsIFrame*)mVisualFrames[i] == aFrame) {
return (nsIFrame*)mVisualFrames[i-1];
}
}
return nsnull;
}
PRBool
nsBidiPresUtils::EnsureBidiContinuation(nsPresContext* aPresContext,
nsIFrame* aFrame,
nsIFrame** aNewFrame,
PRInt32& aFrameIndex)
{
NS_PRECONDITION(aNewFrame, "null OUT ptr");
NS_PRECONDITION(aFrame, "aFrame is null");
*aNewFrame = nsnull;
nsBidiLevel embeddingLevel = NS_GET_EMBEDDING_LEVEL(aFrame);
nsBidiLevel baseLevel = NS_GET_BASE_LEVEL(aFrame);
nsCharType charType = (nsCharType)NS_PTR_TO_INT32(aFrame->GetProperty(nsLayoutAtoms::charType));
// Skip fluid continuations
while (aFrameIndex + 1 < mLogicalFrames.Count()) {
nsIFrame* frame = (nsIFrame*)mLogicalFrames[aFrameIndex + 1];
if (frame->GetPrevInFlow() != aFrame) {
// If we found a non-fluid continuation, use it
if (frame->GetPrevContinuation() == aFrame) {
*aNewFrame = frame;
aFrameIndex++;
}
break;
}
frame->SetProperty(nsLayoutAtoms::embeddingLevel, NS_INT32_TO_PTR(embeddingLevel));
frame->SetProperty(nsLayoutAtoms::baseLevel, NS_INT32_TO_PTR(baseLevel));
frame->SetProperty(nsLayoutAtoms::charType, NS_INT32_TO_PTR(charType));
frame->AddStateBits(NS_FRAME_IS_BIDI);
aFrameIndex++;
aFrame = frame;
}
if (!*aNewFrame) {
mSuccess = CreateBidiContinuation(aPresContext, aFrame, aNewFrame);
if (NS_FAILED(mSuccess) ) {
return PR_FALSE;
}
}
return PR_TRUE;
}
void
nsBidiPresUtils::RemoveBidiContinuation(nsPresContext* aPresContext,
nsIFrame* aFrame,
PRInt32 aFirstIndex,
PRInt32 aLastIndex,
PRInt32& aOffset) const
{
aOffset = 0;
nsresult rv;
nsBidiLevel embeddingLevel = (nsCharType)NS_PTR_TO_INT32(aFrame->GetProperty(nsLayoutAtoms::embeddingLevel, &rv));
NS_ASSERTION(NS_SUCCEEDED(rv), "embeddingLevel attribute missing from aFrame");
nsBidiLevel baseLevel = (nsCharType)NS_PTR_TO_INT32(aFrame->GetProperty(nsLayoutAtoms::baseLevel, &rv));
NS_ASSERTION(NS_SUCCEEDED(rv), "baseLevel attribute missing from aFrame");
nsCharType charType = (nsCharType)NS_PTR_TO_INT32(aFrame->GetProperty(nsLayoutAtoms::charType, &rv));
NS_ASSERTION(NS_SUCCEEDED(rv), "charType attribute missing from aFrame");
for (PRInt32 index = aFirstIndex + 1; index <= aLastIndex; index++) {
nsIFrame* frame = (nsIFrame*) mLogicalFrames[index];
if (nsLayoutAtoms::directionalFrame == frame->GetType()) {
frame->Destroy();
++aOffset;
}
else {
// Make the frame and its continuation ancestors fluid,
// so they can be reused or deleted by normal reflow code
frame->SetProperty(nsLayoutAtoms::embeddingLevel, NS_INT32_TO_PTR(embeddingLevel));
frame->SetProperty(nsLayoutAtoms::baseLevel, NS_INT32_TO_PTR(baseLevel));
frame->SetProperty(nsLayoutAtoms::charType, NS_INT32_TO_PTR(charType));
frame->AddStateBits(NS_FRAME_IS_BIDI);
while (frame) {
nsIFrame* prev = frame->GetPrevContinuation();
if (prev) {
NS_ASSERTION (!frame->GetPrevInFlow() || frame->GetPrevInFlow() == prev,
"prev-in-flow is not prev continuation!");
frame->SetPrevInFlow(prev);
NS_ASSERTION (!prev->GetNextInFlow() || prev->GetNextInFlow() == frame,
"next-in-flow is not next continuation!");
prev->SetNextInFlow(frame);
frame = frame->GetParent();
} else {
break;
}
}
}
}
}
nsresult
nsBidiPresUtils::FormatUnicodeText(nsPresContext* aPresContext,
PRUnichar* aText,
PRInt32& aTextLength,
nsCharType aCharType,
PRBool aIsOddLevel,
PRBool aIsBidiSystem)
{
NS_ASSERTION(aIsOddLevel == 0 || aIsOddLevel == 1, "aIsOddLevel should be 0 or 1");
nsresult rv = NS_OK;
// ahmed
//adjusted for correct numeral shaping
PRUint32 bidiOptions = aPresContext->GetBidi();
switch (GET_BIDI_OPTION_NUMERAL(bidiOptions)) {
case IBMBIDI_NUMERAL_HINDI:
HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI);
break;
case IBMBIDI_NUMERAL_ARABIC:
HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
break;
case IBMBIDI_NUMERAL_REGULAR:
switch (aCharType) {
case eCharType_EuropeanNumber:
HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
break;
case eCharType_ArabicNumber:
HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI);
break;
default:
break;
}
break;
case IBMBIDI_NUMERAL_HINDICONTEXT:
if ( ( (GET_BIDI_OPTION_DIRECTION(bidiOptions)==IBMBIDI_TEXTDIRECTION_RTL) && (IS_ARABIC_DIGIT (aText[0])) ) || (eCharType_ArabicNumber == aCharType) )
HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI);
else if (eCharType_EuropeanNumber == aCharType)
HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
break;
case IBMBIDI_NUMERAL_NOMINAL:
default:
break;
}
PRBool doReverse = PR_FALSE;
PRBool doShape = PR_FALSE;
if (aIsBidiSystem) {
if ( (CHARTYPE_IS_RTL(aCharType)) ^ (aIsOddLevel) )
doReverse = PR_TRUE;
}
else {
if (aIsOddLevel)
doReverse = PR_TRUE;
if (eCharType_RightToLeftArabic == aCharType)
doShape = PR_TRUE;
}
if (doReverse || doShape) {
PRInt32 newLen;
if (mBuffer.Length() < aTextLength) {
mBuffer.SetLength(aTextLength);
}
PRUnichar* buffer = mBuffer.BeginWriting();
if (doReverse) {
rv = mBidiEngine->WriteReverse(aText, aTextLength, buffer,
NSBIDI_DO_MIRRORING, &newLen);
if (NS_SUCCEEDED(rv) ) {
aTextLength = newLen;
memcpy(aText, buffer, aTextLength * sizeof(PRUnichar) );
}
}
if (doShape) {
rv = ArabicShaping(aText, aTextLength, buffer, (PRUint32 *)&newLen,
PR_FALSE, PR_FALSE);
if (NS_SUCCEEDED(rv) ) {
aTextLength = newLen;
memcpy(aText, buffer, aTextLength * sizeof(PRUnichar) );
}
}
}
StripBidiControlCharacters(aText, aTextLength);
return rv;
}
void
nsBidiPresUtils::StripBidiControlCharacters(PRUnichar* aText,
PRInt32& aTextLength) const
{
if ( (nsnull == aText) || (aTextLength < 1) ) {
return;
}
PRInt32 stripLen = 0;
for (PRInt32 i = 0; i < aTextLength; i++) {
// XXX: This silently ignores surrogate characters.
// As of Unicode 4.0, all Bidi control characters are within the BMP.
if (mBidiEngine->IsBidiControl((PRUint32)aText[i])) {
++stripLen;
}
else {
aText[i - stripLen] = aText[i];
}
}
aTextLength -= stripLen;
}
#if 0 // XXX: for the future use ???
void
RemoveDiacritics(PRUnichar* aText,
PRInt32& aTextLength)
{
if (aText && (aTextLength > 0) ) {
PRInt32 offset = 0;
for (PRInt32 i = 0; i < aTextLength && aText[i]; i++) {
if (IS_BIDI_DIACRITIC(aText[i]) ) {
++offset;
continue;
}
aText[i - offset] = aText[i];
}
aTextLength = i - offset;
aText[aTextLength] = 0;
}
}
#endif
void
nsBidiPresUtils::CalculateCharType(PRInt32& aOffset,
PRInt32 aCharTypeLimit,
PRInt32& aRunLimit,
PRInt32& aRunLength,
PRInt32& aRunCount,
PRUint8& aCharType,
PRUint8& aPrevCharType) const
{
PRBool strongTypeFound = PR_FALSE;
PRInt32 offset;
nsCharType charType;
aCharType = eCharType_OtherNeutral;
for (offset = aOffset; offset < aCharTypeLimit; offset++) {
// Make sure we give RTL chartype to all characters that would be classified
// as Right-To-Left by a bidi platform.
// (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.)
if (IS_HEBREW_CHAR(mBuffer[offset]) ) {
charType = eCharType_RightToLeft;
}
else if (IS_ARABIC_ALPHABETIC(mBuffer[offset]) ) {
charType = eCharType_RightToLeftArabic;
}
else {
mBidiEngine->GetCharTypeAt(offset, &charType);
}
if (!CHARTYPE_IS_WEAK(charType) ) {
if (strongTypeFound
&& (charType != aPrevCharType)
&& (CHARTYPE_IS_RTL(charType) || CHARTYPE_IS_RTL(aPrevCharType) ) ) {
// Stop at this point to ensure uni-directionality of the text
// (from platform's point of view).
// Also, don't mix Arabic and Hebrew content (since platform may
// provide BIDI support to one of them only).
aRunLength = offset - aOffset;
aRunLimit = offset;
++aRunCount;
break;
}
if ( (eCharType_RightToLeftArabic == aPrevCharType
|| eCharType_ArabicNumber == aPrevCharType)
&& eCharType_EuropeanNumber == charType) {
charType = eCharType_ArabicNumber;
}
// Set PrevCharType to the last strong type in this frame
// (for correct numeric shaping)
aPrevCharType = charType;
strongTypeFound = PR_TRUE;
aCharType = charType;
}
}
aOffset = offset;
}
nsresult nsBidiPresUtils::GetBidiEngine(nsBidi** aBidiEngine)
{
nsresult rv = NS_ERROR_FAILURE;
if (mBidiEngine) {
*aBidiEngine = mBidiEngine;
rv = NS_OK;
}
return rv;
}
nsresult nsBidiPresUtils::RenderText(const PRUnichar* aText,
PRInt32 aLength,
nsBidiDirection aBaseDirection,
nsPresContext* aPresContext,
nsIRenderingContext& aRenderingContext,
nscoord aX,
nscoord aY,
nsBidiPositionResolve* aPosResolve,
PRInt32 aPosResolveCount)
{
NS_ASSERTION((aPosResolve == nsnull) != (aPosResolveCount > 0), "Incorrect aPosResolve / aPosResolveCount arguments");
PRInt32 runCount;
mBuffer.Assign(aText);
nsresult rv = mBidiEngine->SetPara(mBuffer.get(), aLength, aBaseDirection, nsnull);
if (NS_FAILED(rv))
return rv;
rv = mBidiEngine->CountRuns(&runCount);
if (NS_FAILED(rv))
return rv;
nscoord width, xEndRun, xStartText = aX;
PRBool isRTL = PR_FALSE;
PRInt32 i, start, limit, length;
PRUint32 visualStart = 0;
PRUint8 charType;
PRUint8 prevType = eCharType_LeftToRight;
nsBidiLevel level;
PRUint32 hints = 0;
aRenderingContext.GetHints(hints);
PRBool isBidiSystem = (hints & NS_RENDERING_HINT_BIDI_REORDERING);
for(int nPosResolve=0; nPosResolve < aPosResolveCount; ++nPosResolve)
{
aPosResolve[nPosResolve].visualIndex = kNotFound;
aPosResolve[nPosResolve].visualLeftTwips = kNotFound;
}
for (i = 0; i < runCount; i++) {
rv = mBidiEngine->GetVisualRun(i, &start, &length, &aBaseDirection);
if (NS_FAILED(rv))
return rv;
rv = mBidiEngine->GetLogicalRun(start, &limit, &level);
if (NS_FAILED(rv))
return rv;
PRInt32 subRunLength = limit - start;
PRInt32 lineOffset = start;
PRInt32 typeLimit = PR_MIN(limit, aLength);
PRInt32 subRunCount = 1;
PRInt32 subRunLimit = typeLimit;
/*
* If |level| is even, i.e. the direction of the run is left-to-right, we
* render the subruns from left to right and increment the x-coordinate
* |aX| by the width of each subrun after rendering.
*
* If |level| is odd, i.e. the direction of the run is right-to-left, we
* render the subruns from right to left. We begin by incrementing |aX| by
* the width of the whole run, and then decrement it by the width of each
* subrun before rendering. After rendering all the subruns, we restore the
* x-coordinate of the end of the run for the start of the next run.
*/
if (level & 1) {
aRenderingContext.GetWidth(aText + start, subRunLength, width, nsnull);
aX += width;
xEndRun = aX;
}
while (subRunCount > 0) {
// CalculateCharType can increment subRunCount if the run
// contains mixed character types
CalculateCharType(lineOffset, typeLimit, subRunLimit, subRunLength, subRunCount, charType, prevType);
if (eCharType_RightToLeftArabic == charType) {
isBidiSystem = (hints & NS_RENDERING_HINT_ARABIC_SHAPING);
}
if (isBidiSystem && (CHARTYPE_IS_RTL(charType) ^ isRTL) ) {
// set reading order into DC
isRTL = !isRTL;
aRenderingContext.SetRightToLeftText(isRTL);
}
nsAutoString runVisualText;
runVisualText.Assign(aText + start, subRunLength);
FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), subRunLength,
(nsCharType)charType, level & 1,
isBidiSystem);
aRenderingContext.GetWidth(runVisualText.get(), subRunLength, width, nsnull);
if (level & 1) {
aX -= width;
}
aRenderingContext.DrawString(runVisualText.get(), subRunLength, aX, aY, width);
/*
* The caller may request to calculate the visual position of one
* or more characters.
*/
for(int nPosResolve=0; nPosResolve<aPosResolveCount; ++nPosResolve)
{
nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve];
/*
* Did we already resolve this position's visual metric? If so, skip.
*/
if (posResolve->visualLeftTwips != kNotFound)
continue;
/*
* First find out if the logical position is within this run.
*/
if (start <= posResolve->logicalIndex &&
start + subRunLength > posResolve->logicalIndex) {
/*
* If this run is only one character long, we have an easy case:
* the visual position is the x-coord of the start of the run
* less the x-coord of the start of the whole text (saved in xStartText).
*/
if (subRunLength == 1) {
posResolve->visualIndex = visualStart;
posResolve->visualLeftTwips = aX - xStartText;
}
/*
* Otherwise, we need to measure the width of the run's part
* which is to the visual left of the index.
* In other words, the run is broken in two, around the logical index,
* and we measure the part which is visually left.
* If the run is right-to-left, this part will span from after the index
* up to the end of the run; if it is left-to-right, this part will span
* from the start of the run up to (and inclduing) the character before the index.
*/
else {
nscoord subWidth;
// The position in the text where this run's "left part" begins.
const PRUnichar* visualLeftPart;
if (level & 1) {
// One day, son, this could all be replaced with mBidiEngine.GetVisualIndex ...
posResolve->visualIndex = visualStart + (subRunLength - (posResolve->logicalIndex + 1 - start));
// Skipping to the "left part".
visualLeftPart = aText + posResolve->logicalIndex + 1;
}
else {
posResolve->visualIndex = visualStart + (posResolve->logicalIndex - start);
// Skipping to the "left part".
visualLeftPart = aText + start;
}
// The delta between the start of the run and the left part's end.
PRInt32 visualLeftLength = posResolve->visualIndex - visualStart;
aRenderingContext.GetWidth(visualLeftPart,
visualLeftLength,
subWidth, nsnull);
posResolve->visualLeftTwips = aX + subWidth - xStartText;
}
}
}
if (!(level & 1)) {
aX += width;
}
--subRunCount;
start = lineOffset;
subRunLimit = typeLimit;
subRunLength = typeLimit - lineOffset;
} // while
if (level & 1) {
aX = xEndRun;
}
visualStart += length;
} // for
// Restore original reading order
if (isRTL) {
aRenderingContext.SetRightToLeftText(PR_FALSE);
}
return NS_OK;
}
nsresult
nsBidiPresUtils::ReorderUnicodeText(PRUnichar* aText,
PRInt32& aTextLength,
nsCharType aCharType,
PRBool aIsOddLevel,
PRBool aIsBidiSystem)
{
NS_ASSERTION(aIsOddLevel == 0 || aIsOddLevel == 1, "aIsOddLevel should be 0 or 1");
nsresult rv = NS_OK;
PRBool doReverse = PR_FALSE;
if (aIsBidiSystem) {
if ( (CHARTYPE_IS_RTL(aCharType)) ^ (aIsOddLevel) )
doReverse = PR_TRUE;
}
else {
if (aIsOddLevel)
doReverse = PR_TRUE;
}
if (doReverse) {
PRInt32 newLen;
if (mBuffer.Length() < aTextLength) {
mBuffer.SetLength(aTextLength);
}
PRUnichar* buffer = mBuffer.BeginWriting();
if (doReverse) {
rv = mBidiEngine->WriteReverse(aText, aTextLength, buffer,
NSBIDI_DO_MIRRORING, &newLen);
if (NS_SUCCEEDED(rv) ) {
aTextLength = newLen;
memcpy(aText, buffer, aTextLength * sizeof(PRUnichar) );
}
}
}
return rv;
}
#endif // IBMBIDI