Implement unicode-bidi: -moz-plaintext in layout. Bug 662288, r=roc
authorSimon Montagu <smontagu@smontagu.org>
Tue, 18 Oct 2011 14:51:58 +0200
changeset 78886 388df914497ea4b3056df44e82e7a3f4e918b5bc
parent 78885 5d100f15d41fcdf96b650fb7e90c2ec39079ae18
child 78887 bfff1c1f8ed929b3ecd69d8a70e6adf34150f016
push id2741
push usersmontagu@mozilla.com
push dateTue, 18 Oct 2011 12:52:49 +0000
treeherdermozilla-inbound@388df914497e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs662288
milestone10.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Implement unicode-bidi: -moz-plaintext in layout. Bug 662288, r=roc
layout/base/nsBidi.cpp
layout/base/nsBidi.h
layout/base/nsBidiPresUtils.cpp
layout/generic/nsBlockFrame.cpp
layout/generic/nsFirstLetterFrame.cpp
layout/generic/nsLineLayout.cpp
layout/generic/nsLineLayout.h
layout/reftests/bidi/662288-1-ref.html
layout/reftests/bidi/662288-1.html
layout/reftests/bidi/reftest.list
layout/style/html.css
--- a/layout/base/nsBidi.cpp
+++ b/layout/base/nsBidi.cpp
@@ -1117,32 +1117,32 @@ void nsBidi::AdjustWSLevels()
   }
 }
 
 nsresult nsBidi::GetDirection(nsBidiDirection* aDirection)
 {
   *aDirection = mDirection;
   return NS_OK;
 }
+
+nsresult nsBidi::GetParaLevel(nsBidiLevel* aParaLevel)
+{
+  *aParaLevel = mParaLevel;
+  return NS_OK;
+}
 #ifdef FULL_BIDI_ENGINE
 
 /* -------------------------------------------------------------------------- */
 
 nsresult nsBidi::GetLength(PRInt32* aLength)
 {
   *aLength = mLength;
   return NS_OK;
 }
 
-nsresult nsBidi::GetParaLevel(nsBidiLevel* aParaLevel)
-{
-  *aParaLevel = mParaLevel;
-  return NS_OK;
-}
-
 /*
  * General remarks about the functions in this section:
  *
  * These functions deal with the aspects of potentially mixed-directional
  * text in a single paragraph or in a line of a single paragraph
  * which has already been processed according to
  * the Unicode 3.0 Bidi algorithm as defined in
  * http://www.unicode.org/unicode/reports/tr9/ , version 5,
--- a/layout/base/nsBidi.h
+++ b/layout/base/nsBidi.h
@@ -516,16 +516,25 @@ public:
    * @param aDirection receives a <code>NSBIDI_XXX</code> value that indicates if the entire text
    *       represented by this object is unidirectional,
    *       and which direction, or if it is mixed-directional.
    *
    * @see nsBidiDirection
    */
   nsresult GetDirection(nsBidiDirection* aDirection);
 
+  /**
+   * Get the paragraph level of the text.
+   *
+   * @param aParaLevel receives a <code>NSBIDI_XXX</code> value indicating the paragraph level
+   *
+   * @see nsBidiLevel
+   */
+  nsresult GetParaLevel(nsBidiLevel* aParaLevel);
+
 #ifdef FULL_BIDI_ENGINE
   /**
    * <code>SetLine</code> sets an <code>nsBidi</code> to
    * contain the reordering information, especially the resolved levels,
    * for all the characters in a line of text. This line of text is
    * specified by referring to an <code>nsBidi</code> object representing
    * this information for a paragraph of text, and by specifying
    * a range of indexes in this paragraph.<p>
@@ -560,25 +569,16 @@ public:
   /**
    * Get the length of the text.
    *
    * @param aLength receives the length of the text that the nsBidi object was created for.
    */
   nsresult GetLength(PRInt32* aLength);
 
   /**
-   * Get the paragraph level of the text.
-   *
-   * @param aParaLevel receives a <code>NSBIDI_XXX</code> value indicating the paragraph level
-   *
-   * @see nsBidiLevel
-   */
-  nsresult GetParaLevel(nsBidiLevel* aParaLevel);
-
-  /**
    * Get the level for one character.
    *
    * @param aCharIndex the index of a character.
    *
    * @param aLevel receives the level for the character at aCharIndex.
    *
    * @see nsBidiLevel
    */
--- a/layout/base/nsBidiPresUtils.cpp
+++ b/layout/base/nsBidiPresUtils.cpp
@@ -86,19 +86,28 @@ struct BidiParagraphData {
   nsIFrame*           mPrevFrame;
   nsAutoPtr<BidiParagraphData> mSubParagraph;
 
   void Init(nsBlockFrame *aBlockFrame)
   {
     mContentToFrameIndex.Init();
     mBidiEngine = new nsBidi();
     mPrevContent = nsnull;
-    mParaLevel =
-     (NS_STYLE_DIRECTION_RTL == aBlockFrame->GetStyleVisibility()->mDirection) ?
-        NSBIDI_RTL : NSBIDI_LTR;
+
+    bool styleDirectionIsRTL =
+      (NS_STYLE_DIRECTION_RTL == aBlockFrame->GetStyleVisibility()->mDirection);
+    if (aBlockFrame->GetStyleTextReset()->mUnicodeBidi &
+        NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
+      // unicode-bidi: plaintext: the Bidi algorithm will determine the
+      // directionality of the paragraph according to the first strong
+      // directional character.
+      mParaLevel = styleDirectionIsRTL ? NSBIDI_DEFAULT_RTL : NSBIDI_DEFAULT_LTR;
+    } else {
+      mParaLevel = styleDirectionIsRTL ? NSBIDI_RTL : NSBIDI_LTR;
+    }
 
     mIsVisual = aBlockFrame->PresContext()->IsVisualMode();
     if (mIsVisual) {
       /**
        * Drill up in content to detect whether this is an element that needs to
        * be rendered with logical order even on visual pages.
        *
        * We always use logical order on form controls, firstly so that text
@@ -135,16 +144,24 @@ struct BidiParagraphData {
   // Initialise a sub-paragraph from its containing paragraph
   void Init(BidiParagraphData *aBpd)
   {
     mContentToFrameIndex.Init();
     mBidiEngine = new nsBidi();
     mPrevContent = nsnull;
     mIsVisual = aBpd->mIsVisual;
     mParaLevel = aBpd->mParaLevel;
+
+    // If the containing paragraph has a level of NSBIDI_DEFAULT_LTR/RTL, set
+    // the sub-paragraph to the corresponding non-default level (We can't use
+    // GetParaLevel, because the containing paragraph hasn't yet been through
+    // bidi resolution
+    if (IS_DEFAULT_LEVEL(mParaLevel)) {
+      mParaLevel = (mParaLevel == NSBIDI_DEFAULT_RTL) ? NSBIDI_RTL : NSBIDI_LTR;
+    }                    
   }
 
   void Reset(nsIFrame* aFrame, BidiParagraphData *aBpd)
   {
     mLogicalFrames.Clear();
     mLinePerFrame.Clear();
     mContentToFrameIndex.Clear();
     mBuffer.SetLength(0);
@@ -184,26 +201,40 @@ struct BidiParagraphData {
   }
 
   nsresult SetPara()
   {
     return mBidiEngine->SetPara(mBuffer.get(), BufferLength(),
                                 mParaLevel, nsnull);
   }
 
+  /**
+   * mParaLevel can be NSBIDI_DEFAULT_LTR or NSBIDI_DEFAULT_RTL.
+   * GetParaLevel() returns the actual (resolved) paragraph level which is
+   * always either NSBIDI_LTR or NSBIDI_RTL
+   */
+  nsBidiLevel GetParaLevel()
+  {
+    nsBidiLevel paraLevel = mParaLevel;
+    if (IS_DEFAULT_LEVEL(paraLevel)) {
+      mBidiEngine->GetParaLevel(&paraLevel);
+    }
+    return paraLevel;
+  }
+
   nsresult CountRuns(PRInt32 *runCount){ return mBidiEngine->CountRuns(runCount); }
 
   nsresult GetLogicalRun(PRInt32 aLogicalStart, 
                          PRInt32* aLogicalLimit,
                          nsBidiLevel* aLevel)
   {
     nsresult rv = mBidiEngine->GetLogicalRun(aLogicalStart,
                                              aLogicalLimit, aLevel);
     if (mIsVisual || NS_FAILED(rv))
-      *aLevel = mParaLevel;
+      *aLevel = GetParaLevel();
     return rv;
   }
 
   void ResetData()
   {
     mLogicalFrames.Clear();
     mLinePerFrame.Clear();
     mContentToFrameIndex.Clear();
@@ -629,21 +660,22 @@ nsBidiPresUtils::ResolveParagraph(nsBloc
   nsPresContext *presContext = aBlockFrame->PresContext();
 
   if (aBpd->BufferLength() < 1) {
     return NS_OK;
   }
   aBpd->mBuffer.ReplaceChar("\t\r\n", kSpace);
 
   PRInt32 runCount;
-  PRUint8 embeddingLevel = aBpd->mParaLevel;
 
   nsresult rv = aBpd->SetPara();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  PRUint8 embeddingLevel = aBpd->GetParaLevel();
+
   rv = aBpd->CountRuns(&runCount);
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRInt32     runLength      = 0;   // the length of the current run of text
   PRInt32     lineOffset     = 0;   // the start of the current run
   PRInt32     logicalLimit   = 0;   // the end of the current run + 1
   PRInt32     numRun         = -1;
   PRInt32     fragmentLength = 0;   // the length of the current text frame
@@ -695,17 +727,17 @@ nsBidiPresUtils::ResolveParagraph(nsBloc
         contentTextLength = content->TextLength();
         if (contentTextLength == 0) {
           frame->AdjustOffsetsForBidi(0, 0);
           // Set the base level and embedding level of the current run even
           // on an empty frame. Otherwise frame reordering will not be correct.
           propTable->Set(frame, nsIFrame::EmbeddingLevelProperty(),
                          NS_INT32_TO_PTR(embeddingLevel));
           propTable->Set(frame, nsIFrame::BaseLevelProperty(),
-                         NS_INT32_TO_PTR(aBpd->mParaLevel));
+                         NS_INT32_TO_PTR(aBpd->GetParaLevel()));
           continue;
         }
         PRInt32 start, end;
         frame->GetOffsets(start, end);
         NS_ASSERTION(!(contentTextLength < end - start),
                      "Frame offsets don't fit in content");
         fragmentLength = NS_MIN(contentTextLength, end - start);
         contentOffset = start;
@@ -729,17 +761,17 @@ nsBidiPresUtils::ResolveParagraph(nsBloc
     if (frame == NS_BIDI_CONTROL_FRAME) {
       frame = nsnull;
       ++lineOffset;
     }
     else {
       propTable->Set(frame, nsIFrame::EmbeddingLevelProperty(),
                      NS_INT32_TO_PTR(embeddingLevel));
       propTable->Set(frame, nsIFrame::BaseLevelProperty(),
-                     NS_INT32_TO_PTR(aBpd->mParaLevel));
+                     NS_INT32_TO_PTR(aBpd->GetParaLevel()));
       if (isTextFrame) {
         if ( (runLength > 0) && (runLength < fragmentLength) ) {
           /*
            * The text in this frame continues beyond the end of this directional run.
            * Create a non-fluid continuation frame for the next directional run.
            */
           currentLine->MarkDirty();
           nsIFrame* nextBidi;
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -3539,20 +3539,34 @@ nsBlockFrame::DoReflowInlineFrames(nsBlo
     /* XXX get the height right! */
     availHeight = aFloatAvailableSpace.mRect.height;
   }
 
   // Make sure to enable resize optimization before we call BeginLineReflow
   // because it might get disabled there
   aLine->EnableResizeReflowOptimization();
 
+  // For unicode-bidi: plaintext, we need to get the direction of the line from
+  // the resolved paragraph level of the first frame on the line, not the block
+  // frame, because the block frame could be split by hard line breaks into
+  // multiple paragraphs with different base direction
+  PRUint8 direction;
+  if (GetStyleTextReset()->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
+    FramePropertyTable *propTable = aState.mPresContext->PropertyTable();
+    direction =  NS_PTR_TO_INT32(propTable->Get(aLine->mFirstChild,
+                                                BaseLevelProperty())) & 1;
+  } else {
+    direction = GetStyleVisibility()->mDirection;
+  }
+
   aLineLayout.BeginLineReflow(x, aState.mY,
                               availWidth, availHeight,
                               aFloatAvailableSpace.mHasFloats,
-                              false /*XXX isTopOfPage*/);
+                              false, /*XXX isTopOfPage*/
+                              direction);
 
   aState.SetFlag(BRS_LINE_LAYOUT_EMPTY, false);
 
   // XXX Unfortunately we need to know this before reflowing the first
   // inline frame in the line. FIX ME.
   if ((0 == aLineLayout.GetLineNumber()) &&
       (NS_BLOCK_HAS_FIRST_LETTER_CHILD & mState) &&
       (NS_BLOCK_HAS_FIRST_LETTER_STYLE & mState)) {
--- a/layout/generic/nsFirstLetterFrame.cpp
+++ b/layout/generic/nsFirstLetterFrame.cpp
@@ -45,16 +45,18 @@
 #include "nsLineLayout.h"
 #include "nsGkAtoms.h"
 #include "nsAutoPtr.h"
 #include "nsStyleSet.h"
 #include "nsFrameManager.h"
 #include "nsPlaceholderFrame.h"
 #include "nsCSSFrameConstructor.h"
 
+using namespace::mozilla;
+
 nsIFrame*
 NS_NewFirstLetterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsFirstLetterFrame(aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame)
 
@@ -210,18 +212,32 @@ nsFirstLetterFrame::Reflow(nsPresContext
 
   // Reflow the child
   if (!aReflowState.mLineLayout) {
     // When there is no lineLayout provided, we provide our own. The
     // only time that the first-letter-frame is not reflowing in a
     // line context is when its floating.
     nsHTMLReflowState rs(aPresContext, aReflowState, kid, availSize);
     nsLineLayout ll(aPresContext, nsnull, &aReflowState, nsnull);
+
+    // For unicode-bidi: plaintext, we need to get the direction of the line
+    // from the resolved paragraph level of the child, not the block frame,
+    // because the block frame could be split by hard line breaks into
+    // multiple paragraphs with different base direction
+    PRUint8 direction;
+    nsIFrame* containerFrame = ll.GetLineContainerFrame();
+    if (containerFrame->GetStyleTextReset()->mUnicodeBidi &
+        NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
+      FramePropertyTable *propTable = aPresContext->PropertyTable();
+      direction = NS_PTR_TO_INT32(propTable->Get(kid, BaseLevelProperty())) & 1;
+    } else {
+      direction = containerFrame->GetStyleVisibility()->mDirection;
+    }
     ll.BeginLineReflow(bp.left, bp.top, availSize.width, NS_UNCONSTRAINEDSIZE,
-                       false, true);
+                       false, true, direction);
     rs.mLineLayout = &ll;
     ll.SetInFirstLetter(true);
     ll.SetFirstLetterStyleOK(true);
 
     kid->WillReflow(aPresContext);
     kid->Reflow(aPresContext, aMetrics, rs, aReflowStatus);
 
     ll.EndLineReflow();
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -164,17 +164,18 @@ HasPrevInFlow(nsIFrame *aFrame)
   nsIFrame *prevInFlow = aFrame->GetPrevInFlow();
   return prevInFlow != nsnull;
 }
 
 void
 nsLineLayout::BeginLineReflow(nscoord aX, nscoord aY,
                               nscoord aWidth, nscoord aHeight,
                               bool aImpactedByFloats,
-                              bool aIsTopOfPage)
+                              bool aIsTopOfPage,
+                              PRUint8 aDirection)
 {
   NS_ASSERTION(nsnull == mRootSpan, "bad linelayout user");
   NS_WARN_IF_FALSE(aWidth != NS_UNCONSTRAINEDSIZE,
                    "have unconstrained width; this should only result from "
                    "very large sizes, not attempts at intrinsic width "
                    "calculation");
 #ifdef DEBUG
   if ((aWidth != NS_UNCONSTRAINEDSIZE) && CRAZY_WIDTH(aWidth)) {
@@ -219,17 +220,17 @@ nsLineLayout::BeginLineReflow(nscoord aX
   psd->mReflowState = mBlockReflowState;
   psd->mLeftEdge = aX;
   psd->mX = aX;
   psd->mRightEdge = aX + aWidth;
 
   mTopEdge = aY;
 
   psd->mNoWrap = !mStyleText->WhiteSpaceCanWrap();
-  psd->mDirection = mBlockReflowState->mStyleVisibility->mDirection;
+  psd->mDirection = aDirection;
   psd->mChangedFrameDirection = false;
 
   // If this is the first line of a block then see if the text-indent
   // property amounts to anything.
 
   if (0 == mLineNumber && !HasPrevInFlow(mBlockReflowState->frame)) {
     const nsStyleCoord &textIndent = mStyleText->mTextIndent;
     nscoord pctBasis = 0;
--- a/layout/generic/nsLineLayout.h
+++ b/layout/generic/nsLineLayout.h
@@ -80,17 +80,18 @@ public:
 
   PRInt32 GetLineNumber() const {
     return mLineNumber;
   }
 
   void BeginLineReflow(nscoord aX, nscoord aY,
                        nscoord aWidth, nscoord aHeight,
                        bool aImpactedByFloats,
-                       bool aIsTopOfPage);
+                       bool aIsTopOfPage,
+                       PRUint8 aDirection);
 
   void EndLineReflow();
 
   /**
    * Called when a float has been placed. This method updates the
    * inline frame and span data to account for any change in positions
    * due to available space for the line boxes changing.
    * @param aX/aY/aWidth/aHeight are the new available
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/662288-1-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+ <head>
+  <meta charset="UTF-8">
+  <title>pre element with auto-direction</title>
+ </head>
+ <body>
+  <pre><div dir="ltr">The word Automaton is the latinization of the Greek
+αὐτόματον,... automaton, (neuter) “acting of one’s own will”. It is more
+often used to describe non-electronic moving machines, especially those that
+have been made to resemble human or animal actions, such as the jacks on old
+public striking clocks, or the cuckoo and any other animated figures on a 
+cuckoo clock.</div><div dir="rtl">העדויות על בובות מכניות ראשונות הן מיוון העתיקה, שם הופעלו פסלים נעים בכוח הקיטור 
+שנקראו אוטומטון (ביוונית: αὐτόματος - נע מעצמו). האוטומטונים שימשו הן כצעצועים והן 
+לצורכי בימת התיאטרון היווני, כמייצגים אלים, במיוחד בכניסה מפתיעה בסוף המחזה. הביטוי 
+דאוס אקס מכינה ("האל מתוך המכונה"), שמשמעותו מצב בסיפור שבו הכותב מכניס לעלילה 
+גורם שאינו נובע מהשרשרת הסיבתית העלילתית כדי לפתור בעיות עלילתיות, מגיע מהופעת ה"אל"
+מתוך הפסל המכני שהוריד אותו אל הבמה. למנגנוני תנועה מכניים אוטומטיים מאותה תקופה נמצא 
+שריד ארכאולוגי בדמותו של מנגנון אנטיקיתרה, ששימש למכונת חישוב קדומה.</div>
+</pre>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/662288-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+ <head>
+  <meta charset="UTF-8">
+  <title>pre element with auto-direction</title>
+ </head>
+ <body>
+  <pre dir="auto">The word Automaton is the latinization of the Greek
+αὐτόματον,... automaton, (neuter) “acting of one’s own will”. It is more
+often used to describe non-electronic moving machines, especially those that
+have been made to resemble human or animal actions, such as the jacks on old
+public striking clocks, or the cuckoo and any other animated figures on a 
+cuckoo clock.
+העדויות על בובות מכניות ראשונות הן מיוון העתיקה, שם הופעלו פסלים נעים בכוח הקיטור 
+שנקראו אוטומטון (ביוונית: αὐτόματος - נע מעצמו). האוטומטונים שימשו הן כצעצועים והן 
+לצורכי בימת התיאטרון היווני, כמייצגים אלים, במיוחד בכניסה מפתיעה בסוף המחזה. הביטוי 
+דאוס אקס מכינה ("האל מתוך המכונה"), שמשמעותו מצב בסיפור שבו הכותב מכניס לעלילה 
+גורם שאינו נובע מהשרשרת הסיבתית העלילתית כדי לפתור בעיות עלילתיות, מגיע מהופעת ה"אל"
+מתוך הפסל המכני שהוריד אותו אל הבמה. למנגנוני תנועה מכניים אוטומטיים מאותה תקופה נמצא 
+שריד ארכאולוגי בדמותו של מנגנון אנטיקיתרה, ששימש למכונת חישוב קדומה.
+</pre>
+ </body>
+</html>
--- a/layout/reftests/bidi/reftest.list
+++ b/layout/reftests/bidi/reftest.list
@@ -82,9 +82,10 @@ random-if(winWidget) == 305643-1.html 30
 == 588739-3.html 588739-ref.html
 == 612843-1.html 612843-1-ref.html
 == 613149-1a.html 613149-1-ref.html
 == 613149-1b.html 613149-1-ref.html
 == 613149-2a.html 613149-2-ref.html
 == 613149-2b.html 613149-2-ref.html
 == 613157-1.html 613157-1-ref.html
 == 613157-2.html 613157-2-ref.html
+== 662288-1.html 662288-1-ref.html
 == 670226-1.html 670226-1-ref.html
--- a/layout/style/html.css
+++ b/layout/style/html.css
@@ -43,22 +43,26 @@
 [dir="rtl"] {
   direction: rtl;
   unicode-bidi: embed;
 }
 [dir="ltr"] {
   direction: ltr;
   unicode-bidi: embed;
 }
-bdi, bdi[dir], output, output[dir] { 
+bdi, bdi[dir], output, output[dir], [dir="auto"] { 
   unicode-bidi: -moz-isolate;
 }
 bdo, bdo[dir] {
   unicode-bidi: bidi-override;
 }
+bdo[dir="auto"] {
+  unicode-bidi: bidi-override -moz-isolate;
+}
+textarea[dir="auto"], pre[dir="auto"] { unicode-bidi: -moz-plaintext; }
 
 /* To ensure http://www.w3.org/TR/REC-html40/struct/dirlang.html#style-bidi:
  *
  * "When a block element that does not have a dir attribute is transformed to
  * the style of an inline element by a style sheet, the resulting presentation
  * should be equivalent, in terms of bidirectional formatting, to the
  * formatting obtained by explicitly adding a dir attribute (assigned the
  * inherited value) to the transformed element." */