Merge mozilla-central and tracemonkey. (a=blockers)
[mozilla-central.git] / layout / base / nsBidiPresUtils.cpp
blob9a4da6dbe697eebac95389ec5bbac7f610f607d3
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Communicator client code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Uri Bernstein <uriber@gmail.com>
24 * Haamed Gheibi <gheibi@metanetworking.com>
25 * Ehsan Akhgari <ehsan.akhgari@gmail.com>
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
41 #ifdef IBMBIDI
43 #include "nsBidiPresUtils.h"
44 #include "nsTextFragment.h"
45 #include "nsGkAtoms.h"
46 #include "nsPresContext.h"
47 #include "nsIRenderingContext.h"
48 #include "nsIServiceManager.h"
49 #include "nsFrameManager.h"
50 #include "nsBidiFrames.h"
51 #include "nsBidiUtils.h"
52 #include "nsCSSFrameConstructor.h"
53 #include "nsHTMLContainerFrame.h"
54 #include "nsInlineFrame.h"
55 #include "nsPlaceholderFrame.h"
56 #include "nsContainerFrame.h"
57 #include "nsFirstLetterFrame.h"
58 #include "gfxUnicodeProperties.h"
59 #include "nsIThebesFontMetrics.h"
61 using namespace mozilla;
63 static const PRUnichar kSpace = 0x0020;
64 static const PRUnichar kLineSeparator = 0x2028;
65 static const PRUnichar kObjectSubstitute = 0xFFFC;
66 static const PRUnichar kLRE = 0x202A;
67 static const PRUnichar kRLE = 0x202B;
68 static const PRUnichar kLRO = 0x202D;
69 static const PRUnichar kRLO = 0x202E;
70 static const PRUnichar kPDF = 0x202C;
71 static const PRUnichar ALEF = 0x05D0;
73 #define CHAR_IS_HEBREW(c) ((0x0590 <= (c)) && ((c)<= 0x05FF))
74 // Note: The above code are moved from gfx/src/windows/nsRenderingContextWin.cpp
76 nsIFrame*
77 NS_NewDirectionalFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUnichar aChar);
79 nsBidiPresUtils::nsBidiPresUtils() : mArraySize(8),
80 mIndexMap(nsnull),
81 mLevels(nsnull),
82 mSuccess(NS_ERROR_FAILURE),
83 mBidiEngine(nsnull)
85 mBidiEngine = new nsBidi();
86 if (mBidiEngine && mContentToFrameIndex.Init()) {
87 mSuccess = NS_OK;
91 nsBidiPresUtils::~nsBidiPresUtils()
93 if (mLevels) {
94 delete[] mLevels;
96 if (mIndexMap) {
97 delete[] mIndexMap;
99 delete mBidiEngine;
102 PRBool
103 nsBidiPresUtils::IsSuccessful() const
105 return NS_SUCCEEDED(mSuccess);
108 /* Some helper methods for Resolve() */
110 // Should this frame be split between text runs?
111 PRBool
112 IsBidiSplittable(nsIFrame* aFrame) {
113 nsIAtom* frameType = aFrame->GetType();
114 // Bidi inline containers should be split, unless they're line frames.
115 return aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer)
116 && frameType != nsGkAtoms::lineFrame;
119 static nsresult
120 SplitInlineAncestors(nsIFrame* aFrame)
122 nsPresContext *presContext = aFrame->PresContext();
123 nsIPresShell *presShell = presContext->PresShell();
124 nsIFrame* frame = aFrame;
125 nsIFrame* parent = aFrame->GetParent();
126 nsIFrame* newParent;
128 while (IsBidiSplittable(parent)) {
129 nsIFrame* grandparent = parent->GetParent();
130 NS_ASSERTION(grandparent, "Couldn't get parent's parent in nsBidiPresUtils::SplitInlineAncestors");
132 nsresult rv = presShell->FrameConstructor()->
133 CreateContinuingFrame(presContext, parent, grandparent, &newParent, PR_FALSE);
134 if (NS_FAILED(rv)) {
135 return rv;
138 // Split the child list after |frame|.
139 nsContainerFrame* container = do_QueryFrame(parent);
140 nsFrameList tail = container->StealFramesAfter(frame);
142 // Reparent views as necessary
143 rv = nsHTMLContainerFrame::ReparentFrameViewList(presContext, tail, parent, newParent);
144 if (NS_FAILED(rv)) {
145 return rv;
148 // The parent's continuation adopts the siblings after the split.
149 rv = newParent->InsertFrames(nsGkAtoms::nextBidi, nsnull, tail);
150 if (NS_FAILED(rv)) {
151 return rv;
153 // The list name nsGkAtoms::nextBidi would indicate we don't want reflow
154 nsFrameList temp(newParent, newParent);
155 rv = grandparent->InsertFrames(nsGkAtoms::nextBidi, parent, temp);
156 if (NS_FAILED(rv)) {
157 return rv;
160 frame = parent;
161 parent = grandparent;
164 return NS_OK;
167 // Convert bidi continuations to fluid continuations for a frame and all of its
168 // inline ancestors.
169 static void
170 JoinInlineAncestors(nsIFrame* aFrame)
172 nsIFrame* frame = aFrame;
173 while (frame && IsBidiSplittable(frame)) {
174 nsIFrame* next = frame->GetNextContinuation();
175 if (next) {
176 NS_ASSERTION (!frame->GetNextInFlow() || frame->GetNextInFlow() == next,
177 "next-in-flow is not next continuation!");
178 frame->SetNextInFlow(next);
180 NS_ASSERTION (!next->GetPrevInFlow() || next->GetPrevInFlow() == frame,
181 "prev-in-flow is not prev continuation!");
182 next->SetPrevInFlow(frame);
184 // Join the parent only as long as we're its last child.
185 if (frame->GetNextSibling())
186 break;
187 frame = frame->GetParent();
191 static nsresult
192 CreateBidiContinuation(nsIFrame* aFrame,
193 nsIFrame** aNewFrame)
195 NS_PRECONDITION(aNewFrame, "null OUT ptr");
196 NS_PRECONDITION(aFrame, "null ptr");
198 *aNewFrame = nsnull;
200 nsPresContext *presContext = aFrame->PresContext();
201 nsIPresShell *presShell = presContext->PresShell();
202 NS_ASSERTION(presShell, "PresShell must be set on PresContext before calling nsBidiPresUtils::CreateBidiContinuation");
204 nsIFrame* parent = aFrame->GetParent();
205 NS_ASSERTION(parent, "Couldn't get frame parent in nsBidiPresUtils::CreateBidiContinuation");
207 nsresult rv = NS_OK;
209 // Have to special case floating first letter frames because the continuation
210 // doesn't go in the first letter frame. The continuation goes with the rest
211 // of the text that the first letter frame was made out of.
212 if (parent->GetType() == nsGkAtoms::letterFrame &&
213 parent->GetStyleDisplay()->IsFloating()) {
214 nsFirstLetterFrame* letterFrame = do_QueryFrame(parent);
215 rv = letterFrame->CreateContinuationForFloatingParent(presContext, aFrame,
216 aNewFrame, PR_FALSE);
217 return rv;
220 rv = presShell->FrameConstructor()->
221 CreateContinuingFrame(presContext, aFrame, parent, aNewFrame, PR_FALSE);
222 if (NS_FAILED(rv)) {
223 return rv;
226 // The list name nsGkAtoms::nextBidi would indicate we don't want reflow
227 // XXXbz this needs higher-level framelist love
228 nsFrameList temp(*aNewFrame, *aNewFrame);
229 rv = parent->InsertFrames(nsGkAtoms::nextBidi, aFrame, temp);
230 if (NS_FAILED(rv)) {
231 return rv;
234 // Split inline ancestor frames
235 rv = SplitInlineAncestors(aFrame);
236 if (NS_FAILED(rv)) {
237 return rv;
240 return NS_OK;
243 static PRBool
244 IsFrameInCurrentLine(nsBlockInFlowLineIterator* aLineIter,
245 nsIFrame* aPrevFrame, nsIFrame* aFrame)
247 nsIFrame* endFrame = aLineIter->IsLastLineInList() ? nsnull :
248 aLineIter->GetLine().next()->mFirstChild;
249 nsIFrame* startFrame = aPrevFrame ? aPrevFrame : aLineIter->GetLine()->mFirstChild;
250 for (nsIFrame* frame = startFrame; frame && frame != endFrame;
251 frame = frame->GetNextSibling()) {
252 if (frame == aFrame)
253 return PR_TRUE;
255 return PR_FALSE;
258 static void
259 AdvanceLineIteratorToFrame(nsIFrame* aFrame,
260 nsBlockInFlowLineIterator* aLineIter,
261 nsIFrame*& aPrevFrame)
263 // Advance aLine to the line containing aFrame
264 nsIFrame* child = aFrame;
265 nsFrameManager* frameManager = aFrame->PresContext()->FrameManager();
266 nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(frameManager, child);
267 while (parent && !nsLayoutUtils::GetAsBlock(parent)) {
268 child = parent;
269 parent = nsLayoutUtils::GetParentOrPlaceholderFor(frameManager, child);
271 NS_ASSERTION (parent, "aFrame is not a descendent of aBlockFrame");
272 while (!IsFrameInCurrentLine(aLineIter, aPrevFrame, child)) {
273 #ifdef DEBUG
274 PRBool hasNext =
275 #endif
276 aLineIter->Next();
277 NS_ASSERTION(hasNext, "Can't find frame in lines!");
278 aPrevFrame = nsnull;
280 aPrevFrame = child;
284 * Overview of the implementation of Resolve():
286 * Walk through the descendants of aBlockFrame and build:
287 * * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
288 * * mBuffer: an nsAutoString containing a representation of
289 * the content of the frames.
290 * In the case of text frames, this is the actual text context of the
291 * frames, but some other elements are represented in a symbolic form which
292 * will make the Unicode Bidi Algorithm give the correct results.
293 * Bidi embeddings and overrides set by CSS or <bdo> elements are
294 * represented by the corresponding Unicode control characters.
295 * <br> elements are represented by U+2028 LINE SEPARATOR
296 * Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
297 * CHARACTER
299 * Then pass mBuffer to the Bidi engine for resolving of embedding levels
300 * by nsBidi::SetPara() and division into directional runs by
301 * nsBidi::CountRuns().
303 * Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
304 * correlate them with the frames indexed in mLogicalFrames, setting the
305 * baseLevel and embeddingLevel properties according to the results returned
306 * by the Bidi engine.
308 * The rendering layer requires each text frame to contain text in only one
309 * direction, so we may need to call EnsureBidiContinuation() to split frames.
310 * We may also need to call RemoveBidiContinuation() to convert frames created
311 * by EnsureBidiContinuation() in previous reflows into fluid continuations.
313 nsresult
314 nsBidiPresUtils::Resolve(nsBlockFrame* aBlockFrame)
316 mLogicalFrames.Clear();
317 mContentToFrameIndex.Clear();
319 nsPresContext *presContext = aBlockFrame->PresContext();
320 nsIPresShell* shell = presContext->PresShell();
321 nsStyleContext* styleContext = aBlockFrame->GetStyleContext();
323 // handle bidi-override being set on the block itself before calling
324 // InitLogicalArray.
325 const nsStyleVisibility* vis = aBlockFrame->GetStyleVisibility();
326 const nsStyleTextReset* text = aBlockFrame->GetStyleTextReset();
328 if (text->mUnicodeBidi == NS_STYLE_UNICODE_BIDI_OVERRIDE) {
329 nsIFrame *directionalFrame = nsnull;
331 if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
332 directionalFrame = NS_NewDirectionalFrame(shell, styleContext, kRLO);
334 else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) {
335 directionalFrame = NS_NewDirectionalFrame(shell, styleContext, kLRO);
338 if (directionalFrame) {
339 mLogicalFrames.AppendElement(directionalFrame);
342 for (nsBlockFrame* block = aBlockFrame; block;
343 block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
344 block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
345 InitLogicalArray(block->GetFirstChild(nsnull));
348 if (text->mUnicodeBidi == NS_STYLE_UNICODE_BIDI_OVERRIDE) {
349 nsIFrame* directionalFrame = NS_NewDirectionalFrame(shell, styleContext, kPDF);
350 if (directionalFrame) {
351 mLogicalFrames.AppendElement(directionalFrame);
355 CreateBlockBuffer();
357 PRInt32 bufferLength = mBuffer.Length();
359 if (bufferLength < 1) {
360 mSuccess = NS_OK;
361 return mSuccess;
363 PRInt32 runCount;
364 PRUint8 embeddingLevel;
366 nsBidiLevel paraLevel = embeddingLevel =
367 (NS_STYLE_DIRECTION_RTL == vis->mDirection)
368 ? NSBIDI_RTL : NSBIDI_LTR;
370 mSuccess = mBidiEngine->SetPara(mBuffer.get(), bufferLength, paraLevel, nsnull);
371 if (NS_FAILED(mSuccess) ) {
372 return mSuccess;
375 mSuccess = mBidiEngine->CountRuns(&runCount);
376 if (NS_FAILED(mSuccess) ) {
377 return mSuccess;
379 PRInt32 runLength = 0; // the length of the current run of text
380 PRInt32 lineOffset = 0; // the start of the current run
381 PRInt32 logicalLimit = 0; // the end of the current run + 1
382 PRInt32 numRun = -1;
383 PRInt32 fragmentLength = 0; // the length of the current text frame
384 PRInt32 frameIndex = -1; // index to the frames in mLogicalFrames
385 PRInt32 frameCount = mLogicalFrames.Length();
386 PRInt32 contentOffset = 0; // offset of current frame in its content node
387 PRBool isTextFrame = PR_FALSE;
388 nsIFrame* frame = nsnull;
389 nsIContent* content = nsnull;
390 PRInt32 contentTextLength;
391 nsIAtom* frameType = nsnull;
393 FramePropertyTable *propTable = presContext->PropertyTable();
395 nsBlockInFlowLineIterator lineIter(aBlockFrame, aBlockFrame->begin_lines(), PR_FALSE);
396 if (lineIter.GetLine() == aBlockFrame->end_lines()) {
397 // Advance to first valid line (might be in a next-continuation)
398 lineIter.Next();
400 nsIFrame* prevFrame = nsnull;
401 PRBool lineNeedsUpdate = PR_FALSE;
403 PRBool isVisual = presContext->IsVisualMode();
404 if (isVisual) {
406 * Drill up in content to detect whether this is an element that needs to be
407 * rendered with logical order even on visual pages.
409 * We always use logical order on form controls, firstly so that text entry
410 * will be in logical order, but also because visual pages were written with
411 * the assumption that even if the browser had no support for right-to-left
412 * text rendering, it would use native widgets with bidi support to display
413 * form controls.
415 * We also use logical order in XUL elements, since we expect that if a XUL
416 * element appears in a visual page, it will be generated by an XBL binding
417 * and contain localized text which will be in logical order.
419 for (content = aBlockFrame->GetContent() ; content; content = content->GetParent()) {
420 if (content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) || content->IsXUL()) {
421 isVisual = PR_FALSE;
422 break;
427 for (; ;) {
428 if (fragmentLength <= 0) {
429 // Get the next frame from mLogicalFrames
430 if (++frameIndex >= frameCount) {
431 break;
433 frame = mLogicalFrames[frameIndex];
434 frameType = frame->GetType();
435 lineNeedsUpdate = PR_TRUE;
436 if (nsGkAtoms::textFrame == frameType) {
437 content = frame->GetContent();
438 if (!content) {
439 mSuccess = NS_OK;
440 break;
442 contentTextLength = content->TextLength();
443 if (contentTextLength == 0) {
444 frame->AdjustOffsetsForBidi(0, 0);
445 // Set the base level and embedding level of the current run even
446 // on an empty frame. Otherwise frame reordering will not be correct.
447 propTable->Set(frame, nsIFrame::EmbeddingLevelProperty(),
448 NS_INT32_TO_PTR(embeddingLevel));
449 propTable->Set(frame, nsIFrame::BaseLevelProperty(),
450 NS_INT32_TO_PTR(paraLevel));
451 continue;
453 PRInt32 start, end;
454 frame->GetOffsets(start, end);
455 NS_ASSERTION(!(contentTextLength < end - start),
456 "Frame offsets don't fit in content");
457 fragmentLength = NS_MIN(contentTextLength, end - start);
458 contentOffset = start;
459 isTextFrame = PR_TRUE;
461 else {
463 * Any non-text frame corresponds to a single character in the text buffer
464 * (a bidi control character, LINE SEPARATOR, or OBJECT SUBSTITUTE)
466 isTextFrame = PR_FALSE;
467 fragmentLength = 1;
469 } // if (fragmentLength <= 0)
471 if (runLength <= 0) {
472 // Get the next run of text from the Bidi engine
473 if (++numRun >= runCount) {
474 break;
476 lineOffset = logicalLimit;
477 if (NS_FAILED(mBidiEngine->GetLogicalRun(
478 lineOffset, &logicalLimit, &embeddingLevel) ) ) {
479 break;
481 runLength = logicalLimit - lineOffset;
482 if (isVisual) {
483 embeddingLevel = paraLevel;
485 } // if (runLength <= 0)
487 if (nsGkAtoms::directionalFrame == frameType) {
488 frame->Destroy();
489 frame = nsnull;
490 ++lineOffset;
492 else {
493 propTable->Set(frame, nsIFrame::EmbeddingLevelProperty(),
494 NS_INT32_TO_PTR(embeddingLevel));
495 propTable->Set(frame, nsIFrame::BaseLevelProperty(),
496 NS_INT32_TO_PTR(paraLevel));
497 if (isTextFrame) {
498 if ( (runLength > 0) && (runLength < fragmentLength) ) {
500 * The text in this frame continues beyond the end of this directional run.
501 * Create a non-fluid continuation frame for the next directional run.
503 if (lineNeedsUpdate) {
504 AdvanceLineIteratorToFrame(frame, &lineIter, prevFrame);
505 lineNeedsUpdate = PR_FALSE;
507 lineIter.GetLine()->MarkDirty();
508 nsIFrame* nextBidi;
509 PRInt32 runEnd = contentOffset + runLength;
510 EnsureBidiContinuation(frame, &nextBidi, frameIndex,
511 contentOffset,
512 runEnd);
513 if (NS_FAILED(mSuccess)) {
514 break;
516 nextBidi->AdjustOffsetsForBidi(runEnd,
517 contentOffset + fragmentLength);
518 frame = nextBidi;
519 contentOffset = runEnd;
520 } // if (runLength < fragmentLength)
521 else {
522 if (contentOffset + fragmentLength == contentTextLength) {
524 * We have finished all the text in this content node. Convert any
525 * further non-fluid continuations to fluid continuations and advance
526 * frameIndex to the last frame in the content node
528 PRInt32 newIndex = 0;
529 mContentToFrameIndex.Get(content, &newIndex);
530 if (newIndex > frameIndex) {
531 RemoveBidiContinuation(frame, frameIndex, newIndex, lineOffset);
532 frameIndex = newIndex;
534 } else if (fragmentLength > 0 && runLength > fragmentLength) {
536 * There is more text that belongs to this directional run in the next
537 * text frame: make sure it is a fluid continuation of the current frame.
538 * Do not advance frameIndex, because the next frame may contain
539 * multi-directional text and need to be split
541 PRInt32 newIndex = frameIndex;
542 do {
543 } while (mLogicalFrames[++newIndex]->GetType() == nsGkAtoms::directionalFrame);
544 RemoveBidiContinuation(frame, frameIndex, newIndex, lineOffset);
545 } else if (runLength == fragmentLength) {
547 * The directional run ends at the end of the frame. Make sure that
548 * the next frame is a non-fluid continuation
550 nsIFrame* next = frame->GetNextInFlow();
551 if (next) {
552 frame->SetNextContinuation(next);
553 next->SetPrevContinuation(frame);
556 frame->AdjustOffsetsForBidi(contentOffset, contentOffset + fragmentLength);
557 if (lineNeedsUpdate) {
558 AdvanceLineIteratorToFrame(frame, &lineIter, prevFrame);
559 lineNeedsUpdate = PR_FALSE;
561 lineIter.GetLine()->MarkDirty();
563 } // isTextFrame
564 else {
565 ++lineOffset;
567 } // not directionalFrame
568 PRInt32 temp = runLength;
569 runLength -= fragmentLength;
570 fragmentLength -= temp;
572 if (frame && fragmentLength <= 0) {
573 // If the frame is at the end of a run, split all ancestor inlines that
574 // need splitting.
575 // To determine whether we're at the end of the run, we check that we've
576 // finished processing the current run, and that the current frame
577 // doesn't have a fluid continuation (it could have a fluid continuation
578 // of zero length, so testing runLength alone is not sufficient).
579 if (runLength <= 0 && !frame->GetNextInFlow()) {
580 nsIFrame* child = frame;
581 nsIFrame* parent = frame->GetParent();
582 // As long as we're on the last sibling, the parent doesn't have to be split.
583 // However, if the parent has a fluid continuation, we do have to make
584 // it non-fluid. This can happen e.g. when we have a first-letter frame
585 // and the end of the first-letter coincides with the end of a
586 // directional run.
587 while (parent &&
588 IsBidiSplittable(parent) &&
589 !child->GetNextSibling()) {
590 nsIFrame* next = parent->GetNextInFlow();
591 if (next) {
592 parent->SetNextContinuation(next);
593 next->SetPrevContinuation(parent);
595 child = parent;
596 parent = child->GetParent();
598 if (parent && IsBidiSplittable(parent))
599 SplitInlineAncestors(child);
601 else if (!frame->GetNextSibling()) {
602 // We're not at an end of a run, and |frame| is the last child of its parent.
603 // If its ancestors happen to have bidi continuations, convert them into
604 // fluid continuations.
605 nsIFrame* parent = frame->GetParent();
606 JoinInlineAncestors(parent);
609 } // for
610 return mSuccess;
613 // Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
614 PRBool IsBidiLeaf(nsIFrame* aFrame) {
615 nsIFrame* kid = aFrame->GetFirstChild(nsnull);
616 return !kid
617 || !aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer);
620 void
621 nsBidiPresUtils::InitLogicalArray(nsIFrame* aCurrentFrame)
623 if (!aCurrentFrame)
624 return;
626 nsIPresShell* shell = aCurrentFrame->PresContext()->PresShell();
627 nsStyleContext* styleContext;
629 for (nsIFrame* childFrame = aCurrentFrame; childFrame;
630 childFrame = childFrame->GetNextSibling()) {
632 // If the real frame for a placeholder is a first letter frame, we need to
633 // drill down into it and include its contents in Bidi resolution.
634 // If not, we just use the placeholder.
635 nsIFrame* frame = childFrame;
636 if (nsGkAtoms::placeholderFrame == childFrame->GetType()) {
637 nsIFrame* realFrame =
638 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
639 if (realFrame->GetType() == nsGkAtoms::letterFrame) {
640 frame = realFrame;
644 PRUnichar ch = 0;
645 if (frame->IsFrameOfType(nsIFrame::eBidiInlineContainer)) {
646 const nsStyleVisibility* vis = frame->GetStyleVisibility();
647 const nsStyleTextReset* text = frame->GetStyleTextReset();
648 switch (text->mUnicodeBidi) {
649 case NS_STYLE_UNICODE_BIDI_NORMAL:
650 break;
651 case NS_STYLE_UNICODE_BIDI_EMBED:
652 styleContext = frame->GetStyleContext();
654 if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
655 ch = kRLE;
657 else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) {
658 ch = kLRE;
660 break;
661 case NS_STYLE_UNICODE_BIDI_OVERRIDE:
662 styleContext = frame->GetStyleContext();
664 if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
665 ch = kRLO;
667 else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) {
668 ch = kLRO;
670 break;
673 // Create a directional frame before the first frame of an
674 // element specifying embedding or override
675 if (ch != 0 && !frame->GetPrevContinuation()) {
676 nsIFrame* dirFrame = NS_NewDirectionalFrame(shell, styleContext, ch);
677 if (dirFrame) {
678 mLogicalFrames.AppendElement(dirFrame);
683 if (IsBidiLeaf(frame)) {
684 /* Bidi leaf frame: add the frame to the mLogicalFrames array,
685 * and add its index to the mContentToFrameIndex hashtable. This
686 * will be used in RemoveBidiContinuation() to identify the last
687 * frame in the array with a given content.
689 nsIContent* content = frame->GetContent();
690 if (content) {
691 mContentToFrameIndex.Put(content, mLogicalFrames.Length());
693 mLogicalFrames.AppendElement(frame);
695 else {
696 nsIFrame* kid = frame->GetFirstChild(nsnull);
697 InitLogicalArray(kid);
700 // If the element is attributed by dir, indicate direction pop (add PDF frame)
701 if (ch != 0 && !frame->GetNextContinuation()) {
702 // Create a directional frame after the last frame of an
703 // element specifying embedding or override
704 nsIFrame* dirFrame = NS_NewDirectionalFrame(shell, styleContext, kPDF);
705 if (dirFrame) {
706 mLogicalFrames.AppendElement(dirFrame);
709 } // for
712 void
713 nsBidiPresUtils::CreateBlockBuffer()
715 mBuffer.SetLength(0);
717 nsIFrame* frame;
718 nsIContent* prevContent = nsnull;
719 PRUint32 i;
720 PRUint32 count = mLogicalFrames.Length();
722 for (i = 0; i < count; i++) {
723 frame = mLogicalFrames[i];
724 nsIAtom* frameType = frame->GetType();
726 if (nsGkAtoms::textFrame == frameType) {
727 nsIContent* content = frame->GetContent();
728 if (!content) {
729 mSuccess = NS_OK;
730 break;
732 if (content == prevContent) {
733 continue;
735 prevContent = content;
736 content->AppendTextTo(mBuffer);
738 else if (nsGkAtoms::brFrame == frameType) { // break frame
739 // Append line separator
740 mBuffer.Append(kLineSeparator);
742 else if (nsGkAtoms::directionalFrame == frameType) {
743 nsDirectionalFrame* dirFrame = static_cast<nsDirectionalFrame*>(frame);
744 mBuffer.Append(dirFrame->GetChar());
746 else { // not text frame
747 // See the Unicode Bidi Algorithm:
748 // "...inline objects (such as graphics) are treated as if they are ... U+FFFC"
749 mBuffer.Append(kObjectSubstitute);
752 // XXX: TODO: Handle preformatted text ('\n')
753 mBuffer.ReplaceChar("\t\r\n", kSpace);
756 void
757 nsBidiPresUtils::ReorderFrames(nsIFrame* aFirstFrameOnLine,
758 PRInt32 aNumFramesOnLine)
760 // If this line consists of a line frame, reorder the line frame's children.
761 if (aFirstFrameOnLine->GetType() == nsGkAtoms::lineFrame) {
762 aFirstFrameOnLine = aFirstFrameOnLine->GetFirstChild(nsnull);
763 if (!aFirstFrameOnLine)
764 return;
765 // All children of the line frame are on the first line. Setting aNumFramesOnLine
766 // to -1 makes InitLogicalArrayFromLine look at all of them.
767 aNumFramesOnLine = -1;
770 InitLogicalArrayFromLine(aFirstFrameOnLine, aNumFramesOnLine);
772 PRBool isReordered;
773 PRBool hasRTLFrames;
774 Reorder(isReordered, hasRTLFrames);
775 RepositionInlineFrames(aFirstFrameOnLine);
778 nsresult
779 nsBidiPresUtils::Reorder(PRBool& aReordered, PRBool& aHasRTLFrames)
781 aReordered = PR_FALSE;
782 aHasRTLFrames = PR_FALSE;
783 PRInt32 count = mLogicalFrames.Length();
785 if (mArraySize < count) {
786 mArraySize = count << 1;
787 if (mLevels) {
788 delete[] mLevels;
789 mLevels = nsnull;
791 if (mIndexMap) {
792 delete[] mIndexMap;
793 mIndexMap = nsnull;
796 if (!mLevels) {
797 mLevels = new PRUint8[mArraySize];
798 if (!mLevels) {
799 return NS_ERROR_OUT_OF_MEMORY;
802 memset(mLevels, 0, sizeof(PRUint8) * mArraySize);
804 nsIFrame* frame;
805 PRInt32 i;
807 for (i = 0; i < count; i++) {
808 frame = mLogicalFrames[i];
809 mLevels[i] = GetFrameEmbeddingLevel(frame);
810 if (mLevels[i] & 1) {
811 aHasRTLFrames = PR_TRUE;
814 if (!mIndexMap) {
815 mIndexMap = new PRInt32[mArraySize];
817 if (!mIndexMap) {
818 mSuccess = NS_ERROR_OUT_OF_MEMORY;
820 else {
821 memset(mIndexMap, 0, sizeof(PRUint32) * mArraySize);
823 mSuccess = mBidiEngine->ReorderVisual(mLevels, count, mIndexMap);
825 if (NS_SUCCEEDED(mSuccess) ) {
826 mVisualFrames.Clear();
828 for (i = 0; i < count; i++) {
829 mVisualFrames.AppendElement(mLogicalFrames[mIndexMap[i]]);
830 if (i != mIndexMap[i]) {
831 aReordered = PR_TRUE;
834 } // NS_SUCCEEDED(mSuccess)
835 } // indexMap
837 if (NS_FAILED(mSuccess) ) {
838 aReordered = PR_FALSE;
840 return mSuccess;
843 nsBidiLevel
844 nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame)
846 nsIFrame* firstLeaf = aFrame;
847 while (!IsBidiLeaf(firstLeaf)) {
848 nsIFrame* firstChild = firstLeaf->GetFirstChild(nsnull);
849 nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(firstChild);
850 firstLeaf = (realFrame->GetType() == nsGkAtoms::letterFrame) ?
851 realFrame : firstChild;
853 return NS_GET_EMBEDDING_LEVEL(firstLeaf);
856 nsBidiLevel
857 nsBidiPresUtils::GetFrameBaseLevel(nsIFrame* aFrame)
859 nsIFrame* firstLeaf = aFrame;
860 while (!IsBidiLeaf(firstLeaf)) {
861 firstLeaf = firstLeaf->GetFirstChild(nsnull);
863 return NS_GET_BASE_LEVEL(firstLeaf);
866 void
867 nsBidiPresUtils::IsLeftOrRightMost(nsIFrame* aFrame,
868 nsContinuationStates* aContinuationStates,
869 PRBool& aIsLeftMost /* out */,
870 PRBool& aIsRightMost /* out */) const
872 const nsStyleVisibility* vis = aFrame->GetStyleVisibility();
873 PRBool isLTR = (NS_STYLE_DIRECTION_LTR == vis->mDirection);
876 * Since we lay out frames from left to right (in both LTR and RTL), visiting a
877 * frame with 'mFirstVisualFrame == nsnull', means it's the first appearance of
878 * one of its continuation chain frames on the line.
879 * To determine if it's the last visual frame of its continuation chain on the line
880 * or not, we count the number of frames of the chain on the line, and then reduce
881 * it when we lay out a frame of the chain. If this value becomes 1 it means
882 * that it's the last visual frame of its continuation chain on this line.
885 nsFrameContinuationState* frameState = aContinuationStates->GetEntry(aFrame);
886 nsFrameContinuationState* firstFrameState;
888 if (!frameState->mFirstVisualFrame) {
889 // aFrame is the first visual frame of its continuation chain
890 nsFrameContinuationState* contState;
891 nsIFrame* frame;
893 frameState->mFrameCount = 1;
894 frameState->mFirstVisualFrame = aFrame;
897 * Traverse continuation chain of aFrame in both backward and forward
898 * directions while the frames are on this line. Count the frames and
899 * set their mFirstVisualFrame to aFrame.
901 // Traverse continuation chain backward
902 for (frame = aFrame->GetPrevContinuation();
903 frame && (contState = aContinuationStates->GetEntry(frame));
904 frame = frame->GetPrevContinuation()) {
905 frameState->mFrameCount++;
906 contState->mFirstVisualFrame = aFrame;
908 frameState->mHasContOnPrevLines = (frame != nsnull);
910 // Traverse continuation chain forward
911 for (frame = aFrame->GetNextContinuation();
912 frame && (contState = aContinuationStates->GetEntry(frame));
913 frame = frame->GetNextContinuation()) {
914 frameState->mFrameCount++;
915 contState->mFirstVisualFrame = aFrame;
917 frameState->mHasContOnNextLines = (frame != nsnull);
919 aIsLeftMost = isLTR ? !frameState->mHasContOnPrevLines
920 : !frameState->mHasContOnNextLines;
921 firstFrameState = frameState;
922 } else {
923 // aFrame is not the first visual frame of its continuation chain
924 aIsLeftMost = PR_FALSE;
925 firstFrameState = aContinuationStates->GetEntry(frameState->mFirstVisualFrame);
928 aIsRightMost = (firstFrameState->mFrameCount == 1) &&
929 (isLTR ? !firstFrameState->mHasContOnNextLines
930 : !firstFrameState->mHasContOnPrevLines);
932 if ((aIsLeftMost || aIsRightMost) &&
933 (aFrame->GetStateBits() & NS_FRAME_IS_SPECIAL)) {
934 // For ib splits, don't treat anything except the last part as
935 // endmost or anything except the first part as startmost.
936 // As an optimization, only get the first continuation once.
937 nsIFrame* firstContinuation = aFrame->GetFirstContinuation();
938 if (nsLayoutUtils::FrameIsNonLastInIBSplit(firstContinuation)) {
939 // We are not endmost
940 if (isLTR) {
941 aIsRightMost = PR_FALSE;
942 } else {
943 aIsLeftMost = PR_FALSE;
946 if (nsLayoutUtils::FrameIsNonFirstInIBSplit(firstContinuation)) {
947 // We are not startmost
948 if (isLTR) {
949 aIsLeftMost = PR_FALSE;
950 } else {
951 aIsRightMost = PR_FALSE;
956 // Reduce number of remaining frames of the continuation chain on the line.
957 firstFrameState->mFrameCount--;
960 void
961 nsBidiPresUtils::RepositionFrame(nsIFrame* aFrame,
962 PRBool aIsOddLevel,
963 nscoord& aLeft,
964 nsContinuationStates* aContinuationStates) const
966 if (!aFrame)
967 return;
969 PRBool isLeftMost, isRightMost;
970 IsLeftOrRightMost(aFrame,
971 aContinuationStates,
972 isLeftMost /* out */,
973 isRightMost /* out */);
975 nsInlineFrame* testFrame = do_QueryFrame(aFrame);
976 if (testFrame) {
977 aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET);
979 if (isLeftMost)
980 aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LEFT_MOST);
981 else
982 aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LEFT_MOST);
984 if (isRightMost)
985 aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_RIGHT_MOST);
986 else
987 aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_RIGHT_MOST);
989 // This method is called from nsBlockFrame::PlaceLine via the call to
990 // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
991 // have been reflowed, which is required for GetUsedMargin/Border/Padding
992 nsMargin margin = aFrame->GetUsedMargin();
993 if (isLeftMost)
994 aLeft += margin.left;
996 nscoord start = aLeft;
998 if (!IsBidiLeaf(aFrame))
1000 nscoord x = 0;
1001 nsMargin borderPadding = aFrame->GetUsedBorderAndPadding();
1002 if (isLeftMost) {
1003 x += borderPadding.left;
1006 // If aIsOddLevel is true, so we need to traverse the child list
1007 // in reverse order, to make it O(n) we store the list locally and
1008 // iterate the list reversely
1009 nsTArray<nsIFrame*> childList;
1010 nsIFrame *frame = aFrame->GetFirstChild(nsnull);
1011 if (frame && aIsOddLevel) {
1012 childList.AppendElement((nsIFrame*)nsnull);
1013 while (frame) {
1014 childList.AppendElement(frame);
1015 frame = frame->GetNextSibling();
1017 frame = childList[childList.Length() - 1];
1020 // Reposition the child frames
1021 PRInt32 index = 0;
1022 while (frame) {
1023 RepositionFrame(frame,
1024 aIsOddLevel,
1026 aContinuationStates);
1027 index++;
1028 frame = aIsOddLevel ?
1029 childList[childList.Length() - index - 1] :
1030 frame->GetNextSibling();
1033 if (isRightMost) {
1034 x += borderPadding.right;
1036 aLeft += x;
1037 } else {
1038 aLeft += aFrame->GetSize().width;
1040 nsRect rect = aFrame->GetRect();
1041 aFrame->SetRect(nsRect(start, rect.y, aLeft - start, rect.height));
1043 if (isRightMost)
1044 aLeft += margin.right;
1047 void
1048 nsBidiPresUtils::InitContinuationStates(nsIFrame* aFrame,
1049 nsContinuationStates* aContinuationStates) const
1051 nsFrameContinuationState* state = aContinuationStates->PutEntry(aFrame);
1052 state->mFirstVisualFrame = nsnull;
1053 state->mFrameCount = 0;
1055 if (!IsBidiLeaf(aFrame)) {
1056 // Continue for child frames
1057 nsIFrame* frame;
1058 for (frame = aFrame->GetFirstChild(nsnull);
1059 frame;
1060 frame = frame->GetNextSibling()) {
1061 InitContinuationStates(frame,
1062 aContinuationStates);
1067 void
1068 nsBidiPresUtils::RepositionInlineFrames(nsIFrame* aFirstChild) const
1070 const nsStyleVisibility* vis = aFirstChild->GetStyleVisibility();
1071 PRBool isLTR = (NS_STYLE_DIRECTION_LTR == vis->mDirection);
1072 nscoord leftSpace = 0;
1074 // This method is called from nsBlockFrame::PlaceLine via the call to
1075 // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
1076 // have been reflowed, which is required for GetUsedMargin/Border/Padding
1077 nsMargin margin = aFirstChild->GetUsedMargin();
1078 if (!aFirstChild->GetPrevContinuation() &&
1079 !nsLayoutUtils::FrameIsNonFirstInIBSplit(aFirstChild))
1080 leftSpace = isLTR ? margin.left : margin.right;
1082 nscoord left = aFirstChild->GetPosition().x - leftSpace;
1083 nsIFrame* frame;
1084 PRInt32 count = mVisualFrames.Length();
1085 PRInt32 index;
1086 nsContinuationStates continuationStates;
1088 continuationStates.Init();
1090 // Initialize continuation states to (nsnull, 0) for
1091 // each frame on the line.
1092 for (index = 0; index < count; index++) {
1093 InitContinuationStates(mVisualFrames[index], &continuationStates);
1096 // Reposition frames in visual order
1097 for (index = 0; index < count; index++) {
1098 frame = mVisualFrames[index];
1099 RepositionFrame(frame,
1100 (mLevels[mIndexMap[index]] & 1),
1101 left,
1102 &continuationStates);
1103 } // for
1106 void
1107 nsBidiPresUtils::InitLogicalArrayFromLine(nsIFrame* aFirstFrameOnLine,
1108 PRInt32 aNumFramesOnLine) {
1109 mLogicalFrames.Clear();
1110 for (nsIFrame* frame = aFirstFrameOnLine;
1111 frame && aNumFramesOnLine--;
1112 frame = frame->GetNextSibling()) {
1113 mLogicalFrames.AppendElement(frame);
1117 PRBool
1118 nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine,
1119 PRInt32 aNumFramesOnLine,
1120 nsIFrame** aFirstVisual,
1121 nsIFrame** aLastVisual)
1123 InitLogicalArrayFromLine(aFirstFrameOnLine, aNumFramesOnLine);
1125 PRBool isReordered;
1126 PRBool hasRTLFrames;
1127 Reorder(isReordered, hasRTLFrames);
1128 PRInt32 count = mLogicalFrames.Length();
1130 if (aFirstVisual) {
1131 *aFirstVisual = mVisualFrames[0];
1133 if (aLastVisual) {
1134 *aLastVisual = mVisualFrames[count-1];
1137 // If there's an RTL frame, assume the line is reordered
1138 return isReordered || hasRTLFrames;
1141 nsIFrame*
1142 nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame,
1143 nsIFrame* aFirstFrameOnLine,
1144 PRInt32 aNumFramesOnLine)
1146 InitLogicalArrayFromLine(aFirstFrameOnLine, aNumFramesOnLine);
1148 PRBool isReordered;
1149 PRBool hasRTLFrames;
1150 Reorder(isReordered, hasRTLFrames);
1151 PRInt32 count = mVisualFrames.Length();
1153 if (aFrame == nsnull)
1154 return mVisualFrames[0];
1156 for (PRInt32 i = 0; i < count - 1; i++) {
1157 if (mVisualFrames[i] == aFrame) {
1158 return mVisualFrames[i+1];
1162 return nsnull;
1165 nsIFrame*
1166 nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame* aFrame,
1167 nsIFrame* aFirstFrameOnLine,
1168 PRInt32 aNumFramesOnLine)
1170 InitLogicalArrayFromLine(aFirstFrameOnLine, aNumFramesOnLine);
1172 PRBool isReordered;
1173 PRBool hasRTLFrames;
1174 Reorder(isReordered, hasRTLFrames);
1175 PRInt32 count = mVisualFrames.Length();
1177 if (aFrame == nsnull)
1178 return mVisualFrames[count-1];
1180 for (PRInt32 i = 1; i < count; i++) {
1181 if (mVisualFrames[i] == aFrame) {
1182 return mVisualFrames[i-1];
1186 return nsnull;
1189 inline void
1190 nsBidiPresUtils::EnsureBidiContinuation(nsIFrame* aFrame,
1191 nsIFrame** aNewFrame,
1192 PRInt32& aFrameIndex,
1193 PRInt32 aStart,
1194 PRInt32 aEnd)
1196 NS_PRECONDITION(aNewFrame, "null OUT ptr");
1197 NS_PRECONDITION(aFrame, "aFrame is null");
1199 aFrame->AdjustOffsetsForBidi(aStart, aEnd);
1200 mSuccess = CreateBidiContinuation(aFrame, aNewFrame);
1203 void
1204 nsBidiPresUtils::RemoveBidiContinuation(nsIFrame* aFrame,
1205 PRInt32 aFirstIndex,
1206 PRInt32 aLastIndex,
1207 PRInt32& aOffset) const
1209 FrameProperties props = aFrame->Properties();
1210 nsBidiLevel embeddingLevel =
1211 (nsBidiLevel)NS_PTR_TO_INT32(props.Get(nsIFrame::EmbeddingLevelProperty()));
1212 nsBidiLevel baseLevel =
1213 (nsBidiLevel)NS_PTR_TO_INT32(props.Get(nsIFrame::BaseLevelProperty()));
1215 for (PRInt32 index = aFirstIndex + 1; index <= aLastIndex; index++) {
1216 nsIFrame* frame = mLogicalFrames[index];
1217 if (nsGkAtoms::directionalFrame == frame->GetType()) {
1218 frame->Destroy();
1219 ++aOffset;
1221 else {
1222 // Make the frame and its continuation ancestors fluid,
1223 // so they can be reused or deleted by normal reflow code
1224 FrameProperties frameProps = frame->Properties();
1225 frameProps.Set(nsIFrame::EmbeddingLevelProperty(),
1226 NS_INT32_TO_PTR(embeddingLevel));
1227 frameProps.Set(nsIFrame::BaseLevelProperty(),
1228 NS_INT32_TO_PTR(baseLevel));
1229 frame->AddStateBits(NS_FRAME_IS_BIDI);
1230 while (frame) {
1231 nsIFrame* prev = frame->GetPrevContinuation();
1232 if (prev) {
1233 NS_ASSERTION (!frame->GetPrevInFlow() || frame->GetPrevInFlow() == prev,
1234 "prev-in-flow is not prev continuation!");
1235 frame->SetPrevInFlow(prev);
1237 NS_ASSERTION (!prev->GetNextInFlow() || prev->GetNextInFlow() == frame,
1238 "next-in-flow is not next continuation!");
1239 prev->SetNextInFlow(frame);
1241 frame = frame->GetParent();
1242 } else {
1243 break;
1250 nsresult
1251 nsBidiPresUtils::FormatUnicodeText(nsPresContext* aPresContext,
1252 PRUnichar* aText,
1253 PRInt32& aTextLength,
1254 nsCharType aCharType,
1255 PRBool aIsOddLevel)
1257 NS_ASSERTION(aIsOddLevel == 0 || aIsOddLevel == 1, "aIsOddLevel should be 0 or 1");
1258 nsresult rv = NS_OK;
1259 // ahmed
1260 //adjusted for correct numeral shaping
1261 PRUint32 bidiOptions = aPresContext->GetBidi();
1262 switch (GET_BIDI_OPTION_NUMERAL(bidiOptions)) {
1264 case IBMBIDI_NUMERAL_HINDI:
1265 HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI);
1266 break;
1268 case IBMBIDI_NUMERAL_ARABIC:
1269 HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
1270 break;
1272 case IBMBIDI_NUMERAL_PERSIAN:
1273 HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_PERSIAN);
1274 break;
1276 case IBMBIDI_NUMERAL_REGULAR:
1278 switch (aCharType) {
1280 case eCharType_EuropeanNumber:
1281 HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
1282 break;
1284 case eCharType_ArabicNumber:
1285 HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI);
1286 break;
1288 default:
1289 break;
1291 break;
1293 case IBMBIDI_NUMERAL_HINDICONTEXT:
1294 if ( ( (GET_BIDI_OPTION_DIRECTION(bidiOptions)==IBMBIDI_TEXTDIRECTION_RTL) && (IS_ARABIC_DIGIT (aText[0])) ) || (eCharType_ArabicNumber == aCharType) )
1295 HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI);
1296 else if (eCharType_EuropeanNumber == aCharType)
1297 HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
1298 break;
1300 case IBMBIDI_NUMERAL_PERSIANCONTEXT:
1301 if ( ( (GET_BIDI_OPTION_DIRECTION(bidiOptions)==IBMBIDI_TEXTDIRECTION_RTL) && (IS_ARABIC_DIGIT (aText[0])) ) || (eCharType_ArabicNumber == aCharType) )
1302 HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_PERSIAN);
1303 else if (eCharType_EuropeanNumber == aCharType)
1304 HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
1305 break;
1307 case IBMBIDI_NUMERAL_NOMINAL:
1308 default:
1309 break;
1312 StripBidiControlCharacters(aText, aTextLength);
1313 return rv;
1316 void
1317 nsBidiPresUtils::StripBidiControlCharacters(PRUnichar* aText,
1318 PRInt32& aTextLength) const
1320 if ( (nsnull == aText) || (aTextLength < 1) ) {
1321 return;
1324 PRInt32 stripLen = 0;
1326 for (PRInt32 i = 0; i < aTextLength; i++) {
1327 // XXX: This silently ignores surrogate characters.
1328 // As of Unicode 4.0, all Bidi control characters are within the BMP.
1329 if (IsBidiControl((PRUint32)aText[i])) {
1330 ++stripLen;
1332 else {
1333 aText[i - stripLen] = aText[i];
1336 aTextLength -= stripLen;
1339 #if 0 // XXX: for the future use ???
1340 void
1341 RemoveDiacritics(PRUnichar* aText,
1342 PRInt32& aTextLength)
1344 if (aText && (aTextLength > 0) ) {
1345 PRInt32 offset = 0;
1347 for (PRInt32 i = 0; i < aTextLength && aText[i]; i++) {
1348 if (IS_BIDI_DIACRITIC(aText[i]) ) {
1349 ++offset;
1350 continue;
1352 aText[i - offset] = aText[i];
1354 aTextLength = i - offset;
1355 aText[aTextLength] = 0;
1358 #endif
1360 void
1361 nsBidiPresUtils::CalculateCharType(PRInt32& aOffset,
1362 PRInt32 aCharTypeLimit,
1363 PRInt32& aRunLimit,
1364 PRInt32& aRunLength,
1365 PRInt32& aRunCount,
1366 PRUint8& aCharType,
1367 PRUint8& aPrevCharType) const
1370 PRBool strongTypeFound = PR_FALSE;
1371 PRInt32 offset;
1372 nsCharType charType;
1374 aCharType = eCharType_OtherNeutral;
1376 for (offset = aOffset; offset < aCharTypeLimit; offset++) {
1377 // Make sure we give RTL chartype to all characters that would be classified
1378 // as Right-To-Left by a bidi platform.
1379 // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.)
1380 if (IS_HEBREW_CHAR(mBuffer[offset]) ) {
1381 charType = eCharType_RightToLeft;
1383 else if (IS_ARABIC_ALPHABETIC(mBuffer[offset]) ) {
1384 charType = eCharType_RightToLeftArabic;
1386 else {
1387 mBidiEngine->GetCharTypeAt(offset, &charType);
1390 if (!CHARTYPE_IS_WEAK(charType) ) {
1392 if (strongTypeFound
1393 && (charType != aPrevCharType)
1394 && (CHARTYPE_IS_RTL(charType) || CHARTYPE_IS_RTL(aPrevCharType) ) ) {
1395 // Stop at this point to ensure uni-directionality of the text
1396 // (from platform's point of view).
1397 // Also, don't mix Arabic and Hebrew content (since platform may
1398 // provide BIDI support to one of them only).
1399 aRunLength = offset - aOffset;
1400 aRunLimit = offset;
1401 ++aRunCount;
1402 break;
1405 if ( (eCharType_RightToLeftArabic == aPrevCharType
1406 || eCharType_ArabicNumber == aPrevCharType)
1407 && eCharType_EuropeanNumber == charType) {
1408 charType = eCharType_ArabicNumber;
1411 // Set PrevCharType to the last strong type in this frame
1412 // (for correct numeric shaping)
1413 aPrevCharType = charType;
1415 strongTypeFound = PR_TRUE;
1416 aCharType = charType;
1419 aOffset = offset;
1422 nsresult nsBidiPresUtils::ProcessText(const PRUnichar* aText,
1423 PRInt32 aLength,
1424 nsBidiDirection aBaseDirection,
1425 nsPresContext* aPresContext,
1426 BidiProcessor& aprocessor,
1427 Mode aMode,
1428 nsBidiPositionResolve* aPosResolve,
1429 PRInt32 aPosResolveCount,
1430 nscoord* aWidth)
1432 NS_ASSERTION((aPosResolve == nsnull) != (aPosResolveCount > 0), "Incorrect aPosResolve / aPosResolveCount arguments");
1434 PRInt32 runCount;
1436 mBuffer.Assign(aText, aLength);
1438 nsresult rv = mBidiEngine->SetPara(mBuffer.get(), aLength, aBaseDirection, nsnull);
1439 if (NS_FAILED(rv))
1440 return rv;
1442 rv = mBidiEngine->CountRuns(&runCount);
1443 if (NS_FAILED(rv))
1444 return rv;
1446 nscoord xOffset = 0;
1447 nscoord width, xEndRun;
1448 nscoord totalWidth = 0;
1449 PRInt32 i, start, limit, length;
1450 PRUint32 visualStart = 0;
1451 PRUint8 charType;
1452 PRUint8 prevType = eCharType_LeftToRight;
1453 nsBidiLevel level;
1455 for(int nPosResolve=0; nPosResolve < aPosResolveCount; ++nPosResolve)
1457 aPosResolve[nPosResolve].visualIndex = kNotFound;
1458 aPosResolve[nPosResolve].visualLeftTwips = kNotFound;
1459 aPosResolve[nPosResolve].visualWidth = kNotFound;
1462 for (i = 0; i < runCount; i++) {
1463 rv = mBidiEngine->GetVisualRun(i, &start, &length, &aBaseDirection);
1464 if (NS_FAILED(rv))
1465 return rv;
1467 rv = mBidiEngine->GetLogicalRun(start, &limit, &level);
1468 if (NS_FAILED(rv))
1469 return rv;
1471 PRInt32 subRunLength = limit - start;
1472 PRInt32 lineOffset = start;
1473 PRInt32 typeLimit = NS_MIN(limit, aLength);
1474 PRInt32 subRunCount = 1;
1475 PRInt32 subRunLimit = typeLimit;
1478 * If |level| is even, i.e. the direction of the run is left-to-right, we
1479 * render the subruns from left to right and increment the x-coordinate
1480 * |xOffset| by the width of each subrun after rendering.
1482 * If |level| is odd, i.e. the direction of the run is right-to-left, we
1483 * render the subruns from right to left. We begin by incrementing |xOffset| by
1484 * the width of the whole run, and then decrement it by the width of each
1485 * subrun before rendering. After rendering all the subruns, we restore the
1486 * x-coordinate of the end of the run for the start of the next run.
1489 if (level & 1) {
1490 aprocessor.SetText(aText + start, subRunLength, nsBidiDirection(level & 1));
1491 width = aprocessor.GetWidth();
1492 xOffset += width;
1493 xEndRun = xOffset;
1496 while (subRunCount > 0) {
1497 // CalculateCharType can increment subRunCount if the run
1498 // contains mixed character types
1499 CalculateCharType(lineOffset, typeLimit, subRunLimit, subRunLength, subRunCount, charType, prevType);
1501 nsAutoString runVisualText;
1502 runVisualText.Assign(aText + start, subRunLength);
1503 if (PRInt32(runVisualText.Length()) < subRunLength)
1504 return NS_ERROR_OUT_OF_MEMORY;
1505 FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), subRunLength,
1506 (nsCharType)charType, level & 1);
1508 aprocessor.SetText(runVisualText.get(), subRunLength, nsBidiDirection(level & 1));
1509 width = aprocessor.GetWidth();
1510 totalWidth += width;
1511 if (level & 1) {
1512 xOffset -= width;
1514 if (aMode == MODE_DRAW) {
1515 aprocessor.DrawText(xOffset, width);
1519 * The caller may request to calculate the visual position of one
1520 * or more characters.
1522 for(int nPosResolve=0; nPosResolve<aPosResolveCount; ++nPosResolve)
1524 nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve];
1526 * Did we already resolve this position's visual metric? If so, skip.
1528 if (posResolve->visualLeftTwips != kNotFound)
1529 continue;
1532 * First find out if the logical position is within this run.
1534 if (start <= posResolve->logicalIndex &&
1535 start + subRunLength > posResolve->logicalIndex) {
1537 * If this run is only one character long, we have an easy case:
1538 * the visual position is the x-coord of the start of the run
1539 * less the x-coord of the start of the whole text.
1541 if (subRunLength == 1) {
1542 posResolve->visualIndex = visualStart;
1543 posResolve->visualLeftTwips = xOffset;
1544 posResolve->visualWidth = width;
1547 * Otherwise, we need to measure the width of the run's part
1548 * which is to the visual left of the index.
1549 * In other words, the run is broken in two, around the logical index,
1550 * and we measure the part which is visually left.
1551 * If the run is right-to-left, this part will span from after the index
1552 * up to the end of the run; if it is left-to-right, this part will span
1553 * from the start of the run up to (and inclduing) the character before the index.
1555 else {
1557 * Here is a description of how the width of the current character
1558 * (posResolve->visualWidth) is calculated:
1560 * LTR (current char: "P"):
1561 * S A M P L E (logical index: 3, visual index: 3)
1562 * ^ (visualLeftPart)
1563 * ^ (visualRightSide)
1564 * visualLeftLength == 3
1565 * ^^^^^^ (subWidth)
1566 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
1567 * ^^ (posResolve->visualWidth)
1569 * RTL (current char: "M"):
1570 * E L P M A S (logical index: 2, visual index: 3)
1571 * ^ (visualLeftPart)
1572 * ^ (visualRightSide)
1573 * visualLeftLength == 3
1574 * ^^^^^^ (subWidth)
1575 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
1576 * ^^ (posResolve->visualWidth)
1578 nscoord subWidth;
1579 // The position in the text where this run's "left part" begins.
1580 const PRUnichar* visualLeftPart, *visualRightSide;
1581 if (level & 1) {
1582 // One day, son, this could all be replaced with mBidiEngine.GetVisualIndex ...
1583 posResolve->visualIndex = visualStart + (subRunLength - (posResolve->logicalIndex + 1 - start));
1584 // Skipping to the "left part".
1585 visualLeftPart = aText + posResolve->logicalIndex + 1;
1586 // Skipping to the right side of the current character
1587 visualRightSide = visualLeftPart - 1;
1589 else {
1590 posResolve->visualIndex = visualStart + (posResolve->logicalIndex - start);
1591 // Skipping to the "left part".
1592 visualLeftPart = aText + start;
1593 // In LTR mode this is the same as visualLeftPart
1594 visualRightSide = visualLeftPart;
1596 // The delta between the start of the run and the left part's end.
1597 PRInt32 visualLeftLength = posResolve->visualIndex - visualStart;
1598 aprocessor.SetText(visualLeftPart, visualLeftLength, nsBidiDirection(level & 1));
1599 subWidth = aprocessor.GetWidth();
1600 aprocessor.SetText(visualRightSide, visualLeftLength + 1, nsBidiDirection(level & 1));
1601 posResolve->visualLeftTwips = xOffset + subWidth;
1602 posResolve->visualWidth = aprocessor.GetWidth() - subWidth;
1607 if (!(level & 1)) {
1608 xOffset += width;
1611 --subRunCount;
1612 start = lineOffset;
1613 subRunLimit = typeLimit;
1614 subRunLength = typeLimit - lineOffset;
1615 } // while
1616 if (level & 1) {
1617 xOffset = xEndRun;
1620 visualStart += length;
1621 } // for
1623 if (aWidth) {
1624 *aWidth = totalWidth;
1626 return NS_OK;
1629 class NS_STACK_CLASS nsIRenderingContextBidiProcessor : public nsBidiPresUtils::BidiProcessor {
1630 public:
1631 nsIRenderingContextBidiProcessor(nsIRenderingContext* aCtx,
1632 nsIRenderingContext* aTextRunConstructionContext,
1633 const nsPoint& aPt)
1634 : mCtx(aCtx), mTextRunConstructionContext(aTextRunConstructionContext), mPt(aPt) { }
1636 ~nsIRenderingContextBidiProcessor()
1638 mCtx->SetRightToLeftText(PR_FALSE);
1641 virtual void SetText(const PRUnichar* aText,
1642 PRInt32 aLength,
1643 nsBidiDirection aDirection)
1645 mTextRunConstructionContext->SetTextRunRTL(aDirection==NSBIDI_RTL);
1646 mText = aText;
1647 mLength = aLength;
1650 virtual nscoord GetWidth()
1652 nscoord width;
1653 mTextRunConstructionContext->GetWidth(mText, mLength, width, nsnull);
1654 return width;
1657 virtual void DrawText(nscoord aXOffset,
1658 nscoord)
1660 nsCOMPtr<nsIFontMetrics> metrics;
1661 mCtx->GetFontMetrics(*getter_AddRefs(metrics));
1662 nsIThebesFontMetrics* fm = static_cast<nsIThebesFontMetrics*>(metrics.get());
1663 fm->DrawString(mText, mLength, mPt.x + aXOffset, mPt.y,
1664 mCtx, mTextRunConstructionContext);
1667 private:
1668 nsIRenderingContext* mCtx;
1669 nsIRenderingContext* mTextRunConstructionContext;
1670 nsPoint mPt;
1671 const PRUnichar* mText;
1672 PRInt32 mLength;
1673 nsBidiDirection mDirection;
1676 nsresult nsBidiPresUtils::ProcessTextForRenderingContext(const PRUnichar* aText,
1677 PRInt32 aLength,
1678 nsBidiDirection aBaseDirection,
1679 nsPresContext* aPresContext,
1680 nsIRenderingContext& aRenderingContext,
1681 nsIRenderingContext& aTextRunConstructionContext,
1682 Mode aMode,
1683 nscoord aX,
1684 nscoord aY,
1685 nsBidiPositionResolve* aPosResolve,
1686 PRInt32 aPosResolveCount,
1687 nscoord* aWidth)
1689 nsIRenderingContextBidiProcessor processor(&aRenderingContext, &aTextRunConstructionContext, nsPoint(aX, aY));
1691 return ProcessText(aText, aLength, aBaseDirection, aPresContext, processor,
1692 aMode, aPosResolve, aPosResolveCount, aWidth);
1695 /* static */
1696 void nsBidiPresUtils::WriteReverse(const PRUnichar* aSrc,
1697 PRUint32 aSrcLength,
1698 PRUnichar* aDest)
1700 const PRUnichar* src = aSrc + aSrcLength;
1701 PRUnichar* dest = aDest;
1702 PRUint32 UTF32Char;
1704 while (--src >= aSrc) {
1705 if (NS_IS_LOW_SURROGATE(*src)) {
1706 if (src > aSrc && NS_IS_HIGH_SURROGATE(*(src - 1))) {
1707 UTF32Char = SURROGATE_TO_UCS4(*(src - 1), *src);
1708 --src;
1709 } else {
1710 UTF32Char = UCS2_REPLACEMENT_CHAR;
1712 } else if (NS_IS_HIGH_SURROGATE(*src)) {
1713 // paired high surrogates are handled above, so this is a lone high surrogate
1714 UTF32Char = UCS2_REPLACEMENT_CHAR;
1715 } else {
1716 UTF32Char = *src;
1719 UTF32Char = gfxUnicodeProperties::GetMirroredChar(UTF32Char);
1721 if (IS_IN_BMP(UTF32Char)) {
1722 *(dest++) = UTF32Char;
1723 } else {
1724 *(dest++) = H_SURROGATE(UTF32Char);
1725 *(dest++) = L_SURROGATE(UTF32Char);
1729 NS_ASSERTION(dest - aDest == aSrcLength, "Whole string not copied");
1732 /* static */
1733 PRBool nsBidiPresUtils::WriteLogicalToVisual(const PRUnichar* aSrc,
1734 PRUint32 aSrcLength,
1735 PRUnichar* aDest,
1736 nsBidiLevel aBaseDirection,
1737 nsBidi* aBidiEngine)
1739 const PRUnichar* src = aSrc;
1740 nsresult rv = aBidiEngine->SetPara(src, aSrcLength, aBaseDirection, nsnull);
1741 if (NS_FAILED(rv)) {
1742 return PR_FALSE;
1745 nsBidiDirection dir;
1746 rv = aBidiEngine->GetDirection(&dir);
1747 // NSBIDI_LTR returned from GetDirection means the whole text is LTR
1748 if (NS_FAILED(rv) || dir == NSBIDI_LTR) {
1749 return PR_FALSE;
1752 PRInt32 runCount;
1753 rv = aBidiEngine->CountRuns(&runCount);
1754 if (NS_FAILED(rv)) {
1755 return PR_FALSE;
1758 PRInt32 runIndex, start, length;
1759 PRUnichar* dest = aDest;
1761 for (runIndex = 0; runIndex < runCount; ++runIndex) {
1762 rv = aBidiEngine->GetVisualRun(runIndex, &start, &length, &dir);
1763 if (NS_FAILED(rv)) {
1764 return PR_FALSE;
1767 src = aSrc + start;
1769 if (dir == NSBIDI_RTL) {
1770 WriteReverse(src, length, dest);
1771 dest += length;
1772 } else {
1773 do {
1774 NS_ASSERTION(src >= aSrc && src < aSrc + aSrcLength,
1775 "logical index out of range");
1776 NS_ASSERTION(dest < aDest + aSrcLength, "visual index out of range");
1777 *(dest++) = *(src++);
1778 } while (--length);
1782 NS_ASSERTION(dest - aDest == aSrcLength, "whole string not copied");
1783 return PR_TRUE;
1786 void nsBidiPresUtils::CopyLogicalToVisual(const nsAString& aSource,
1787 nsAString& aDest,
1788 nsBidiLevel aBaseDirection,
1789 PRBool aOverride)
1791 aDest.SetLength(0);
1792 PRUint32 srcLength = aSource.Length();
1793 if (srcLength == 0)
1794 return;
1795 if (!EnsureStringLength(aDest, srcLength)) {
1796 return;
1798 nsAString::const_iterator fromBegin, fromEnd;
1799 nsAString::iterator toBegin;
1800 aSource.BeginReading(fromBegin);
1801 aSource.EndReading(fromEnd);
1802 aDest.BeginWriting(toBegin);
1804 if (aOverride) {
1805 if (aBaseDirection == NSBIDI_RTL) {
1806 // no need to use the converter -- just copy the string in reverse order
1807 WriteReverse(fromBegin.get(), srcLength, toBegin.get());
1808 } else {
1809 // if aOverride && aBaseDirection == NSBIDI_LTR, fall through to the
1810 // simple copy
1811 aDest.SetLength(0);
1813 } else {
1814 if (!WriteLogicalToVisual(fromBegin.get(), srcLength, toBegin.get(),
1815 aBaseDirection, mBidiEngine)) {
1816 aDest.SetLength(0);
1820 if (aDest.IsEmpty()) {
1821 // Either there was an error or the source is unidirectional
1822 // left-to-right. In either case, just copy source to dest.
1823 CopyUnicodeTo(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd),
1824 aDest);
1828 PRUint32 nsBidiPresUtils::EstimateMemoryUsed()
1830 PRUint32 size = 0;
1832 size += sizeof(nsBidiPresUtils);
1833 size += mBuffer.Length() * sizeof(PRUnichar);
1834 size += moz_malloc_usable_size(mBidiEngine->mDirPropsMemory);
1835 size += moz_malloc_usable_size(mBidiEngine->mLevelsMemory);
1836 size += moz_malloc_usable_size(mBidiEngine->mRunsMemory);
1838 return size;
1841 static PLDHashOperator
1842 TraverseKey(nsISupports *aKey, PRInt32 aData, void *aUserArg)
1844 nsCycleCollectionTraversalCallback *cb =
1845 static_cast<nsCycleCollectionTraversalCallback*>(aUserArg);
1846 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mContentToFrameIndex key");
1847 cb->NoteXPCOMChild(aKey);
1848 return PL_DHASH_NEXT;
1851 void nsBidiPresUtils::Traverse(nsCycleCollectionTraversalCallback &cb) const
1853 mContentToFrameIndex.EnumerateRead(TraverseKey, &cb);
1856 void nsBidiPresUtils::Unlink()
1858 mContentToFrameIndex.Clear();
1860 #endif // IBMBIDI