Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include "nsBidiPresUtils.h" |
michael@0 | 7 | #include "nsGkAtoms.h" |
michael@0 | 8 | #include "nsPresContext.h" |
michael@0 | 9 | #include "nsRenderingContext.h" |
michael@0 | 10 | #include "nsBidiUtils.h" |
michael@0 | 11 | #include "nsCSSFrameConstructor.h" |
michael@0 | 12 | #include "nsContainerFrame.h" |
michael@0 | 13 | #include "nsInlineFrame.h" |
michael@0 | 14 | #include "nsPlaceholderFrame.h" |
michael@0 | 15 | #include "nsFirstLetterFrame.h" |
michael@0 | 16 | #include "nsUnicodeProperties.h" |
michael@0 | 17 | #include "nsTextFrame.h" |
michael@0 | 18 | #include "nsBlockFrame.h" |
michael@0 | 19 | #include "nsIFrameInlines.h" |
michael@0 | 20 | #include <algorithm> |
michael@0 | 21 | |
michael@0 | 22 | #undef NOISY_BIDI |
michael@0 | 23 | #undef REALLY_NOISY_BIDI |
michael@0 | 24 | |
michael@0 | 25 | using namespace mozilla; |
michael@0 | 26 | |
michael@0 | 27 | static const char16_t kSpace = 0x0020; |
michael@0 | 28 | static const char16_t kZWSP = 0x200B; |
michael@0 | 29 | static const char16_t kLineSeparator = 0x2028; |
michael@0 | 30 | static const char16_t kObjectSubstitute = 0xFFFC; |
michael@0 | 31 | static const char16_t kLRE = 0x202A; |
michael@0 | 32 | static const char16_t kRLE = 0x202B; |
michael@0 | 33 | static const char16_t kLRO = 0x202D; |
michael@0 | 34 | static const char16_t kRLO = 0x202E; |
michael@0 | 35 | static const char16_t kPDF = 0x202C; |
michael@0 | 36 | static const char16_t kSeparators[] = { |
michael@0 | 37 | // All characters with Bidi type Segment Separator or Block Separator |
michael@0 | 38 | char16_t('\t'), |
michael@0 | 39 | char16_t('\r'), |
michael@0 | 40 | char16_t('\n'), |
michael@0 | 41 | char16_t(0xb), |
michael@0 | 42 | char16_t(0x1c), |
michael@0 | 43 | char16_t(0x1d), |
michael@0 | 44 | char16_t(0x1e), |
michael@0 | 45 | char16_t(0x1f), |
michael@0 | 46 | char16_t(0x85), |
michael@0 | 47 | char16_t(0x2029), |
michael@0 | 48 | char16_t(0) |
michael@0 | 49 | }; |
michael@0 | 50 | |
michael@0 | 51 | #define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1) |
michael@0 | 52 | |
michael@0 | 53 | struct BidiParagraphData { |
michael@0 | 54 | nsString mBuffer; |
michael@0 | 55 | nsAutoTArray<char16_t, 16> mEmbeddingStack; |
michael@0 | 56 | nsTArray<nsIFrame*> mLogicalFrames; |
michael@0 | 57 | nsTArray<nsLineBox*> mLinePerFrame; |
michael@0 | 58 | nsDataHashtable<nsISupportsHashKey, int32_t> mContentToFrameIndex; |
michael@0 | 59 | bool mIsVisual; |
michael@0 | 60 | bool mReset; |
michael@0 | 61 | nsBidiLevel mParaLevel; |
michael@0 | 62 | nsIContent* mPrevContent; |
michael@0 | 63 | nsAutoPtr<nsBidi> mBidiEngine; |
michael@0 | 64 | nsIFrame* mPrevFrame; |
michael@0 | 65 | nsAutoPtr<BidiParagraphData> mSubParagraph; |
michael@0 | 66 | uint8_t mParagraphDepth; |
michael@0 | 67 | |
michael@0 | 68 | void Init(nsBlockFrame *aBlockFrame) |
michael@0 | 69 | { |
michael@0 | 70 | mBidiEngine = new nsBidi(); |
michael@0 | 71 | mPrevContent = nullptr; |
michael@0 | 72 | mParagraphDepth = 0; |
michael@0 | 73 | |
michael@0 | 74 | mParaLevel = nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame->StyleContext()); |
michael@0 | 75 | |
michael@0 | 76 | mIsVisual = aBlockFrame->PresContext()->IsVisualMode(); |
michael@0 | 77 | if (mIsVisual) { |
michael@0 | 78 | /** |
michael@0 | 79 | * Drill up in content to detect whether this is an element that needs to |
michael@0 | 80 | * be rendered with logical order even on visual pages. |
michael@0 | 81 | * |
michael@0 | 82 | * We always use logical order on form controls, firstly so that text |
michael@0 | 83 | * entry will be in logical order, but also because visual pages were |
michael@0 | 84 | * written with the assumption that even if the browser had no support |
michael@0 | 85 | * for right-to-left text rendering, it would use native widgets with |
michael@0 | 86 | * bidi support to display form controls. |
michael@0 | 87 | * |
michael@0 | 88 | * We also use logical order in XUL elements, since we expect that if a |
michael@0 | 89 | * XUL element appears in a visual page, it will be generated by an XBL |
michael@0 | 90 | * binding and contain localized text which will be in logical order. |
michael@0 | 91 | */ |
michael@0 | 92 | for (nsIContent* content = aBlockFrame->GetContent() ; content; |
michael@0 | 93 | content = content->GetParent()) { |
michael@0 | 94 | if (content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) || |
michael@0 | 95 | content->IsXUL()) { |
michael@0 | 96 | mIsVisual = false; |
michael@0 | 97 | break; |
michael@0 | 98 | } |
michael@0 | 99 | } |
michael@0 | 100 | } |
michael@0 | 101 | } |
michael@0 | 102 | |
michael@0 | 103 | BidiParagraphData* GetSubParagraph() |
michael@0 | 104 | { |
michael@0 | 105 | if (!mSubParagraph) { |
michael@0 | 106 | mSubParagraph = new BidiParagraphData(); |
michael@0 | 107 | mSubParagraph->Init(this); |
michael@0 | 108 | } |
michael@0 | 109 | |
michael@0 | 110 | return mSubParagraph; |
michael@0 | 111 | } |
michael@0 | 112 | |
michael@0 | 113 | // Initialise a sub-paragraph from its containing paragraph |
michael@0 | 114 | void Init(BidiParagraphData *aBpd) |
michael@0 | 115 | { |
michael@0 | 116 | mBidiEngine = new nsBidi(); |
michael@0 | 117 | mPrevContent = nullptr; |
michael@0 | 118 | mIsVisual = aBpd->mIsVisual; |
michael@0 | 119 | mReset = false; |
michael@0 | 120 | } |
michael@0 | 121 | |
michael@0 | 122 | void Reset(nsIFrame* aBDIFrame, BidiParagraphData *aBpd) |
michael@0 | 123 | { |
michael@0 | 124 | mReset = true; |
michael@0 | 125 | mLogicalFrames.Clear(); |
michael@0 | 126 | mLinePerFrame.Clear(); |
michael@0 | 127 | mContentToFrameIndex.Clear(); |
michael@0 | 128 | mBuffer.SetLength(0); |
michael@0 | 129 | mPrevFrame = aBpd->mPrevFrame; |
michael@0 | 130 | mParagraphDepth = aBpd->mParagraphDepth + 1; |
michael@0 | 131 | |
michael@0 | 132 | const nsStyleTextReset* text = aBDIFrame->StyleTextReset(); |
michael@0 | 133 | bool isRTL = (NS_STYLE_DIRECTION_RTL == |
michael@0 | 134 | aBDIFrame->StyleVisibility()->mDirection); |
michael@0 | 135 | |
michael@0 | 136 | if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) { |
michael@0 | 137 | mParaLevel = NSBIDI_DEFAULT_LTR; |
michael@0 | 138 | } else { |
michael@0 | 139 | mParaLevel = mParagraphDepth * 2; |
michael@0 | 140 | if (isRTL) ++mParaLevel; |
michael@0 | 141 | } |
michael@0 | 142 | |
michael@0 | 143 | if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_OVERRIDE) { |
michael@0 | 144 | PushBidiControl(isRTL ? kRLO : kLRO); |
michael@0 | 145 | } |
michael@0 | 146 | } |
michael@0 | 147 | |
michael@0 | 148 | void EmptyBuffer() |
michael@0 | 149 | { |
michael@0 | 150 | mBuffer.SetLength(0); |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | nsresult SetPara() |
michael@0 | 154 | { |
michael@0 | 155 | return mBidiEngine->SetPara(mBuffer.get(), BufferLength(), |
michael@0 | 156 | mParaLevel, nullptr); |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | /** |
michael@0 | 160 | * mParaLevel can be NSBIDI_DEFAULT_LTR as well as NSBIDI_LTR or NSBIDI_RTL. |
michael@0 | 161 | * GetParaLevel() returns the actual (resolved) paragraph level which is |
michael@0 | 162 | * always either NSBIDI_LTR or NSBIDI_RTL |
michael@0 | 163 | */ |
michael@0 | 164 | nsBidiLevel GetParaLevel() |
michael@0 | 165 | { |
michael@0 | 166 | nsBidiLevel paraLevel = mParaLevel; |
michael@0 | 167 | if (IS_DEFAULT_LEVEL(paraLevel)) { |
michael@0 | 168 | mBidiEngine->GetParaLevel(¶Level); |
michael@0 | 169 | } |
michael@0 | 170 | return paraLevel; |
michael@0 | 171 | } |
michael@0 | 172 | |
michael@0 | 173 | nsBidiDirection GetDirection() |
michael@0 | 174 | { |
michael@0 | 175 | nsBidiDirection dir; |
michael@0 | 176 | mBidiEngine->GetDirection(&dir); |
michael@0 | 177 | return dir; |
michael@0 | 178 | } |
michael@0 | 179 | |
michael@0 | 180 | nsresult CountRuns(int32_t *runCount){ return mBidiEngine->CountRuns(runCount); } |
michael@0 | 181 | |
michael@0 | 182 | nsresult GetLogicalRun(int32_t aLogicalStart, |
michael@0 | 183 | int32_t* aLogicalLimit, |
michael@0 | 184 | nsBidiLevel* aLevel) |
michael@0 | 185 | { |
michael@0 | 186 | nsresult rv = mBidiEngine->GetLogicalRun(aLogicalStart, |
michael@0 | 187 | aLogicalLimit, aLevel); |
michael@0 | 188 | if (mIsVisual || NS_FAILED(rv)) |
michael@0 | 189 | *aLevel = GetParaLevel(); |
michael@0 | 190 | return rv; |
michael@0 | 191 | } |
michael@0 | 192 | |
michael@0 | 193 | void ResetData() |
michael@0 | 194 | { |
michael@0 | 195 | mLogicalFrames.Clear(); |
michael@0 | 196 | mLinePerFrame.Clear(); |
michael@0 | 197 | mContentToFrameIndex.Clear(); |
michael@0 | 198 | mBuffer.SetLength(0); |
michael@0 | 199 | mPrevContent = nullptr; |
michael@0 | 200 | for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) { |
michael@0 | 201 | mBuffer.Append(mEmbeddingStack[i]); |
michael@0 | 202 | mLogicalFrames.AppendElement(NS_BIDI_CONTROL_FRAME); |
michael@0 | 203 | mLinePerFrame.AppendElement((nsLineBox*)nullptr); |
michael@0 | 204 | } |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | void ResetForNewBlock() |
michael@0 | 208 | { |
michael@0 | 209 | for (BidiParagraphData* bpd = this; bpd; bpd = bpd->mSubParagraph) { |
michael@0 | 210 | bpd->mPrevFrame = nullptr; |
michael@0 | 211 | } |
michael@0 | 212 | } |
michael@0 | 213 | |
michael@0 | 214 | void AppendFrame(nsIFrame* aFrame, |
michael@0 | 215 | nsBlockInFlowLineIterator* aLineIter, |
michael@0 | 216 | nsIContent* aContent = nullptr) |
michael@0 | 217 | { |
michael@0 | 218 | if (aContent) { |
michael@0 | 219 | mContentToFrameIndex.Put(aContent, FrameCount()); |
michael@0 | 220 | } |
michael@0 | 221 | mLogicalFrames.AppendElement(aFrame); |
michael@0 | 222 | |
michael@0 | 223 | AdvanceLineIteratorToFrame(aFrame, aLineIter, mPrevFrame); |
michael@0 | 224 | mLinePerFrame.AppendElement(aLineIter->GetLine().get()); |
michael@0 | 225 | } |
michael@0 | 226 | |
michael@0 | 227 | void AdvanceAndAppendFrame(nsIFrame** aFrame, |
michael@0 | 228 | nsBlockInFlowLineIterator* aLineIter, |
michael@0 | 229 | nsIFrame** aNextSibling) |
michael@0 | 230 | { |
michael@0 | 231 | nsIFrame* frame = *aFrame; |
michael@0 | 232 | nsIFrame* nextSibling = *aNextSibling; |
michael@0 | 233 | |
michael@0 | 234 | frame = frame->GetNextContinuation(); |
michael@0 | 235 | if (frame) { |
michael@0 | 236 | AppendFrame(frame, aLineIter, nullptr); |
michael@0 | 237 | |
michael@0 | 238 | /* |
michael@0 | 239 | * If we have already overshot the saved next-sibling while |
michael@0 | 240 | * scanning the frame's continuations, advance it. |
michael@0 | 241 | */ |
michael@0 | 242 | if (frame == nextSibling) { |
michael@0 | 243 | nextSibling = frame->GetNextSibling(); |
michael@0 | 244 | } |
michael@0 | 245 | } |
michael@0 | 246 | |
michael@0 | 247 | *aFrame = frame; |
michael@0 | 248 | *aNextSibling = nextSibling; |
michael@0 | 249 | } |
michael@0 | 250 | |
michael@0 | 251 | int32_t GetLastFrameForContent(nsIContent *aContent) |
michael@0 | 252 | { |
michael@0 | 253 | int32_t index = 0; |
michael@0 | 254 | mContentToFrameIndex.Get(aContent, &index); |
michael@0 | 255 | return index; |
michael@0 | 256 | } |
michael@0 | 257 | |
michael@0 | 258 | int32_t FrameCount(){ return mLogicalFrames.Length(); } |
michael@0 | 259 | |
michael@0 | 260 | int32_t BufferLength(){ return mBuffer.Length(); } |
michael@0 | 261 | |
michael@0 | 262 | nsIFrame* FrameAt(int32_t aIndex){ return mLogicalFrames[aIndex]; } |
michael@0 | 263 | |
michael@0 | 264 | nsLineBox* GetLineForFrameAt(int32_t aIndex){ return mLinePerFrame[aIndex]; } |
michael@0 | 265 | |
michael@0 | 266 | void AppendUnichar(char16_t aCh){ mBuffer.Append(aCh); } |
michael@0 | 267 | |
michael@0 | 268 | void AppendString(const nsDependentSubstring& aString){ mBuffer.Append(aString); } |
michael@0 | 269 | |
michael@0 | 270 | void AppendControlChar(char16_t aCh) |
michael@0 | 271 | { |
michael@0 | 272 | mLogicalFrames.AppendElement(NS_BIDI_CONTROL_FRAME); |
michael@0 | 273 | mLinePerFrame.AppendElement((nsLineBox*)nullptr); |
michael@0 | 274 | AppendUnichar(aCh); |
michael@0 | 275 | } |
michael@0 | 276 | |
michael@0 | 277 | void PushBidiControl(char16_t aCh) |
michael@0 | 278 | { |
michael@0 | 279 | AppendControlChar(aCh); |
michael@0 | 280 | mEmbeddingStack.AppendElement(aCh); |
michael@0 | 281 | } |
michael@0 | 282 | |
michael@0 | 283 | void PopBidiControl() |
michael@0 | 284 | { |
michael@0 | 285 | AppendControlChar(kPDF); |
michael@0 | 286 | NS_ASSERTION(mEmbeddingStack.Length(), "embedding/override underflow"); |
michael@0 | 287 | mEmbeddingStack.TruncateLength(mEmbeddingStack.Length() - 1); |
michael@0 | 288 | } |
michael@0 | 289 | |
michael@0 | 290 | void ClearBidiControls() |
michael@0 | 291 | { |
michael@0 | 292 | for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) { |
michael@0 | 293 | AppendControlChar(kPDF); |
michael@0 | 294 | } |
michael@0 | 295 | } |
michael@0 | 296 | |
michael@0 | 297 | static bool |
michael@0 | 298 | IsFrameInCurrentLine(nsBlockInFlowLineIterator* aLineIter, |
michael@0 | 299 | nsIFrame* aPrevFrame, nsIFrame* aFrame) |
michael@0 | 300 | { |
michael@0 | 301 | nsIFrame* endFrame = aLineIter->IsLastLineInList() ? nullptr : |
michael@0 | 302 | aLineIter->GetLine().next()->mFirstChild; |
michael@0 | 303 | nsIFrame* startFrame = aPrevFrame ? aPrevFrame : aLineIter->GetLine()->mFirstChild; |
michael@0 | 304 | for (nsIFrame* frame = startFrame; frame && frame != endFrame; |
michael@0 | 305 | frame = frame->GetNextSibling()) { |
michael@0 | 306 | if (frame == aFrame) |
michael@0 | 307 | return true; |
michael@0 | 308 | } |
michael@0 | 309 | return false; |
michael@0 | 310 | } |
michael@0 | 311 | |
michael@0 | 312 | static void |
michael@0 | 313 | AdvanceLineIteratorToFrame(nsIFrame* aFrame, |
michael@0 | 314 | nsBlockInFlowLineIterator* aLineIter, |
michael@0 | 315 | nsIFrame*& aPrevFrame) |
michael@0 | 316 | { |
michael@0 | 317 | // Advance aLine to the line containing aFrame |
michael@0 | 318 | nsIFrame* child = aFrame; |
michael@0 | 319 | nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(child); |
michael@0 | 320 | while (parent && !nsLayoutUtils::GetAsBlock(parent)) { |
michael@0 | 321 | child = parent; |
michael@0 | 322 | parent = nsLayoutUtils::GetParentOrPlaceholderFor(child); |
michael@0 | 323 | } |
michael@0 | 324 | NS_ASSERTION (parent, "aFrame is not a descendent of aBlockFrame"); |
michael@0 | 325 | while (!IsFrameInCurrentLine(aLineIter, aPrevFrame, child)) { |
michael@0 | 326 | #ifdef DEBUG |
michael@0 | 327 | bool hasNext = |
michael@0 | 328 | #endif |
michael@0 | 329 | aLineIter->Next(); |
michael@0 | 330 | NS_ASSERTION(hasNext, "Can't find frame in lines!"); |
michael@0 | 331 | aPrevFrame = nullptr; |
michael@0 | 332 | } |
michael@0 | 333 | aPrevFrame = child; |
michael@0 | 334 | } |
michael@0 | 335 | |
michael@0 | 336 | }; |
michael@0 | 337 | |
michael@0 | 338 | struct BidiLineData { |
michael@0 | 339 | nsTArray<nsIFrame*> mLogicalFrames; |
michael@0 | 340 | nsTArray<nsIFrame*> mVisualFrames; |
michael@0 | 341 | nsTArray<int32_t> mIndexMap; |
michael@0 | 342 | nsAutoTArray<uint8_t, 18> mLevels; |
michael@0 | 343 | bool mIsReordered; |
michael@0 | 344 | |
michael@0 | 345 | BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine) |
michael@0 | 346 | { |
michael@0 | 347 | /** |
michael@0 | 348 | * Initialize the logically-ordered array of frames using the top-level |
michael@0 | 349 | * frames of a single line |
michael@0 | 350 | */ |
michael@0 | 351 | mLogicalFrames.Clear(); |
michael@0 | 352 | |
michael@0 | 353 | bool isReordered = false; |
michael@0 | 354 | bool hasRTLFrames = false; |
michael@0 | 355 | |
michael@0 | 356 | for (nsIFrame* frame = aFirstFrameOnLine; |
michael@0 | 357 | frame && aNumFramesOnLine--; |
michael@0 | 358 | frame = frame->GetNextSibling()) { |
michael@0 | 359 | AppendFrame(frame); |
michael@0 | 360 | uint8_t level = nsBidiPresUtils::GetFrameEmbeddingLevel(frame); |
michael@0 | 361 | mLevels.AppendElement(level); |
michael@0 | 362 | mIndexMap.AppendElement(0); |
michael@0 | 363 | if (level & 1) { |
michael@0 | 364 | hasRTLFrames = true; |
michael@0 | 365 | } |
michael@0 | 366 | } |
michael@0 | 367 | |
michael@0 | 368 | // Reorder the line |
michael@0 | 369 | nsBidi::ReorderVisual(mLevels.Elements(), FrameCount(), |
michael@0 | 370 | mIndexMap.Elements()); |
michael@0 | 371 | |
michael@0 | 372 | for (int32_t i = 0; i < FrameCount(); i++) { |
michael@0 | 373 | mVisualFrames.AppendElement(LogicalFrameAt(mIndexMap[i])); |
michael@0 | 374 | if (i != mIndexMap[i]) { |
michael@0 | 375 | isReordered = true; |
michael@0 | 376 | } |
michael@0 | 377 | } |
michael@0 | 378 | |
michael@0 | 379 | // If there's an RTL frame, assume the line is reordered |
michael@0 | 380 | mIsReordered = isReordered || hasRTLFrames; |
michael@0 | 381 | } |
michael@0 | 382 | |
michael@0 | 383 | void AppendFrame(nsIFrame* aFrame) |
michael@0 | 384 | { |
michael@0 | 385 | mLogicalFrames.AppendElement(aFrame); |
michael@0 | 386 | } |
michael@0 | 387 | |
michael@0 | 388 | int32_t FrameCount(){ return mLogicalFrames.Length(); } |
michael@0 | 389 | |
michael@0 | 390 | nsIFrame* LogicalFrameAt(int32_t aIndex){ return mLogicalFrames[aIndex]; } |
michael@0 | 391 | |
michael@0 | 392 | nsIFrame* VisualFrameAt(int32_t aIndex){ return mVisualFrames[aIndex]; } |
michael@0 | 393 | }; |
michael@0 | 394 | |
michael@0 | 395 | /* Some helper methods for Resolve() */ |
michael@0 | 396 | |
michael@0 | 397 | // Should this frame be split between text runs? |
michael@0 | 398 | static bool |
michael@0 | 399 | IsBidiSplittable(nsIFrame* aFrame) |
michael@0 | 400 | { |
michael@0 | 401 | // Bidi inline containers should be split, unless they're line frames. |
michael@0 | 402 | nsIAtom* frameType = aFrame->GetType(); |
michael@0 | 403 | return (aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer) && |
michael@0 | 404 | frameType != nsGkAtoms::lineFrame) || |
michael@0 | 405 | frameType == nsGkAtoms::textFrame; |
michael@0 | 406 | } |
michael@0 | 407 | |
michael@0 | 408 | // Should this frame be treated as a leaf (e.g. when building mLogicalFrames)? |
michael@0 | 409 | static bool |
michael@0 | 410 | IsBidiLeaf(nsIFrame* aFrame) |
michael@0 | 411 | { |
michael@0 | 412 | nsIFrame* kid = aFrame->GetFirstPrincipalChild(); |
michael@0 | 413 | return !kid || !aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer); |
michael@0 | 414 | } |
michael@0 | 415 | |
michael@0 | 416 | /** |
michael@0 | 417 | * Create non-fluid continuations for the ancestors of a given frame all the way |
michael@0 | 418 | * up the frame tree until we hit a non-splittable frame (a line or a block). |
michael@0 | 419 | * |
michael@0 | 420 | * @param aParent the first parent frame to be split |
michael@0 | 421 | * @param aFrame the child frames after this frame are reparented to the |
michael@0 | 422 | * newly-created continuation of aParent. |
michael@0 | 423 | * If aFrame is null, all the children of aParent are reparented. |
michael@0 | 424 | */ |
michael@0 | 425 | static nsresult |
michael@0 | 426 | SplitInlineAncestors(nsIFrame* aParent, |
michael@0 | 427 | nsIFrame* aFrame) |
michael@0 | 428 | { |
michael@0 | 429 | nsPresContext *presContext = aParent->PresContext(); |
michael@0 | 430 | nsIPresShell *presShell = presContext->PresShell(); |
michael@0 | 431 | nsIFrame* frame = aFrame; |
michael@0 | 432 | nsIFrame* parent = aParent; |
michael@0 | 433 | nsIFrame* newParent; |
michael@0 | 434 | |
michael@0 | 435 | while (IsBidiSplittable(parent)) { |
michael@0 | 436 | nsIFrame* grandparent = parent->GetParent(); |
michael@0 | 437 | NS_ASSERTION(grandparent, "Couldn't get parent's parent in nsBidiPresUtils::SplitInlineAncestors"); |
michael@0 | 438 | |
michael@0 | 439 | // Split the child list after |frame|, unless it is the last child. |
michael@0 | 440 | if (!frame || frame->GetNextSibling()) { |
michael@0 | 441 | |
michael@0 | 442 | newParent = presShell->FrameConstructor()-> |
michael@0 | 443 | CreateContinuingFrame(presContext, parent, grandparent, false); |
michael@0 | 444 | |
michael@0 | 445 | nsContainerFrame* container = do_QueryFrame(parent); |
michael@0 | 446 | nsFrameList tail = container->StealFramesAfter(frame); |
michael@0 | 447 | |
michael@0 | 448 | // Reparent views as necessary |
michael@0 | 449 | nsresult rv; |
michael@0 | 450 | rv = nsContainerFrame::ReparentFrameViewList(tail, parent, newParent); |
michael@0 | 451 | if (NS_FAILED(rv)) { |
michael@0 | 452 | return rv; |
michael@0 | 453 | } |
michael@0 | 454 | |
michael@0 | 455 | // The parent's continuation adopts the siblings after the split. |
michael@0 | 456 | rv = newParent->InsertFrames(nsIFrame::kNoReflowPrincipalList, nullptr, tail); |
michael@0 | 457 | if (NS_FAILED(rv)) { |
michael@0 | 458 | return rv; |
michael@0 | 459 | } |
michael@0 | 460 | |
michael@0 | 461 | // The list name kNoReflowPrincipalList would indicate we don't want reflow |
michael@0 | 462 | nsFrameList temp(newParent, newParent); |
michael@0 | 463 | rv = grandparent->InsertFrames(nsIFrame::kNoReflowPrincipalList, parent, temp); |
michael@0 | 464 | if (NS_FAILED(rv)) { |
michael@0 | 465 | return rv; |
michael@0 | 466 | } |
michael@0 | 467 | } |
michael@0 | 468 | |
michael@0 | 469 | frame = parent; |
michael@0 | 470 | parent = grandparent; |
michael@0 | 471 | } |
michael@0 | 472 | |
michael@0 | 473 | return NS_OK; |
michael@0 | 474 | } |
michael@0 | 475 | |
michael@0 | 476 | static void |
michael@0 | 477 | MakeContinuationFluid(nsIFrame* aFrame, nsIFrame* aNext) |
michael@0 | 478 | { |
michael@0 | 479 | NS_ASSERTION (!aFrame->GetNextInFlow() || aFrame->GetNextInFlow() == aNext, |
michael@0 | 480 | "next-in-flow is not next continuation!"); |
michael@0 | 481 | aFrame->SetNextInFlow(aNext); |
michael@0 | 482 | |
michael@0 | 483 | NS_ASSERTION (!aNext->GetPrevInFlow() || aNext->GetPrevInFlow() == aFrame, |
michael@0 | 484 | "prev-in-flow is not prev continuation!"); |
michael@0 | 485 | aNext->SetPrevInFlow(aFrame); |
michael@0 | 486 | } |
michael@0 | 487 | |
michael@0 | 488 | static void |
michael@0 | 489 | MakeContinuationsNonFluidUpParentChain(nsIFrame* aFrame, nsIFrame* aNext) |
michael@0 | 490 | { |
michael@0 | 491 | nsIFrame* frame; |
michael@0 | 492 | nsIFrame* next; |
michael@0 | 493 | |
michael@0 | 494 | for (frame = aFrame, next = aNext; |
michael@0 | 495 | frame && next && |
michael@0 | 496 | next != frame && next == frame->GetNextInFlow() && |
michael@0 | 497 | IsBidiSplittable(frame); |
michael@0 | 498 | frame = frame->GetParent(), next = next->GetParent()) { |
michael@0 | 499 | |
michael@0 | 500 | frame->SetNextContinuation(next); |
michael@0 | 501 | next->SetPrevContinuation(frame); |
michael@0 | 502 | } |
michael@0 | 503 | } |
michael@0 | 504 | |
michael@0 | 505 | // If aFrame is the last child of its parent, convert bidi continuations to |
michael@0 | 506 | // fluid continuations for all of its inline ancestors. |
michael@0 | 507 | // If it isn't the last child, make sure that its continuation is fluid. |
michael@0 | 508 | static void |
michael@0 | 509 | JoinInlineAncestors(nsIFrame* aFrame) |
michael@0 | 510 | { |
michael@0 | 511 | nsIFrame* frame = aFrame; |
michael@0 | 512 | do { |
michael@0 | 513 | nsIFrame* next = frame->GetNextContinuation(); |
michael@0 | 514 | if (next) { |
michael@0 | 515 | // Don't join frames if they come from different paragraph depths (i.e. |
michael@0 | 516 | // one is bidi isolated relative to the other |
michael@0 | 517 | if (nsBidiPresUtils::GetParagraphDepth(frame) == |
michael@0 | 518 | nsBidiPresUtils::GetParagraphDepth(next)) { |
michael@0 | 519 | MakeContinuationFluid(frame, next); |
michael@0 | 520 | } |
michael@0 | 521 | } |
michael@0 | 522 | // Join the parent only as long as we're its last child. |
michael@0 | 523 | if (frame->GetNextSibling()) |
michael@0 | 524 | break; |
michael@0 | 525 | frame = frame->GetParent(); |
michael@0 | 526 | } while (frame && IsBidiSplittable(frame)); |
michael@0 | 527 | } |
michael@0 | 528 | |
michael@0 | 529 | static nsresult |
michael@0 | 530 | CreateContinuation(nsIFrame* aFrame, |
michael@0 | 531 | nsIFrame** aNewFrame, |
michael@0 | 532 | bool aIsFluid) |
michael@0 | 533 | { |
michael@0 | 534 | NS_PRECONDITION(aNewFrame, "null OUT ptr"); |
michael@0 | 535 | NS_PRECONDITION(aFrame, "null ptr"); |
michael@0 | 536 | |
michael@0 | 537 | *aNewFrame = nullptr; |
michael@0 | 538 | |
michael@0 | 539 | nsPresContext *presContext = aFrame->PresContext(); |
michael@0 | 540 | nsIPresShell *presShell = presContext->PresShell(); |
michael@0 | 541 | NS_ASSERTION(presShell, "PresShell must be set on PresContext before calling nsBidiPresUtils::CreateContinuation"); |
michael@0 | 542 | |
michael@0 | 543 | nsIFrame* parent = aFrame->GetParent(); |
michael@0 | 544 | NS_ASSERTION(parent, "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation"); |
michael@0 | 545 | |
michael@0 | 546 | nsresult rv = NS_OK; |
michael@0 | 547 | |
michael@0 | 548 | // Have to special case floating first letter frames because the continuation |
michael@0 | 549 | // doesn't go in the first letter frame. The continuation goes with the rest |
michael@0 | 550 | // of the text that the first letter frame was made out of. |
michael@0 | 551 | if (parent->GetType() == nsGkAtoms::letterFrame && |
michael@0 | 552 | parent->IsFloating()) { |
michael@0 | 553 | nsFirstLetterFrame* letterFrame = do_QueryFrame(parent); |
michael@0 | 554 | rv = letterFrame->CreateContinuationForFloatingParent(presContext, aFrame, |
michael@0 | 555 | aNewFrame, aIsFluid); |
michael@0 | 556 | return rv; |
michael@0 | 557 | } |
michael@0 | 558 | |
michael@0 | 559 | *aNewFrame = presShell->FrameConstructor()-> |
michael@0 | 560 | CreateContinuingFrame(presContext, aFrame, parent, aIsFluid); |
michael@0 | 561 | |
michael@0 | 562 | // The list name kNoReflowPrincipalList would indicate we don't want reflow |
michael@0 | 563 | // XXXbz this needs higher-level framelist love |
michael@0 | 564 | nsFrameList temp(*aNewFrame, *aNewFrame); |
michael@0 | 565 | rv = parent->InsertFrames(nsIFrame::kNoReflowPrincipalList, aFrame, temp); |
michael@0 | 566 | if (NS_FAILED(rv)) { |
michael@0 | 567 | return rv; |
michael@0 | 568 | } |
michael@0 | 569 | |
michael@0 | 570 | if (!aIsFluid) { |
michael@0 | 571 | // Split inline ancestor frames |
michael@0 | 572 | rv = SplitInlineAncestors(parent, aFrame); |
michael@0 | 573 | if (NS_FAILED(rv)) { |
michael@0 | 574 | return rv; |
michael@0 | 575 | } |
michael@0 | 576 | } |
michael@0 | 577 | |
michael@0 | 578 | return NS_OK; |
michael@0 | 579 | } |
michael@0 | 580 | |
michael@0 | 581 | /* |
michael@0 | 582 | * Overview of the implementation of Resolve(): |
michael@0 | 583 | * |
michael@0 | 584 | * Walk through the descendants of aBlockFrame and build: |
michael@0 | 585 | * * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order |
michael@0 | 586 | * * mBuffer: an nsString containing a representation of |
michael@0 | 587 | * the content of the frames. |
michael@0 | 588 | * In the case of text frames, this is the actual text context of the |
michael@0 | 589 | * frames, but some other elements are represented in a symbolic form which |
michael@0 | 590 | * will make the Unicode Bidi Algorithm give the correct results. |
michael@0 | 591 | * Bidi embeddings and overrides set by CSS or <bdo> elements are |
michael@0 | 592 | * represented by the corresponding Unicode control characters. |
michael@0 | 593 | * <br> elements are represented by U+2028 LINE SEPARATOR |
michael@0 | 594 | * Other inline elements are represented by U+FFFC OBJECT REPLACEMENT |
michael@0 | 595 | * CHARACTER |
michael@0 | 596 | * |
michael@0 | 597 | * Then pass mBuffer to the Bidi engine for resolving of embedding levels |
michael@0 | 598 | * by nsBidi::SetPara() and division into directional runs by |
michael@0 | 599 | * nsBidi::CountRuns(). |
michael@0 | 600 | * |
michael@0 | 601 | * Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and |
michael@0 | 602 | * correlate them with the frames indexed in mLogicalFrames, setting the |
michael@0 | 603 | * baseLevel and embeddingLevel properties according to the results returned |
michael@0 | 604 | * by the Bidi engine. |
michael@0 | 605 | * |
michael@0 | 606 | * The rendering layer requires each text frame to contain text in only one |
michael@0 | 607 | * direction, so we may need to call EnsureBidiContinuation() to split frames. |
michael@0 | 608 | * We may also need to call RemoveBidiContinuation() to convert frames created |
michael@0 | 609 | * by EnsureBidiContinuation() in previous reflows into fluid continuations. |
michael@0 | 610 | */ |
michael@0 | 611 | nsresult |
michael@0 | 612 | nsBidiPresUtils::Resolve(nsBlockFrame* aBlockFrame) |
michael@0 | 613 | { |
michael@0 | 614 | BidiParagraphData bpd; |
michael@0 | 615 | bpd.Init(aBlockFrame); |
michael@0 | 616 | |
michael@0 | 617 | // Handle bidi-override being set on the block itself before calling |
michael@0 | 618 | // TraverseFrames. |
michael@0 | 619 | const nsStyleTextReset* text = aBlockFrame->StyleTextReset(); |
michael@0 | 620 | char16_t ch = 0; |
michael@0 | 621 | if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_OVERRIDE) { |
michael@0 | 622 | const nsStyleVisibility* vis = aBlockFrame->StyleVisibility(); |
michael@0 | 623 | if (NS_STYLE_DIRECTION_RTL == vis->mDirection) { |
michael@0 | 624 | ch = kRLO; |
michael@0 | 625 | } |
michael@0 | 626 | else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) { |
michael@0 | 627 | ch = kLRO; |
michael@0 | 628 | } |
michael@0 | 629 | if (ch != 0) { |
michael@0 | 630 | bpd.PushBidiControl(ch); |
michael@0 | 631 | } |
michael@0 | 632 | } |
michael@0 | 633 | for (nsBlockFrame* block = aBlockFrame; block; |
michael@0 | 634 | block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) { |
michael@0 | 635 | block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); |
michael@0 | 636 | nsBlockInFlowLineIterator lineIter(block, block->begin_lines()); |
michael@0 | 637 | bpd.ResetForNewBlock(); |
michael@0 | 638 | TraverseFrames(aBlockFrame, &lineIter, block->GetFirstPrincipalChild(), &bpd); |
michael@0 | 639 | // XXX what about overflow lines? |
michael@0 | 640 | } |
michael@0 | 641 | |
michael@0 | 642 | if (ch != 0) { |
michael@0 | 643 | bpd.PopBidiControl(); |
michael@0 | 644 | } |
michael@0 | 645 | |
michael@0 | 646 | BidiParagraphData* subParagraph = bpd.GetSubParagraph(); |
michael@0 | 647 | if (subParagraph->BufferLength()) { |
michael@0 | 648 | ResolveParagraph(aBlockFrame, subParagraph); |
michael@0 | 649 | subParagraph->EmptyBuffer(); |
michael@0 | 650 | } |
michael@0 | 651 | return ResolveParagraph(aBlockFrame, &bpd); |
michael@0 | 652 | } |
michael@0 | 653 | |
michael@0 | 654 | nsresult |
michael@0 | 655 | nsBidiPresUtils::ResolveParagraph(nsBlockFrame* aBlockFrame, |
michael@0 | 656 | BidiParagraphData* aBpd) |
michael@0 | 657 | { |
michael@0 | 658 | nsPresContext *presContext = aBlockFrame->PresContext(); |
michael@0 | 659 | |
michael@0 | 660 | if (aBpd->BufferLength() < 1) { |
michael@0 | 661 | return NS_OK; |
michael@0 | 662 | } |
michael@0 | 663 | aBpd->mBuffer.ReplaceChar(kSeparators, kSpace); |
michael@0 | 664 | |
michael@0 | 665 | int32_t runCount; |
michael@0 | 666 | |
michael@0 | 667 | nsresult rv = aBpd->SetPara(); |
michael@0 | 668 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 669 | |
michael@0 | 670 | uint8_t embeddingLevel = aBpd->GetParaLevel(); |
michael@0 | 671 | |
michael@0 | 672 | rv = aBpd->CountRuns(&runCount); |
michael@0 | 673 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 674 | |
michael@0 | 675 | int32_t runLength = 0; // the length of the current run of text |
michael@0 | 676 | int32_t lineOffset = 0; // the start of the current run |
michael@0 | 677 | int32_t logicalLimit = 0; // the end of the current run + 1 |
michael@0 | 678 | int32_t numRun = -1; |
michael@0 | 679 | int32_t fragmentLength = 0; // the length of the current text frame |
michael@0 | 680 | int32_t frameIndex = -1; // index to the frames in mLogicalFrames |
michael@0 | 681 | int32_t frameCount = aBpd->FrameCount(); |
michael@0 | 682 | int32_t contentOffset = 0; // offset of current frame in its content node |
michael@0 | 683 | bool isTextFrame = false; |
michael@0 | 684 | nsIFrame* frame = nullptr; |
michael@0 | 685 | nsIContent* content = nullptr; |
michael@0 | 686 | int32_t contentTextLength = 0; |
michael@0 | 687 | |
michael@0 | 688 | FramePropertyTable *propTable = presContext->PropertyTable(); |
michael@0 | 689 | nsLineBox* currentLine = nullptr; |
michael@0 | 690 | |
michael@0 | 691 | #ifdef DEBUG |
michael@0 | 692 | #ifdef NOISY_BIDI |
michael@0 | 693 | printf("Before Resolve(), aBlockFrame=0x%p, mBuffer='%s', frameCount=%d, runCount=%d\n", |
michael@0 | 694 | (void*)aBlockFrame, NS_ConvertUTF16toUTF8(aBpd->mBuffer).get(), frameCount, runCount); |
michael@0 | 695 | #ifdef REALLY_NOISY_BIDI |
michael@0 | 696 | printf(" block frame tree=:\n"); |
michael@0 | 697 | aBlockFrame->List(stdout, 0); |
michael@0 | 698 | #endif |
michael@0 | 699 | #endif |
michael@0 | 700 | #endif |
michael@0 | 701 | |
michael@0 | 702 | if (runCount == 1 && frameCount == 1 && |
michael@0 | 703 | aBpd->mParagraphDepth == 0 && aBpd->GetDirection() == NSBIDI_LTR && |
michael@0 | 704 | aBpd->GetParaLevel() == 0) { |
michael@0 | 705 | // We have a single left-to-right frame in a left-to-right paragraph, |
michael@0 | 706 | // without bidi isolation from the surrounding text. |
michael@0 | 707 | // Make sure that the embedding level and base level frame properties aren't |
michael@0 | 708 | // set (because if they are this frame used to have some other direction, |
michael@0 | 709 | // so we can't do this optimization), and we're done. |
michael@0 | 710 | nsIFrame* frame = aBpd->FrameAt(0); |
michael@0 | 711 | if (frame != NS_BIDI_CONTROL_FRAME && |
michael@0 | 712 | !frame->Properties().Get(nsIFrame::EmbeddingLevelProperty()) && |
michael@0 | 713 | !frame->Properties().Get(nsIFrame::BaseLevelProperty())) { |
michael@0 | 714 | #ifdef DEBUG |
michael@0 | 715 | #ifdef NOISY_BIDI |
michael@0 | 716 | printf("early return for single direction frame %p\n", (void*)frame); |
michael@0 | 717 | #endif |
michael@0 | 718 | #endif |
michael@0 | 719 | frame->AddStateBits(NS_FRAME_IS_BIDI); |
michael@0 | 720 | return NS_OK; |
michael@0 | 721 | } |
michael@0 | 722 | } |
michael@0 | 723 | |
michael@0 | 724 | nsIFrame* firstFrame = nullptr; |
michael@0 | 725 | nsIFrame* lastFrame = nullptr; |
michael@0 | 726 | |
michael@0 | 727 | for (; ;) { |
michael@0 | 728 | if (fragmentLength <= 0) { |
michael@0 | 729 | // Get the next frame from mLogicalFrames |
michael@0 | 730 | if (++frameIndex >= frameCount) { |
michael@0 | 731 | break; |
michael@0 | 732 | } |
michael@0 | 733 | frame = aBpd->FrameAt(frameIndex); |
michael@0 | 734 | if (frame == NS_BIDI_CONTROL_FRAME || |
michael@0 | 735 | nsGkAtoms::textFrame != frame->GetType()) { |
michael@0 | 736 | /* |
michael@0 | 737 | * Any non-text frame corresponds to a single character in the text buffer |
michael@0 | 738 | * (a bidi control character, LINE SEPARATOR, or OBJECT SUBSTITUTE) |
michael@0 | 739 | */ |
michael@0 | 740 | isTextFrame = false; |
michael@0 | 741 | fragmentLength = 1; |
michael@0 | 742 | } |
michael@0 | 743 | else { |
michael@0 | 744 | if (!firstFrame) { |
michael@0 | 745 | firstFrame = frame; |
michael@0 | 746 | } |
michael@0 | 747 | lastFrame = frame; |
michael@0 | 748 | currentLine = aBpd->GetLineForFrameAt(frameIndex); |
michael@0 | 749 | content = frame->GetContent(); |
michael@0 | 750 | if (!content) { |
michael@0 | 751 | rv = NS_OK; |
michael@0 | 752 | break; |
michael@0 | 753 | } |
michael@0 | 754 | contentTextLength = content->TextLength(); |
michael@0 | 755 | if (contentTextLength == 0) { |
michael@0 | 756 | frame->AdjustOffsetsForBidi(0, 0); |
michael@0 | 757 | // Set the base level and embedding level of the current run even |
michael@0 | 758 | // on an empty frame. Otherwise frame reordering will not be correct. |
michael@0 | 759 | propTable->Set(frame, nsIFrame::EmbeddingLevelProperty(), |
michael@0 | 760 | NS_INT32_TO_PTR(embeddingLevel)); |
michael@0 | 761 | propTable->Set(frame, nsIFrame::BaseLevelProperty(), |
michael@0 | 762 | NS_INT32_TO_PTR(aBpd->GetParaLevel())); |
michael@0 | 763 | propTable->Set(frame, nsIFrame::ParagraphDepthProperty(), |
michael@0 | 764 | NS_INT32_TO_PTR(aBpd->mParagraphDepth)); |
michael@0 | 765 | continue; |
michael@0 | 766 | } |
michael@0 | 767 | int32_t start, end; |
michael@0 | 768 | frame->GetOffsets(start, end); |
michael@0 | 769 | NS_ASSERTION(!(contentTextLength < end - start), |
michael@0 | 770 | "Frame offsets don't fit in content"); |
michael@0 | 771 | fragmentLength = std::min(contentTextLength, end - start); |
michael@0 | 772 | contentOffset = start; |
michael@0 | 773 | isTextFrame = true; |
michael@0 | 774 | } |
michael@0 | 775 | } // if (fragmentLength <= 0) |
michael@0 | 776 | |
michael@0 | 777 | if (runLength <= 0) { |
michael@0 | 778 | // Get the next run of text from the Bidi engine |
michael@0 | 779 | if (++numRun >= runCount) { |
michael@0 | 780 | break; |
michael@0 | 781 | } |
michael@0 | 782 | lineOffset = logicalLimit; |
michael@0 | 783 | if (NS_FAILED(aBpd->GetLogicalRun( |
michael@0 | 784 | lineOffset, &logicalLimit, &embeddingLevel) ) ) { |
michael@0 | 785 | break; |
michael@0 | 786 | } |
michael@0 | 787 | runLength = logicalLimit - lineOffset; |
michael@0 | 788 | } // if (runLength <= 0) |
michael@0 | 789 | |
michael@0 | 790 | if (frame == NS_BIDI_CONTROL_FRAME) { |
michael@0 | 791 | frame = nullptr; |
michael@0 | 792 | ++lineOffset; |
michael@0 | 793 | } |
michael@0 | 794 | else { |
michael@0 | 795 | propTable->Set(frame, nsIFrame::EmbeddingLevelProperty(), |
michael@0 | 796 | NS_INT32_TO_PTR(embeddingLevel)); |
michael@0 | 797 | propTable->Set(frame, nsIFrame::BaseLevelProperty(), |
michael@0 | 798 | NS_INT32_TO_PTR(aBpd->GetParaLevel())); |
michael@0 | 799 | propTable->Set(frame, nsIFrame::ParagraphDepthProperty(), |
michael@0 | 800 | NS_INT32_TO_PTR(aBpd->mParagraphDepth)); |
michael@0 | 801 | if (isTextFrame) { |
michael@0 | 802 | if ( (runLength > 0) && (runLength < fragmentLength) ) { |
michael@0 | 803 | /* |
michael@0 | 804 | * The text in this frame continues beyond the end of this directional run. |
michael@0 | 805 | * Create a non-fluid continuation frame for the next directional run. |
michael@0 | 806 | */ |
michael@0 | 807 | currentLine->MarkDirty(); |
michael@0 | 808 | nsIFrame* nextBidi; |
michael@0 | 809 | int32_t runEnd = contentOffset + runLength; |
michael@0 | 810 | rv = EnsureBidiContinuation(frame, &nextBidi, frameIndex, |
michael@0 | 811 | contentOffset, |
michael@0 | 812 | runEnd); |
michael@0 | 813 | if (NS_FAILED(rv)) { |
michael@0 | 814 | break; |
michael@0 | 815 | } |
michael@0 | 816 | nextBidi->AdjustOffsetsForBidi(runEnd, |
michael@0 | 817 | contentOffset + fragmentLength); |
michael@0 | 818 | lastFrame = frame = nextBidi; |
michael@0 | 819 | contentOffset = runEnd; |
michael@0 | 820 | } // if (runLength < fragmentLength) |
michael@0 | 821 | else { |
michael@0 | 822 | if (contentOffset + fragmentLength == contentTextLength) { |
michael@0 | 823 | /* |
michael@0 | 824 | * We have finished all the text in this content node. Convert any |
michael@0 | 825 | * further non-fluid continuations to fluid continuations and advance |
michael@0 | 826 | * frameIndex to the last frame in the content node |
michael@0 | 827 | */ |
michael@0 | 828 | int32_t newIndex = aBpd->GetLastFrameForContent(content); |
michael@0 | 829 | if (newIndex > frameIndex) { |
michael@0 | 830 | currentLine->MarkDirty(); |
michael@0 | 831 | RemoveBidiContinuation(aBpd, frame, |
michael@0 | 832 | frameIndex, newIndex, lineOffset); |
michael@0 | 833 | frameIndex = newIndex; |
michael@0 | 834 | lastFrame = frame = aBpd->FrameAt(frameIndex); |
michael@0 | 835 | } |
michael@0 | 836 | } else if (fragmentLength > 0 && runLength > fragmentLength) { |
michael@0 | 837 | /* |
michael@0 | 838 | * There is more text that belongs to this directional run in the next |
michael@0 | 839 | * text frame: make sure it is a fluid continuation of the current frame. |
michael@0 | 840 | * Do not advance frameIndex, because the next frame may contain |
michael@0 | 841 | * multi-directional text and need to be split |
michael@0 | 842 | */ |
michael@0 | 843 | int32_t newIndex = frameIndex; |
michael@0 | 844 | do { |
michael@0 | 845 | } while (++newIndex < frameCount && |
michael@0 | 846 | aBpd->FrameAt(newIndex) == NS_BIDI_CONTROL_FRAME); |
michael@0 | 847 | if (newIndex < frameCount) { |
michael@0 | 848 | currentLine->MarkDirty(); |
michael@0 | 849 | RemoveBidiContinuation(aBpd, frame, |
michael@0 | 850 | frameIndex, newIndex, lineOffset); |
michael@0 | 851 | } |
michael@0 | 852 | } else if (runLength == fragmentLength) { |
michael@0 | 853 | /* |
michael@0 | 854 | * If the directional run ends at the end of the frame, make sure |
michael@0 | 855 | * that any continuation is non-fluid, and do the same up the |
michael@0 | 856 | * parent chain |
michael@0 | 857 | */ |
michael@0 | 858 | nsIFrame* next = frame->GetNextInFlow(); |
michael@0 | 859 | if (next) { |
michael@0 | 860 | currentLine->MarkDirty(); |
michael@0 | 861 | MakeContinuationsNonFluidUpParentChain(frame, next); |
michael@0 | 862 | } |
michael@0 | 863 | } |
michael@0 | 864 | frame->AdjustOffsetsForBidi(contentOffset, contentOffset + fragmentLength); |
michael@0 | 865 | currentLine->MarkDirty(); |
michael@0 | 866 | } |
michael@0 | 867 | } // isTextFrame |
michael@0 | 868 | else { |
michael@0 | 869 | ++lineOffset; |
michael@0 | 870 | } |
michael@0 | 871 | } // not bidi control frame |
michael@0 | 872 | int32_t temp = runLength; |
michael@0 | 873 | runLength -= fragmentLength; |
michael@0 | 874 | fragmentLength -= temp; |
michael@0 | 875 | |
michael@0 | 876 | if (frame && fragmentLength <= 0) { |
michael@0 | 877 | // If the frame is at the end of a run, and this is not the end of our |
michael@0 | 878 | // paragrah, split all ancestor inlines that need splitting. |
michael@0 | 879 | // To determine whether we're at the end of the run, we check that we've |
michael@0 | 880 | // finished processing the current run, and that the current frame |
michael@0 | 881 | // doesn't have a fluid continuation (it could have a fluid continuation |
michael@0 | 882 | // of zero length, so testing runLength alone is not sufficient). |
michael@0 | 883 | if (runLength <= 0 && !frame->GetNextInFlow()) { |
michael@0 | 884 | if (numRun + 1 < runCount) { |
michael@0 | 885 | nsIFrame* child = frame; |
michael@0 | 886 | nsIFrame* parent = frame->GetParent(); |
michael@0 | 887 | // As long as we're on the last sibling, the parent doesn't have to |
michael@0 | 888 | // be split. |
michael@0 | 889 | // However, if the parent has a fluid continuation, we do have to make |
michael@0 | 890 | // it non-fluid. This can happen e.g. when we have a first-letter |
michael@0 | 891 | // frame and the end of the first-letter coincides with the end of a |
michael@0 | 892 | // directional run. |
michael@0 | 893 | while (parent && |
michael@0 | 894 | IsBidiSplittable(parent) && |
michael@0 | 895 | !child->GetNextSibling()) { |
michael@0 | 896 | nsIFrame* next = parent->GetNextInFlow(); |
michael@0 | 897 | if (next) { |
michael@0 | 898 | parent->SetNextContinuation(next); |
michael@0 | 899 | next->SetPrevContinuation(parent); |
michael@0 | 900 | } |
michael@0 | 901 | child = parent; |
michael@0 | 902 | parent = child->GetParent(); |
michael@0 | 903 | } |
michael@0 | 904 | if (parent && IsBidiSplittable(parent)) { |
michael@0 | 905 | SplitInlineAncestors(parent, child); |
michael@0 | 906 | } |
michael@0 | 907 | } |
michael@0 | 908 | } |
michael@0 | 909 | else { |
michael@0 | 910 | // We're not at an end of a run. If |frame| is the last child of its |
michael@0 | 911 | // parent, and its ancestors happen to have bidi continuations, convert |
michael@0 | 912 | // them into fluid continuations. |
michael@0 | 913 | JoinInlineAncestors(frame); |
michael@0 | 914 | } |
michael@0 | 915 | } |
michael@0 | 916 | } // for |
michael@0 | 917 | |
michael@0 | 918 | if (aBpd->mParagraphDepth > 0) { |
michael@0 | 919 | nsIFrame* child; |
michael@0 | 920 | nsIFrame* parent; |
michael@0 | 921 | if (firstFrame) { |
michael@0 | 922 | child = firstFrame->GetParent(); |
michael@0 | 923 | if (child) { |
michael@0 | 924 | parent = child->GetParent(); |
michael@0 | 925 | if (parent && IsBidiSplittable(parent)) { |
michael@0 | 926 | nsIFrame* prev = child->GetPrevSibling(); |
michael@0 | 927 | if (prev) { |
michael@0 | 928 | SplitInlineAncestors(parent, prev); |
michael@0 | 929 | } |
michael@0 | 930 | } |
michael@0 | 931 | } |
michael@0 | 932 | } |
michael@0 | 933 | if (lastFrame) { |
michael@0 | 934 | child = lastFrame->GetParent(); |
michael@0 | 935 | if (child) { |
michael@0 | 936 | parent = child->GetParent(); |
michael@0 | 937 | if (parent && IsBidiSplittable(parent)) { |
michael@0 | 938 | SplitInlineAncestors(parent, child); |
michael@0 | 939 | } |
michael@0 | 940 | } |
michael@0 | 941 | } |
michael@0 | 942 | } |
michael@0 | 943 | |
michael@0 | 944 | #ifdef DEBUG |
michael@0 | 945 | #ifdef REALLY_NOISY_BIDI |
michael@0 | 946 | printf("---\nAfter Resolve(), frameTree =:\n"); |
michael@0 | 947 | aBlockFrame->List(stdout, 0); |
michael@0 | 948 | printf("===\n"); |
michael@0 | 949 | #endif |
michael@0 | 950 | #endif |
michael@0 | 951 | |
michael@0 | 952 | return rv; |
michael@0 | 953 | } |
michael@0 | 954 | |
michael@0 | 955 | void |
michael@0 | 956 | nsBidiPresUtils::TraverseFrames(nsBlockFrame* aBlockFrame, |
michael@0 | 957 | nsBlockInFlowLineIterator* aLineIter, |
michael@0 | 958 | nsIFrame* aCurrentFrame, |
michael@0 | 959 | BidiParagraphData* aBpd) |
michael@0 | 960 | { |
michael@0 | 961 | if (!aCurrentFrame) |
michael@0 | 962 | return; |
michael@0 | 963 | |
michael@0 | 964 | #ifdef DEBUG |
michael@0 | 965 | nsBlockFrame* initialLineContainer = aLineIter->GetContainer(); |
michael@0 | 966 | #endif |
michael@0 | 967 | |
michael@0 | 968 | nsIFrame* childFrame = aCurrentFrame; |
michael@0 | 969 | do { |
michael@0 | 970 | /* |
michael@0 | 971 | * It's important to get the next sibling and next continuation *before* |
michael@0 | 972 | * handling the frame: If we encounter a forced paragraph break and call |
michael@0 | 973 | * ResolveParagraph within this loop, doing GetNextSibling and |
michael@0 | 974 | * GetNextContinuation after that could return a bidi continuation that had |
michael@0 | 975 | * just been split from the original childFrame and we would process it |
michael@0 | 976 | * twice. |
michael@0 | 977 | */ |
michael@0 | 978 | nsIFrame* nextSibling = childFrame->GetNextSibling(); |
michael@0 | 979 | bool isLastFrame = !childFrame->GetNextContinuation(); |
michael@0 | 980 | bool isFirstFrame = !childFrame->GetPrevContinuation(); |
michael@0 | 981 | |
michael@0 | 982 | // If the real frame for a placeholder is a first letter frame, we need to |
michael@0 | 983 | // drill down into it and include its contents in Bidi resolution. |
michael@0 | 984 | // If not, we just use the placeholder. |
michael@0 | 985 | nsIFrame* frame = childFrame; |
michael@0 | 986 | if (nsGkAtoms::placeholderFrame == childFrame->GetType()) { |
michael@0 | 987 | nsIFrame* realFrame = |
michael@0 | 988 | nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame); |
michael@0 | 989 | if (realFrame->GetType() == nsGkAtoms::letterFrame) { |
michael@0 | 990 | frame = realFrame; |
michael@0 | 991 | } |
michael@0 | 992 | } |
michael@0 | 993 | |
michael@0 | 994 | char16_t ch = 0; |
michael@0 | 995 | if (frame->IsFrameOfType(nsIFrame::eBidiInlineContainer)) { |
michael@0 | 996 | if (!(frame->GetStateBits() & NS_FRAME_FIRST_REFLOW)) { |
michael@0 | 997 | nsContainerFrame* c = static_cast<nsContainerFrame*>(frame); |
michael@0 | 998 | MOZ_ASSERT(c = do_QueryFrame(frame), |
michael@0 | 999 | "eBidiInlineContainer must be a nsContainerFrame subclass"); |
michael@0 | 1000 | c->DrainSelfOverflowList(); |
michael@0 | 1001 | } |
michael@0 | 1002 | |
michael@0 | 1003 | const nsStyleVisibility* vis = frame->StyleVisibility(); |
michael@0 | 1004 | const nsStyleTextReset* text = frame->StyleTextReset(); |
michael@0 | 1005 | if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_OVERRIDE) { |
michael@0 | 1006 | if (NS_STYLE_DIRECTION_RTL == vis->mDirection) { |
michael@0 | 1007 | ch = kRLO; |
michael@0 | 1008 | } |
michael@0 | 1009 | else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) { |
michael@0 | 1010 | ch = kLRO; |
michael@0 | 1011 | } |
michael@0 | 1012 | } else if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_EMBED) { |
michael@0 | 1013 | if (NS_STYLE_DIRECTION_RTL == vis->mDirection) { |
michael@0 | 1014 | ch = kRLE; |
michael@0 | 1015 | } |
michael@0 | 1016 | else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) { |
michael@0 | 1017 | ch = kLRE; |
michael@0 | 1018 | } |
michael@0 | 1019 | } |
michael@0 | 1020 | |
michael@0 | 1021 | // Add a dummy frame pointer representing a bidi control code before the |
michael@0 | 1022 | // first frame of an element specifying embedding or override |
michael@0 | 1023 | if (ch != 0 && isFirstFrame) { |
michael@0 | 1024 | aBpd->PushBidiControl(ch); |
michael@0 | 1025 | } |
michael@0 | 1026 | } |
michael@0 | 1027 | |
michael@0 | 1028 | if (IsBidiLeaf(frame)) { |
michael@0 | 1029 | /* Bidi leaf frame: add the frame to the mLogicalFrames array, |
michael@0 | 1030 | * and add its index to the mContentToFrameIndex hashtable. This |
michael@0 | 1031 | * will be used in RemoveBidiContinuation() to identify the last |
michael@0 | 1032 | * frame in the array with a given content. |
michael@0 | 1033 | */ |
michael@0 | 1034 | nsIContent* content = frame->GetContent(); |
michael@0 | 1035 | aBpd->AppendFrame(frame, aLineIter, content); |
michael@0 | 1036 | |
michael@0 | 1037 | // Append the content of the frame to the paragraph buffer |
michael@0 | 1038 | nsIAtom* frameType = frame->GetType(); |
michael@0 | 1039 | if (nsGkAtoms::textFrame == frameType) { |
michael@0 | 1040 | if (content != aBpd->mPrevContent) { |
michael@0 | 1041 | aBpd->mPrevContent = content; |
michael@0 | 1042 | if (!frame->StyleText()->NewlineIsSignificant()) { |
michael@0 | 1043 | content->AppendTextTo(aBpd->mBuffer); |
michael@0 | 1044 | } else { |
michael@0 | 1045 | /* |
michael@0 | 1046 | * For preformatted text we have to do bidi resolution on each line |
michael@0 | 1047 | * separately. |
michael@0 | 1048 | */ |
michael@0 | 1049 | nsAutoString text; |
michael@0 | 1050 | content->AppendTextTo(text); |
michael@0 | 1051 | nsIFrame* next; |
michael@0 | 1052 | do { |
michael@0 | 1053 | next = nullptr; |
michael@0 | 1054 | |
michael@0 | 1055 | int32_t start, end; |
michael@0 | 1056 | frame->GetOffsets(start, end); |
michael@0 | 1057 | int32_t endLine = text.FindChar('\n', start); |
michael@0 | 1058 | if (endLine == -1) { |
michael@0 | 1059 | /* |
michael@0 | 1060 | * If there is no newline in the text content, just save the |
michael@0 | 1061 | * text from this frame and its continuations, and do bidi |
michael@0 | 1062 | * resolution later |
michael@0 | 1063 | */ |
michael@0 | 1064 | aBpd->AppendString(Substring(text, start)); |
michael@0 | 1065 | while (frame && nextSibling) { |
michael@0 | 1066 | aBpd->AdvanceAndAppendFrame(&frame, aLineIter, &nextSibling); |
michael@0 | 1067 | } |
michael@0 | 1068 | break; |
michael@0 | 1069 | } |
michael@0 | 1070 | |
michael@0 | 1071 | /* |
michael@0 | 1072 | * If there is a newline in the frame, break the frame after the |
michael@0 | 1073 | * newline, do bidi resolution and repeat until the last sibling |
michael@0 | 1074 | */ |
michael@0 | 1075 | ++endLine; |
michael@0 | 1076 | |
michael@0 | 1077 | /* |
michael@0 | 1078 | * If the frame ends before the new line, save the text and move |
michael@0 | 1079 | * into the next continuation |
michael@0 | 1080 | */ |
michael@0 | 1081 | aBpd->AppendString(Substring(text, start, |
michael@0 | 1082 | std::min(end, endLine) - start)); |
michael@0 | 1083 | while (end < endLine && nextSibling) { |
michael@0 | 1084 | aBpd->AdvanceAndAppendFrame(&frame, aLineIter, &nextSibling); |
michael@0 | 1085 | NS_ASSERTION(frame, "Premature end of continuation chain"); |
michael@0 | 1086 | frame->GetOffsets(start, end); |
michael@0 | 1087 | aBpd->AppendString(Substring(text, start, |
michael@0 | 1088 | std::min(end, endLine) - start)); |
michael@0 | 1089 | } |
michael@0 | 1090 | |
michael@0 | 1091 | if (end < endLine) { |
michael@0 | 1092 | aBpd->mPrevContent = nullptr; |
michael@0 | 1093 | break; |
michael@0 | 1094 | } |
michael@0 | 1095 | |
michael@0 | 1096 | bool createdContinuation = false; |
michael@0 | 1097 | if (uint32_t(endLine) < text.Length()) { |
michael@0 | 1098 | /* |
michael@0 | 1099 | * Timing is everything here: if the frame already has a bidi |
michael@0 | 1100 | * continuation, we need to make the continuation fluid *before* |
michael@0 | 1101 | * resetting the length of the current frame. Otherwise |
michael@0 | 1102 | * nsTextFrame::SetLength won't set the continuation frame's |
michael@0 | 1103 | * text offsets correctly. |
michael@0 | 1104 | * |
michael@0 | 1105 | * On the other hand, if the frame doesn't have a continuation, |
michael@0 | 1106 | * we need to create one *after* resetting the length, or |
michael@0 | 1107 | * CreateContinuingFrame will complain that there is no more |
michael@0 | 1108 | * content for the continuation. |
michael@0 | 1109 | */ |
michael@0 | 1110 | next = frame->GetNextInFlow(); |
michael@0 | 1111 | if (!next) { |
michael@0 | 1112 | // If the frame already has a bidi continuation, make it fluid |
michael@0 | 1113 | next = frame->GetNextContinuation(); |
michael@0 | 1114 | if (next) { |
michael@0 | 1115 | MakeContinuationFluid(frame, next); |
michael@0 | 1116 | JoinInlineAncestors(frame); |
michael@0 | 1117 | } |
michael@0 | 1118 | } |
michael@0 | 1119 | |
michael@0 | 1120 | nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame); |
michael@0 | 1121 | textFrame->SetLength(endLine - start, nullptr); |
michael@0 | 1122 | |
michael@0 | 1123 | if (!next) { |
michael@0 | 1124 | // If the frame has no next in flow, create one. |
michael@0 | 1125 | CreateContinuation(frame, &next, true); |
michael@0 | 1126 | createdContinuation = true; |
michael@0 | 1127 | } |
michael@0 | 1128 | aBpd->GetLineForFrameAt(aBpd->FrameCount() - 1)->MarkDirty(); |
michael@0 | 1129 | } |
michael@0 | 1130 | ResolveParagraphWithinBlock(aBlockFrame, aBpd); |
michael@0 | 1131 | |
michael@0 | 1132 | if (!nextSibling && !createdContinuation) { |
michael@0 | 1133 | break; |
michael@0 | 1134 | } else if (next) { |
michael@0 | 1135 | frame = next; |
michael@0 | 1136 | aBpd->AppendFrame(frame, aLineIter); |
michael@0 | 1137 | } |
michael@0 | 1138 | |
michael@0 | 1139 | /* |
michael@0 | 1140 | * If we have already overshot the saved next-sibling while |
michael@0 | 1141 | * scanning the frame's continuations, advance it. |
michael@0 | 1142 | */ |
michael@0 | 1143 | if (frame && frame == nextSibling) { |
michael@0 | 1144 | nextSibling = frame->GetNextSibling(); |
michael@0 | 1145 | } |
michael@0 | 1146 | |
michael@0 | 1147 | } while (next); |
michael@0 | 1148 | } |
michael@0 | 1149 | } |
michael@0 | 1150 | } else if (nsGkAtoms::brFrame == frameType) { |
michael@0 | 1151 | // break frame -- append line separator |
michael@0 | 1152 | aBpd->AppendUnichar(kLineSeparator); |
michael@0 | 1153 | ResolveParagraphWithinBlock(aBlockFrame, aBpd); |
michael@0 | 1154 | } else { |
michael@0 | 1155 | // other frame type -- see the Unicode Bidi Algorithm: |
michael@0 | 1156 | // "...inline objects (such as graphics) are treated as if they are ... |
michael@0 | 1157 | // U+FFFC" |
michael@0 | 1158 | // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See |
michael@0 | 1159 | // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1 |
michael@0 | 1160 | aBpd->AppendUnichar(content->IsHTML(nsGkAtoms::wbr) ? |
michael@0 | 1161 | kZWSP : kObjectSubstitute); |
michael@0 | 1162 | if (!frame->IsInlineOutside()) { |
michael@0 | 1163 | // if it is not inline, end the paragraph |
michael@0 | 1164 | ResolveParagraphWithinBlock(aBlockFrame, aBpd); |
michael@0 | 1165 | } |
michael@0 | 1166 | } |
michael@0 | 1167 | } else { |
michael@0 | 1168 | // For a non-leaf frame, recurse into TraverseFrames |
michael@0 | 1169 | nsIFrame* kid = frame->GetFirstPrincipalChild(); |
michael@0 | 1170 | MOZ_ASSERT(!frame->GetFirstChild(nsIFrame::kOverflowList), |
michael@0 | 1171 | "should have drained the overflow list above"); |
michael@0 | 1172 | if (kid) { |
michael@0 | 1173 | const nsStyleTextReset* text = frame->StyleTextReset(); |
michael@0 | 1174 | if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_ISOLATE || |
michael@0 | 1175 | text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) { |
michael@0 | 1176 | // css "unicode-bidi: isolate" and html5 bdi: |
michael@0 | 1177 | // resolve the element as a separate paragraph |
michael@0 | 1178 | BidiParagraphData* subParagraph = aBpd->GetSubParagraph(); |
michael@0 | 1179 | |
michael@0 | 1180 | /* |
michael@0 | 1181 | * As at the beginning of the loop, it's important to check for |
michael@0 | 1182 | * next-continuations before handling the frame. If we do |
michael@0 | 1183 | * TraverseFrames and *then* do GetNextContinuation on the original |
michael@0 | 1184 | * first frame, it could return a bidi continuation that had only |
michael@0 | 1185 | * just been created, and we would skip doing bidi resolution on the |
michael@0 | 1186 | * last part of the sub-paragraph. |
michael@0 | 1187 | */ |
michael@0 | 1188 | bool isLastContinuation = !frame->GetNextContinuation(); |
michael@0 | 1189 | if (!frame->GetPrevContinuation() || !subParagraph->mReset) { |
michael@0 | 1190 | if (subParagraph->BufferLength()) { |
michael@0 | 1191 | ResolveParagraph(aBlockFrame, subParagraph); |
michael@0 | 1192 | } |
michael@0 | 1193 | subParagraph->Reset(frame, aBpd); |
michael@0 | 1194 | } |
michael@0 | 1195 | TraverseFrames(aBlockFrame, aLineIter, kid, subParagraph); |
michael@0 | 1196 | if (isLastContinuation) { |
michael@0 | 1197 | ResolveParagraph(aBlockFrame, subParagraph); |
michael@0 | 1198 | subParagraph->EmptyBuffer(); |
michael@0 | 1199 | } |
michael@0 | 1200 | |
michael@0 | 1201 | // Treat the element as a neutral character within its containing |
michael@0 | 1202 | // paragraph. |
michael@0 | 1203 | aBpd->AppendControlChar(kObjectSubstitute); |
michael@0 | 1204 | } else { |
michael@0 | 1205 | TraverseFrames(aBlockFrame, aLineIter, kid, aBpd); |
michael@0 | 1206 | } |
michael@0 | 1207 | } |
michael@0 | 1208 | } |
michael@0 | 1209 | |
michael@0 | 1210 | // If the element is attributed by dir, indicate direction pop (add PDF frame) |
michael@0 | 1211 | if (isLastFrame) { |
michael@0 | 1212 | if (ch) { |
michael@0 | 1213 | // Add a dummy frame pointer representing a bidi control code after the |
michael@0 | 1214 | // last frame of an element specifying embedding or override |
michael@0 | 1215 | aBpd->PopBidiControl(); |
michael@0 | 1216 | } |
michael@0 | 1217 | } |
michael@0 | 1218 | childFrame = nextSibling; |
michael@0 | 1219 | } while (childFrame); |
michael@0 | 1220 | |
michael@0 | 1221 | MOZ_ASSERT(initialLineContainer == aLineIter->GetContainer()); |
michael@0 | 1222 | } |
michael@0 | 1223 | |
michael@0 | 1224 | void |
michael@0 | 1225 | nsBidiPresUtils::ResolveParagraphWithinBlock(nsBlockFrame* aBlockFrame, |
michael@0 | 1226 | BidiParagraphData* aBpd) |
michael@0 | 1227 | { |
michael@0 | 1228 | aBpd->ClearBidiControls(); |
michael@0 | 1229 | ResolveParagraph(aBlockFrame, aBpd); |
michael@0 | 1230 | aBpd->ResetData(); |
michael@0 | 1231 | } |
michael@0 | 1232 | |
michael@0 | 1233 | void |
michael@0 | 1234 | nsBidiPresUtils::ReorderFrames(nsIFrame* aFirstFrameOnLine, |
michael@0 | 1235 | int32_t aNumFramesOnLine, |
michael@0 | 1236 | WritingMode aLineWM, |
michael@0 | 1237 | nscoord& aLineWidth) |
michael@0 | 1238 | { |
michael@0 | 1239 | // If this line consists of a line frame, reorder the line frame's children. |
michael@0 | 1240 | if (aFirstFrameOnLine->GetType() == nsGkAtoms::lineFrame) { |
michael@0 | 1241 | aFirstFrameOnLine = aFirstFrameOnLine->GetFirstPrincipalChild(); |
michael@0 | 1242 | if (!aFirstFrameOnLine) |
michael@0 | 1243 | return; |
michael@0 | 1244 | // All children of the line frame are on the first line. Setting aNumFramesOnLine |
michael@0 | 1245 | // to -1 makes InitLogicalArrayFromLine look at all of them. |
michael@0 | 1246 | aNumFramesOnLine = -1; |
michael@0 | 1247 | } |
michael@0 | 1248 | |
michael@0 | 1249 | BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); |
michael@0 | 1250 | RepositionInlineFrames(&bld, aFirstFrameOnLine, aLineWM, aLineWidth); |
michael@0 | 1251 | } |
michael@0 | 1252 | |
michael@0 | 1253 | nsIFrame* |
michael@0 | 1254 | nsBidiPresUtils::GetFirstLeaf(nsIFrame* aFrame) |
michael@0 | 1255 | { |
michael@0 | 1256 | nsIFrame* firstLeaf = aFrame; |
michael@0 | 1257 | while (!IsBidiLeaf(firstLeaf)) { |
michael@0 | 1258 | nsIFrame* firstChild = firstLeaf->GetFirstPrincipalChild(); |
michael@0 | 1259 | nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(firstChild); |
michael@0 | 1260 | firstLeaf = (realFrame->GetType() == nsGkAtoms::letterFrame) ? |
michael@0 | 1261 | realFrame : firstChild; |
michael@0 | 1262 | } |
michael@0 | 1263 | return firstLeaf; |
michael@0 | 1264 | } |
michael@0 | 1265 | |
michael@0 | 1266 | nsBidiLevel |
michael@0 | 1267 | nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) |
michael@0 | 1268 | { |
michael@0 | 1269 | return NS_GET_EMBEDDING_LEVEL(nsBidiPresUtils::GetFirstLeaf(aFrame)); |
michael@0 | 1270 | } |
michael@0 | 1271 | |
michael@0 | 1272 | uint8_t |
michael@0 | 1273 | nsBidiPresUtils::GetParagraphDepth(nsIFrame* aFrame) |
michael@0 | 1274 | { |
michael@0 | 1275 | return NS_GET_PARAGRAPH_DEPTH(nsBidiPresUtils::GetFirstLeaf(aFrame)); |
michael@0 | 1276 | } |
michael@0 | 1277 | |
michael@0 | 1278 | |
michael@0 | 1279 | nsBidiLevel |
michael@0 | 1280 | nsBidiPresUtils::GetFrameBaseLevel(nsIFrame* aFrame) |
michael@0 | 1281 | { |
michael@0 | 1282 | nsIFrame* firstLeaf = aFrame; |
michael@0 | 1283 | while (!IsBidiLeaf(firstLeaf)) { |
michael@0 | 1284 | firstLeaf = firstLeaf->GetFirstPrincipalChild(); |
michael@0 | 1285 | } |
michael@0 | 1286 | return NS_GET_BASE_LEVEL(firstLeaf); |
michael@0 | 1287 | } |
michael@0 | 1288 | |
michael@0 | 1289 | void |
michael@0 | 1290 | nsBidiPresUtils::IsFirstOrLast(nsIFrame* aFrame, |
michael@0 | 1291 | nsContinuationStates* aContinuationStates, |
michael@0 | 1292 | bool& aIsFirst /* out */, |
michael@0 | 1293 | bool& aIsLast /* out */) |
michael@0 | 1294 | { |
michael@0 | 1295 | /* |
michael@0 | 1296 | * Since we lay out frames in the line's direction, visiting a frame with |
michael@0 | 1297 | * 'mFirstVisualFrame == nullptr', means it's the first appearance of one |
michael@0 | 1298 | * of its continuation chain frames on the line. |
michael@0 | 1299 | * To determine if it's the last visual frame of its continuation chain on |
michael@0 | 1300 | * the line or not, we count the number of frames of the chain on the line, |
michael@0 | 1301 | * and then reduce it when we lay out a frame of the chain. If this value |
michael@0 | 1302 | * becomes 1 it means that it's the last visual frame of its continuation |
michael@0 | 1303 | * chain on this line. |
michael@0 | 1304 | */ |
michael@0 | 1305 | |
michael@0 | 1306 | nsFrameContinuationState* frameState = aContinuationStates->GetEntry(aFrame); |
michael@0 | 1307 | nsFrameContinuationState* firstFrameState; |
michael@0 | 1308 | |
michael@0 | 1309 | if (!frameState->mFirstVisualFrame) { |
michael@0 | 1310 | // aFrame is the first visual frame of its continuation chain |
michael@0 | 1311 | nsFrameContinuationState* contState; |
michael@0 | 1312 | nsIFrame* frame; |
michael@0 | 1313 | |
michael@0 | 1314 | frameState->mFrameCount = 1; |
michael@0 | 1315 | frameState->mFirstVisualFrame = aFrame; |
michael@0 | 1316 | |
michael@0 | 1317 | /** |
michael@0 | 1318 | * Traverse continuation chain of aFrame in both backward and forward |
michael@0 | 1319 | * directions while the frames are on this line. Count the frames and |
michael@0 | 1320 | * set their mFirstVisualFrame to aFrame. |
michael@0 | 1321 | */ |
michael@0 | 1322 | // Traverse continuation chain backward |
michael@0 | 1323 | for (frame = aFrame->GetPrevContinuation(); |
michael@0 | 1324 | frame && (contState = aContinuationStates->GetEntry(frame)); |
michael@0 | 1325 | frame = frame->GetPrevContinuation()) { |
michael@0 | 1326 | frameState->mFrameCount++; |
michael@0 | 1327 | contState->mFirstVisualFrame = aFrame; |
michael@0 | 1328 | } |
michael@0 | 1329 | frameState->mHasContOnPrevLines = (frame != nullptr); |
michael@0 | 1330 | |
michael@0 | 1331 | // Traverse continuation chain forward |
michael@0 | 1332 | for (frame = aFrame->GetNextContinuation(); |
michael@0 | 1333 | frame && (contState = aContinuationStates->GetEntry(frame)); |
michael@0 | 1334 | frame = frame->GetNextContinuation()) { |
michael@0 | 1335 | frameState->mFrameCount++; |
michael@0 | 1336 | contState->mFirstVisualFrame = aFrame; |
michael@0 | 1337 | } |
michael@0 | 1338 | frameState->mHasContOnNextLines = (frame != nullptr); |
michael@0 | 1339 | |
michael@0 | 1340 | aIsFirst = !frameState->mHasContOnPrevLines; |
michael@0 | 1341 | firstFrameState = frameState; |
michael@0 | 1342 | } else { |
michael@0 | 1343 | // aFrame is not the first visual frame of its continuation chain |
michael@0 | 1344 | aIsFirst = false; |
michael@0 | 1345 | firstFrameState = aContinuationStates->GetEntry(frameState->mFirstVisualFrame); |
michael@0 | 1346 | } |
michael@0 | 1347 | |
michael@0 | 1348 | aIsLast = (firstFrameState->mFrameCount == 1 && |
michael@0 | 1349 | !firstFrameState->mHasContOnNextLines); |
michael@0 | 1350 | |
michael@0 | 1351 | if ((aIsFirst || aIsLast) && |
michael@0 | 1352 | (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) { |
michael@0 | 1353 | // For ib splits, don't treat anything except the last part as |
michael@0 | 1354 | // endmost or anything except the first part as startmost. |
michael@0 | 1355 | // As an optimization, only get the first continuation once. |
michael@0 | 1356 | nsIFrame* firstContinuation = aFrame->FirstContinuation(); |
michael@0 | 1357 | if (firstContinuation->FrameIsNonLastInIBSplit()) { |
michael@0 | 1358 | // We are not endmost |
michael@0 | 1359 | aIsLast = false; |
michael@0 | 1360 | } |
michael@0 | 1361 | if (firstContinuation->FrameIsNonFirstInIBSplit()) { |
michael@0 | 1362 | // We are not startmost |
michael@0 | 1363 | aIsFirst = false; |
michael@0 | 1364 | } |
michael@0 | 1365 | } |
michael@0 | 1366 | |
michael@0 | 1367 | // Reduce number of remaining frames of the continuation chain on the line. |
michael@0 | 1368 | firstFrameState->mFrameCount--; |
michael@0 | 1369 | } |
michael@0 | 1370 | |
michael@0 | 1371 | void |
michael@0 | 1372 | nsBidiPresUtils::RepositionFrame(nsIFrame* aFrame, |
michael@0 | 1373 | bool aIsEvenLevel, |
michael@0 | 1374 | nscoord& aStart, |
michael@0 | 1375 | nsContinuationStates* aContinuationStates, |
michael@0 | 1376 | WritingMode aLineWM, |
michael@0 | 1377 | nscoord& aLineWidth) |
michael@0 | 1378 | { |
michael@0 | 1379 | if (!aFrame) |
michael@0 | 1380 | return; |
michael@0 | 1381 | |
michael@0 | 1382 | bool isFirst, isLast; |
michael@0 | 1383 | IsFirstOrLast(aFrame, |
michael@0 | 1384 | aContinuationStates, |
michael@0 | 1385 | isFirst /* out */, |
michael@0 | 1386 | isLast /* out */); |
michael@0 | 1387 | |
michael@0 | 1388 | WritingMode frameWM = aFrame->GetWritingMode(); |
michael@0 | 1389 | nsInlineFrame* testFrame = do_QueryFrame(aFrame); |
michael@0 | 1390 | |
michael@0 | 1391 | if (testFrame) { |
michael@0 | 1392 | aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET); |
michael@0 | 1393 | |
michael@0 | 1394 | if (isFirst) { |
michael@0 | 1395 | aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST); |
michael@0 | 1396 | } else { |
michael@0 | 1397 | aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST); |
michael@0 | 1398 | } |
michael@0 | 1399 | |
michael@0 | 1400 | if (isLast) { |
michael@0 | 1401 | aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST); |
michael@0 | 1402 | } else { |
michael@0 | 1403 | aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST); |
michael@0 | 1404 | } |
michael@0 | 1405 | } |
michael@0 | 1406 | // This method is called from nsBlockFrame::PlaceLine via the call to |
michael@0 | 1407 | // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines |
michael@0 | 1408 | // have been reflowed, which is required for GetUsedMargin/Border/Padding |
michael@0 | 1409 | LogicalMargin margin(frameWM, aFrame->GetUsedMargin()); |
michael@0 | 1410 | if (isFirst) { |
michael@0 | 1411 | aStart += margin.IStart(frameWM); |
michael@0 | 1412 | } |
michael@0 | 1413 | |
michael@0 | 1414 | nscoord start = aStart; |
michael@0 | 1415 | nscoord frameWidth = aFrame->GetSize().width; |
michael@0 | 1416 | |
michael@0 | 1417 | if (!IsBidiLeaf(aFrame)) |
michael@0 | 1418 | { |
michael@0 | 1419 | nscoord iCoord = 0; |
michael@0 | 1420 | LogicalMargin borderPadding(frameWM, aFrame->GetUsedBorderAndPadding()); |
michael@0 | 1421 | if (isFirst) { |
michael@0 | 1422 | iCoord += borderPadding.IStart(frameWM); |
michael@0 | 1423 | } |
michael@0 | 1424 | |
michael@0 | 1425 | // If the resolved direction of the container is different from the |
michael@0 | 1426 | // direction of the frame, we need to traverse the child list in reverse |
michael@0 | 1427 | // order, to make it O(n) we store the list locally and iterate the list |
michael@0 | 1428 | // in reverse |
michael@0 | 1429 | bool reverseOrder = aIsEvenLevel != frameWM.IsBidiLTR(); |
michael@0 | 1430 | nsTArray<nsIFrame*> childList; |
michael@0 | 1431 | nsIFrame *frame = aFrame->GetFirstPrincipalChild(); |
michael@0 | 1432 | if (frame && reverseOrder) { |
michael@0 | 1433 | childList.AppendElement((nsIFrame*)nullptr); |
michael@0 | 1434 | while (frame) { |
michael@0 | 1435 | childList.AppendElement(frame); |
michael@0 | 1436 | frame = frame->GetNextSibling(); |
michael@0 | 1437 | } |
michael@0 | 1438 | frame = childList[childList.Length() - 1]; |
michael@0 | 1439 | } |
michael@0 | 1440 | |
michael@0 | 1441 | // Reposition the child frames |
michael@0 | 1442 | int32_t index = 0; |
michael@0 | 1443 | while (frame) { |
michael@0 | 1444 | RepositionFrame(frame, |
michael@0 | 1445 | aIsEvenLevel, |
michael@0 | 1446 | iCoord, |
michael@0 | 1447 | aContinuationStates, |
michael@0 | 1448 | frameWM, |
michael@0 | 1449 | frameWidth); |
michael@0 | 1450 | index++; |
michael@0 | 1451 | frame = reverseOrder ? |
michael@0 | 1452 | childList[childList.Length() - index - 1] : |
michael@0 | 1453 | frame->GetNextSibling(); |
michael@0 | 1454 | } |
michael@0 | 1455 | |
michael@0 | 1456 | if (isLast) { |
michael@0 | 1457 | iCoord += borderPadding.IEnd(frameWM); |
michael@0 | 1458 | } |
michael@0 | 1459 | aStart += iCoord; |
michael@0 | 1460 | } else { |
michael@0 | 1461 | aStart += frameWidth; |
michael@0 | 1462 | } |
michael@0 | 1463 | |
michael@0 | 1464 | LogicalRect logicalRect(aLineWM, aFrame->GetRect(), aLineWidth); |
michael@0 | 1465 | logicalRect.IStart(aLineWM) = start; |
michael@0 | 1466 | logicalRect.ISize(aLineWM) = aStart - start; |
michael@0 | 1467 | aFrame->SetRect(aLineWM, logicalRect, aLineWidth); |
michael@0 | 1468 | |
michael@0 | 1469 | if (isLast) { |
michael@0 | 1470 | aStart += margin.IEnd(frameWM); |
michael@0 | 1471 | } |
michael@0 | 1472 | } |
michael@0 | 1473 | |
michael@0 | 1474 | void |
michael@0 | 1475 | nsBidiPresUtils::InitContinuationStates(nsIFrame* aFrame, |
michael@0 | 1476 | nsContinuationStates* aContinuationStates) |
michael@0 | 1477 | { |
michael@0 | 1478 | nsFrameContinuationState* state = aContinuationStates->PutEntry(aFrame); |
michael@0 | 1479 | state->mFirstVisualFrame = nullptr; |
michael@0 | 1480 | state->mFrameCount = 0; |
michael@0 | 1481 | |
michael@0 | 1482 | if (!IsBidiLeaf(aFrame)) { |
michael@0 | 1483 | // Continue for child frames |
michael@0 | 1484 | nsIFrame* frame; |
michael@0 | 1485 | for (frame = aFrame->GetFirstPrincipalChild(); |
michael@0 | 1486 | frame; |
michael@0 | 1487 | frame = frame->GetNextSibling()) { |
michael@0 | 1488 | InitContinuationStates(frame, |
michael@0 | 1489 | aContinuationStates); |
michael@0 | 1490 | } |
michael@0 | 1491 | } |
michael@0 | 1492 | } |
michael@0 | 1493 | |
michael@0 | 1494 | void |
michael@0 | 1495 | nsBidiPresUtils::RepositionInlineFrames(BidiLineData *aBld, |
michael@0 | 1496 | nsIFrame* aFirstChild, |
michael@0 | 1497 | WritingMode aLineWM, |
michael@0 | 1498 | nscoord& aLineWidth) |
michael@0 | 1499 | { |
michael@0 | 1500 | nscoord startSpace = 0; |
michael@0 | 1501 | |
michael@0 | 1502 | // This method is called from nsBlockFrame::PlaceLine via the call to |
michael@0 | 1503 | // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines |
michael@0 | 1504 | // have been reflowed, which is required for GetUsedMargin/Border/Padding |
michael@0 | 1505 | WritingMode frameWM = aFirstChild->GetWritingMode(); |
michael@0 | 1506 | LogicalMargin margin(frameWM, aFirstChild->GetUsedMargin()); |
michael@0 | 1507 | if (!aFirstChild->GetPrevContinuation() && |
michael@0 | 1508 | !aFirstChild->FrameIsNonFirstInIBSplit()) |
michael@0 | 1509 | startSpace = margin.IStart(frameWM); |
michael@0 | 1510 | |
michael@0 | 1511 | nscoord start = LogicalRect(aLineWM, aFirstChild->GetRect(), |
michael@0 | 1512 | aLineWidth).IStart(aLineWM) - startSpace; |
michael@0 | 1513 | nsIFrame* frame; |
michael@0 | 1514 | int32_t count = aBld->mVisualFrames.Length(); |
michael@0 | 1515 | int32_t index; |
michael@0 | 1516 | nsContinuationStates continuationStates; |
michael@0 | 1517 | |
michael@0 | 1518 | // Initialize continuation states to (nullptr, 0) for |
michael@0 | 1519 | // each frame on the line. |
michael@0 | 1520 | for (index = 0; index < count; index++) { |
michael@0 | 1521 | InitContinuationStates(aBld->VisualFrameAt(index), &continuationStates); |
michael@0 | 1522 | } |
michael@0 | 1523 | |
michael@0 | 1524 | // Reposition frames in visual order |
michael@0 | 1525 | int32_t step, limit; |
michael@0 | 1526 | if (aLineWM.IsBidiLTR()) { |
michael@0 | 1527 | index = 0; |
michael@0 | 1528 | step = 1; |
michael@0 | 1529 | limit = count; |
michael@0 | 1530 | } else { |
michael@0 | 1531 | index = count - 1; |
michael@0 | 1532 | step = -1; |
michael@0 | 1533 | limit = -1; |
michael@0 | 1534 | } |
michael@0 | 1535 | for (; index != limit; index += step) { |
michael@0 | 1536 | frame = aBld->VisualFrameAt(index); |
michael@0 | 1537 | RepositionFrame(frame, |
michael@0 | 1538 | !(aBld->mLevels[aBld->mIndexMap[index]] & 1), |
michael@0 | 1539 | start, |
michael@0 | 1540 | &continuationStates, |
michael@0 | 1541 | aLineWM, |
michael@0 | 1542 | aLineWidth); |
michael@0 | 1543 | } |
michael@0 | 1544 | } |
michael@0 | 1545 | |
michael@0 | 1546 | bool |
michael@0 | 1547 | nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine, |
michael@0 | 1548 | int32_t aNumFramesOnLine, |
michael@0 | 1549 | nsIFrame** aFirstVisual, |
michael@0 | 1550 | nsIFrame** aLastVisual) |
michael@0 | 1551 | { |
michael@0 | 1552 | BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); |
michael@0 | 1553 | int32_t count = bld.FrameCount(); |
michael@0 | 1554 | |
michael@0 | 1555 | if (aFirstVisual) { |
michael@0 | 1556 | *aFirstVisual = bld.VisualFrameAt(0); |
michael@0 | 1557 | } |
michael@0 | 1558 | if (aLastVisual) { |
michael@0 | 1559 | *aLastVisual = bld.VisualFrameAt(count-1); |
michael@0 | 1560 | } |
michael@0 | 1561 | |
michael@0 | 1562 | return bld.mIsReordered; |
michael@0 | 1563 | } |
michael@0 | 1564 | |
michael@0 | 1565 | nsIFrame* |
michael@0 | 1566 | nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame, |
michael@0 | 1567 | nsIFrame* aFirstFrameOnLine, |
michael@0 | 1568 | int32_t aNumFramesOnLine) |
michael@0 | 1569 | { |
michael@0 | 1570 | BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); |
michael@0 | 1571 | |
michael@0 | 1572 | int32_t count = bld.mVisualFrames.Length(); |
michael@0 | 1573 | |
michael@0 | 1574 | if (aFrame == nullptr && count) |
michael@0 | 1575 | return bld.VisualFrameAt(0); |
michael@0 | 1576 | |
michael@0 | 1577 | for (int32_t i = 0; i < count - 1; i++) { |
michael@0 | 1578 | if (bld.VisualFrameAt(i) == aFrame) { |
michael@0 | 1579 | return bld.VisualFrameAt(i+1); |
michael@0 | 1580 | } |
michael@0 | 1581 | } |
michael@0 | 1582 | |
michael@0 | 1583 | return nullptr; |
michael@0 | 1584 | } |
michael@0 | 1585 | |
michael@0 | 1586 | nsIFrame* |
michael@0 | 1587 | nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame* aFrame, |
michael@0 | 1588 | nsIFrame* aFirstFrameOnLine, |
michael@0 | 1589 | int32_t aNumFramesOnLine) |
michael@0 | 1590 | { |
michael@0 | 1591 | BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); |
michael@0 | 1592 | |
michael@0 | 1593 | int32_t count = bld.mVisualFrames.Length(); |
michael@0 | 1594 | |
michael@0 | 1595 | if (aFrame == nullptr && count) |
michael@0 | 1596 | return bld.VisualFrameAt(count-1); |
michael@0 | 1597 | |
michael@0 | 1598 | for (int32_t i = 1; i < count; i++) { |
michael@0 | 1599 | if (bld.VisualFrameAt(i) == aFrame) { |
michael@0 | 1600 | return bld.VisualFrameAt(i-1); |
michael@0 | 1601 | } |
michael@0 | 1602 | } |
michael@0 | 1603 | |
michael@0 | 1604 | return nullptr; |
michael@0 | 1605 | } |
michael@0 | 1606 | |
michael@0 | 1607 | inline nsresult |
michael@0 | 1608 | nsBidiPresUtils::EnsureBidiContinuation(nsIFrame* aFrame, |
michael@0 | 1609 | nsIFrame** aNewFrame, |
michael@0 | 1610 | int32_t& aFrameIndex, |
michael@0 | 1611 | int32_t aStart, |
michael@0 | 1612 | int32_t aEnd) |
michael@0 | 1613 | { |
michael@0 | 1614 | NS_PRECONDITION(aNewFrame, "null OUT ptr"); |
michael@0 | 1615 | NS_PRECONDITION(aFrame, "aFrame is null"); |
michael@0 | 1616 | |
michael@0 | 1617 | aFrame->AdjustOffsetsForBidi(aStart, aEnd); |
michael@0 | 1618 | return CreateContinuation(aFrame, aNewFrame, false); |
michael@0 | 1619 | } |
michael@0 | 1620 | |
michael@0 | 1621 | void |
michael@0 | 1622 | nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData *aBpd, |
michael@0 | 1623 | nsIFrame* aFrame, |
michael@0 | 1624 | int32_t aFirstIndex, |
michael@0 | 1625 | int32_t aLastIndex, |
michael@0 | 1626 | int32_t& aOffset) |
michael@0 | 1627 | { |
michael@0 | 1628 | FrameProperties props = aFrame->Properties(); |
michael@0 | 1629 | nsBidiLevel embeddingLevel = |
michael@0 | 1630 | (nsBidiLevel)NS_PTR_TO_INT32(props.Get(nsIFrame::EmbeddingLevelProperty())); |
michael@0 | 1631 | nsBidiLevel baseLevel = |
michael@0 | 1632 | (nsBidiLevel)NS_PTR_TO_INT32(props.Get(nsIFrame::BaseLevelProperty())); |
michael@0 | 1633 | uint8_t paragraphDepth = |
michael@0 | 1634 | NS_PTR_TO_INT32(props.Get(nsIFrame::ParagraphDepthProperty())); |
michael@0 | 1635 | |
michael@0 | 1636 | for (int32_t index = aFirstIndex + 1; index <= aLastIndex; index++) { |
michael@0 | 1637 | nsIFrame* frame = aBpd->FrameAt(index); |
michael@0 | 1638 | if (frame == NS_BIDI_CONTROL_FRAME) { |
michael@0 | 1639 | ++aOffset; |
michael@0 | 1640 | } |
michael@0 | 1641 | else { |
michael@0 | 1642 | // Make the frame and its continuation ancestors fluid, |
michael@0 | 1643 | // so they can be reused or deleted by normal reflow code |
michael@0 | 1644 | FrameProperties frameProps = frame->Properties(); |
michael@0 | 1645 | frameProps.Set(nsIFrame::EmbeddingLevelProperty(), |
michael@0 | 1646 | NS_INT32_TO_PTR(embeddingLevel)); |
michael@0 | 1647 | frameProps.Set(nsIFrame::BaseLevelProperty(), |
michael@0 | 1648 | NS_INT32_TO_PTR(baseLevel)); |
michael@0 | 1649 | frameProps.Set(nsIFrame::ParagraphDepthProperty(), |
michael@0 | 1650 | NS_INT32_TO_PTR(paragraphDepth)); |
michael@0 | 1651 | frame->AddStateBits(NS_FRAME_IS_BIDI); |
michael@0 | 1652 | while (frame) { |
michael@0 | 1653 | nsIFrame* prev = frame->GetPrevContinuation(); |
michael@0 | 1654 | if (prev) { |
michael@0 | 1655 | MakeContinuationFluid(prev, frame); |
michael@0 | 1656 | frame = frame->GetParent(); |
michael@0 | 1657 | } else { |
michael@0 | 1658 | break; |
michael@0 | 1659 | } |
michael@0 | 1660 | } |
michael@0 | 1661 | } |
michael@0 | 1662 | } |
michael@0 | 1663 | |
michael@0 | 1664 | // Make sure that the last continuation we made fluid does not itself have a |
michael@0 | 1665 | // fluid continuation (this can happen when re-resolving after dynamic changes |
michael@0 | 1666 | // to content) |
michael@0 | 1667 | nsIFrame* lastFrame = aBpd->FrameAt(aLastIndex); |
michael@0 | 1668 | MakeContinuationsNonFluidUpParentChain(lastFrame, lastFrame->GetNextInFlow()); |
michael@0 | 1669 | } |
michael@0 | 1670 | |
michael@0 | 1671 | nsresult |
michael@0 | 1672 | nsBidiPresUtils::FormatUnicodeText(nsPresContext* aPresContext, |
michael@0 | 1673 | char16_t* aText, |
michael@0 | 1674 | int32_t& aTextLength, |
michael@0 | 1675 | nsCharType aCharType, |
michael@0 | 1676 | bool aIsOddLevel) |
michael@0 | 1677 | { |
michael@0 | 1678 | nsresult rv = NS_OK; |
michael@0 | 1679 | // ahmed |
michael@0 | 1680 | //adjusted for correct numeral shaping |
michael@0 | 1681 | uint32_t bidiOptions = aPresContext->GetBidi(); |
michael@0 | 1682 | switch (GET_BIDI_OPTION_NUMERAL(bidiOptions)) { |
michael@0 | 1683 | |
michael@0 | 1684 | case IBMBIDI_NUMERAL_HINDI: |
michael@0 | 1685 | HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI); |
michael@0 | 1686 | break; |
michael@0 | 1687 | |
michael@0 | 1688 | case IBMBIDI_NUMERAL_ARABIC: |
michael@0 | 1689 | HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC); |
michael@0 | 1690 | break; |
michael@0 | 1691 | |
michael@0 | 1692 | case IBMBIDI_NUMERAL_PERSIAN: |
michael@0 | 1693 | HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_PERSIAN); |
michael@0 | 1694 | break; |
michael@0 | 1695 | |
michael@0 | 1696 | case IBMBIDI_NUMERAL_REGULAR: |
michael@0 | 1697 | |
michael@0 | 1698 | switch (aCharType) { |
michael@0 | 1699 | |
michael@0 | 1700 | case eCharType_EuropeanNumber: |
michael@0 | 1701 | HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC); |
michael@0 | 1702 | break; |
michael@0 | 1703 | |
michael@0 | 1704 | case eCharType_ArabicNumber: |
michael@0 | 1705 | HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI); |
michael@0 | 1706 | break; |
michael@0 | 1707 | |
michael@0 | 1708 | default: |
michael@0 | 1709 | break; |
michael@0 | 1710 | } |
michael@0 | 1711 | break; |
michael@0 | 1712 | |
michael@0 | 1713 | case IBMBIDI_NUMERAL_HINDICONTEXT: |
michael@0 | 1714 | if ( ( (GET_BIDI_OPTION_DIRECTION(bidiOptions)==IBMBIDI_TEXTDIRECTION_RTL) && (IS_ARABIC_DIGIT (aText[0])) ) || (eCharType_ArabicNumber == aCharType) ) |
michael@0 | 1715 | HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI); |
michael@0 | 1716 | else if (eCharType_EuropeanNumber == aCharType) |
michael@0 | 1717 | HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC); |
michael@0 | 1718 | break; |
michael@0 | 1719 | |
michael@0 | 1720 | case IBMBIDI_NUMERAL_PERSIANCONTEXT: |
michael@0 | 1721 | if ( ( (GET_BIDI_OPTION_DIRECTION(bidiOptions)==IBMBIDI_TEXTDIRECTION_RTL) && (IS_ARABIC_DIGIT (aText[0])) ) || (eCharType_ArabicNumber == aCharType) ) |
michael@0 | 1722 | HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_PERSIAN); |
michael@0 | 1723 | else if (eCharType_EuropeanNumber == aCharType) |
michael@0 | 1724 | HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC); |
michael@0 | 1725 | break; |
michael@0 | 1726 | |
michael@0 | 1727 | case IBMBIDI_NUMERAL_NOMINAL: |
michael@0 | 1728 | default: |
michael@0 | 1729 | break; |
michael@0 | 1730 | } |
michael@0 | 1731 | |
michael@0 | 1732 | StripBidiControlCharacters(aText, aTextLength); |
michael@0 | 1733 | return rv; |
michael@0 | 1734 | } |
michael@0 | 1735 | |
michael@0 | 1736 | void |
michael@0 | 1737 | nsBidiPresUtils::StripBidiControlCharacters(char16_t* aText, |
michael@0 | 1738 | int32_t& aTextLength) |
michael@0 | 1739 | { |
michael@0 | 1740 | if ( (nullptr == aText) || (aTextLength < 1) ) { |
michael@0 | 1741 | return; |
michael@0 | 1742 | } |
michael@0 | 1743 | |
michael@0 | 1744 | int32_t stripLen = 0; |
michael@0 | 1745 | |
michael@0 | 1746 | for (int32_t i = 0; i < aTextLength; i++) { |
michael@0 | 1747 | // XXX: This silently ignores surrogate characters. |
michael@0 | 1748 | // As of Unicode 4.0, all Bidi control characters are within the BMP. |
michael@0 | 1749 | if (IsBidiControl((uint32_t)aText[i])) { |
michael@0 | 1750 | ++stripLen; |
michael@0 | 1751 | } |
michael@0 | 1752 | else { |
michael@0 | 1753 | aText[i - stripLen] = aText[i]; |
michael@0 | 1754 | } |
michael@0 | 1755 | } |
michael@0 | 1756 | aTextLength -= stripLen; |
michael@0 | 1757 | } |
michael@0 | 1758 | |
michael@0 | 1759 | #if 0 // XXX: for the future use ??? |
michael@0 | 1760 | void |
michael@0 | 1761 | RemoveDiacritics(char16_t* aText, |
michael@0 | 1762 | int32_t& aTextLength) |
michael@0 | 1763 | { |
michael@0 | 1764 | if (aText && (aTextLength > 0) ) { |
michael@0 | 1765 | int32_t offset = 0; |
michael@0 | 1766 | |
michael@0 | 1767 | for (int32_t i = 0; i < aTextLength && aText[i]; i++) { |
michael@0 | 1768 | if (IS_BIDI_DIACRITIC(aText[i]) ) { |
michael@0 | 1769 | ++offset; |
michael@0 | 1770 | continue; |
michael@0 | 1771 | } |
michael@0 | 1772 | aText[i - offset] = aText[i]; |
michael@0 | 1773 | } |
michael@0 | 1774 | aTextLength = i - offset; |
michael@0 | 1775 | aText[aTextLength] = 0; |
michael@0 | 1776 | } |
michael@0 | 1777 | } |
michael@0 | 1778 | #endif |
michael@0 | 1779 | |
michael@0 | 1780 | void |
michael@0 | 1781 | nsBidiPresUtils::CalculateCharType(nsBidi* aBidiEngine, |
michael@0 | 1782 | const char16_t* aText, |
michael@0 | 1783 | int32_t& aOffset, |
michael@0 | 1784 | int32_t aCharTypeLimit, |
michael@0 | 1785 | int32_t& aRunLimit, |
michael@0 | 1786 | int32_t& aRunLength, |
michael@0 | 1787 | int32_t& aRunCount, |
michael@0 | 1788 | uint8_t& aCharType, |
michael@0 | 1789 | uint8_t& aPrevCharType) |
michael@0 | 1790 | |
michael@0 | 1791 | { |
michael@0 | 1792 | bool strongTypeFound = false; |
michael@0 | 1793 | int32_t offset; |
michael@0 | 1794 | nsCharType charType; |
michael@0 | 1795 | |
michael@0 | 1796 | aCharType = eCharType_OtherNeutral; |
michael@0 | 1797 | |
michael@0 | 1798 | for (offset = aOffset; offset < aCharTypeLimit; offset++) { |
michael@0 | 1799 | // Make sure we give RTL chartype to all characters that would be classified |
michael@0 | 1800 | // as Right-To-Left by a bidi platform. |
michael@0 | 1801 | // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.) |
michael@0 | 1802 | if (IS_HEBREW_CHAR(aText[offset]) ) { |
michael@0 | 1803 | charType = eCharType_RightToLeft; |
michael@0 | 1804 | } |
michael@0 | 1805 | else if (IS_ARABIC_ALPHABETIC(aText[offset]) ) { |
michael@0 | 1806 | charType = eCharType_RightToLeftArabic; |
michael@0 | 1807 | } |
michael@0 | 1808 | else { |
michael@0 | 1809 | aBidiEngine->GetCharTypeAt(offset, &charType); |
michael@0 | 1810 | } |
michael@0 | 1811 | |
michael@0 | 1812 | if (!CHARTYPE_IS_WEAK(charType) ) { |
michael@0 | 1813 | |
michael@0 | 1814 | if (strongTypeFound |
michael@0 | 1815 | && (charType != aPrevCharType) |
michael@0 | 1816 | && (CHARTYPE_IS_RTL(charType) || CHARTYPE_IS_RTL(aPrevCharType) ) ) { |
michael@0 | 1817 | // Stop at this point to ensure uni-directionality of the text |
michael@0 | 1818 | // (from platform's point of view). |
michael@0 | 1819 | // Also, don't mix Arabic and Hebrew content (since platform may |
michael@0 | 1820 | // provide BIDI support to one of them only). |
michael@0 | 1821 | aRunLength = offset - aOffset; |
michael@0 | 1822 | aRunLimit = offset; |
michael@0 | 1823 | ++aRunCount; |
michael@0 | 1824 | break; |
michael@0 | 1825 | } |
michael@0 | 1826 | |
michael@0 | 1827 | if ( (eCharType_RightToLeftArabic == aPrevCharType |
michael@0 | 1828 | || eCharType_ArabicNumber == aPrevCharType) |
michael@0 | 1829 | && eCharType_EuropeanNumber == charType) { |
michael@0 | 1830 | charType = eCharType_ArabicNumber; |
michael@0 | 1831 | } |
michael@0 | 1832 | |
michael@0 | 1833 | // Set PrevCharType to the last strong type in this frame |
michael@0 | 1834 | // (for correct numeric shaping) |
michael@0 | 1835 | aPrevCharType = charType; |
michael@0 | 1836 | |
michael@0 | 1837 | strongTypeFound = true; |
michael@0 | 1838 | aCharType = charType; |
michael@0 | 1839 | } |
michael@0 | 1840 | } |
michael@0 | 1841 | aOffset = offset; |
michael@0 | 1842 | } |
michael@0 | 1843 | |
michael@0 | 1844 | nsresult nsBidiPresUtils::ProcessText(const char16_t* aText, |
michael@0 | 1845 | int32_t aLength, |
michael@0 | 1846 | nsBidiLevel aBaseLevel, |
michael@0 | 1847 | nsPresContext* aPresContext, |
michael@0 | 1848 | BidiProcessor& aprocessor, |
michael@0 | 1849 | Mode aMode, |
michael@0 | 1850 | nsBidiPositionResolve* aPosResolve, |
michael@0 | 1851 | int32_t aPosResolveCount, |
michael@0 | 1852 | nscoord* aWidth, |
michael@0 | 1853 | nsBidi* aBidiEngine) |
michael@0 | 1854 | { |
michael@0 | 1855 | NS_ASSERTION((aPosResolve == nullptr) != (aPosResolveCount > 0), "Incorrect aPosResolve / aPosResolveCount arguments"); |
michael@0 | 1856 | |
michael@0 | 1857 | int32_t runCount; |
michael@0 | 1858 | |
michael@0 | 1859 | nsAutoString textBuffer(aText, aLength); |
michael@0 | 1860 | |
michael@0 | 1861 | nsresult rv = aBidiEngine->SetPara(aText, aLength, aBaseLevel, nullptr); |
michael@0 | 1862 | if (NS_FAILED(rv)) |
michael@0 | 1863 | return rv; |
michael@0 | 1864 | |
michael@0 | 1865 | rv = aBidiEngine->CountRuns(&runCount); |
michael@0 | 1866 | if (NS_FAILED(rv)) |
michael@0 | 1867 | return rv; |
michael@0 | 1868 | |
michael@0 | 1869 | nscoord xOffset = 0; |
michael@0 | 1870 | nscoord width, xEndRun = 0; |
michael@0 | 1871 | nscoord totalWidth = 0; |
michael@0 | 1872 | int32_t i, start, limit, length; |
michael@0 | 1873 | uint32_t visualStart = 0; |
michael@0 | 1874 | uint8_t charType; |
michael@0 | 1875 | uint8_t prevType = eCharType_LeftToRight; |
michael@0 | 1876 | nsBidiLevel level; |
michael@0 | 1877 | |
michael@0 | 1878 | for(int nPosResolve=0; nPosResolve < aPosResolveCount; ++nPosResolve) |
michael@0 | 1879 | { |
michael@0 | 1880 | aPosResolve[nPosResolve].visualIndex = kNotFound; |
michael@0 | 1881 | aPosResolve[nPosResolve].visualLeftTwips = kNotFound; |
michael@0 | 1882 | aPosResolve[nPosResolve].visualWidth = kNotFound; |
michael@0 | 1883 | } |
michael@0 | 1884 | |
michael@0 | 1885 | for (i = 0; i < runCount; i++) { |
michael@0 | 1886 | nsBidiDirection dir; |
michael@0 | 1887 | rv = aBidiEngine->GetVisualRun(i, &start, &length, &dir); |
michael@0 | 1888 | if (NS_FAILED(rv)) |
michael@0 | 1889 | return rv; |
michael@0 | 1890 | |
michael@0 | 1891 | rv = aBidiEngine->GetLogicalRun(start, &limit, &level); |
michael@0 | 1892 | if (NS_FAILED(rv)) |
michael@0 | 1893 | return rv; |
michael@0 | 1894 | |
michael@0 | 1895 | int32_t subRunLength = limit - start; |
michael@0 | 1896 | int32_t lineOffset = start; |
michael@0 | 1897 | int32_t typeLimit = std::min(limit, aLength); |
michael@0 | 1898 | int32_t subRunCount = 1; |
michael@0 | 1899 | int32_t subRunLimit = typeLimit; |
michael@0 | 1900 | |
michael@0 | 1901 | /* |
michael@0 | 1902 | * If |level| is even, i.e. the direction of the run is left-to-right, we |
michael@0 | 1903 | * render the subruns from left to right and increment the x-coordinate |
michael@0 | 1904 | * |xOffset| by the width of each subrun after rendering. |
michael@0 | 1905 | * |
michael@0 | 1906 | * If |level| is odd, i.e. the direction of the run is right-to-left, we |
michael@0 | 1907 | * render the subruns from right to left. We begin by incrementing |xOffset| by |
michael@0 | 1908 | * the width of the whole run, and then decrement it by the width of each |
michael@0 | 1909 | * subrun before rendering. After rendering all the subruns, we restore the |
michael@0 | 1910 | * x-coordinate of the end of the run for the start of the next run. |
michael@0 | 1911 | */ |
michael@0 | 1912 | |
michael@0 | 1913 | if (level & 1) { |
michael@0 | 1914 | aprocessor.SetText(aText + start, subRunLength, nsBidiDirection(level & 1)); |
michael@0 | 1915 | width = aprocessor.GetWidth(); |
michael@0 | 1916 | xOffset += width; |
michael@0 | 1917 | xEndRun = xOffset; |
michael@0 | 1918 | } |
michael@0 | 1919 | |
michael@0 | 1920 | while (subRunCount > 0) { |
michael@0 | 1921 | // CalculateCharType can increment subRunCount if the run |
michael@0 | 1922 | // contains mixed character types |
michael@0 | 1923 | CalculateCharType(aBidiEngine, aText, lineOffset, typeLimit, subRunLimit, subRunLength, subRunCount, charType, prevType); |
michael@0 | 1924 | |
michael@0 | 1925 | nsAutoString runVisualText; |
michael@0 | 1926 | runVisualText.Assign(aText + start, subRunLength); |
michael@0 | 1927 | if (int32_t(runVisualText.Length()) < subRunLength) |
michael@0 | 1928 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 1929 | FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), subRunLength, |
michael@0 | 1930 | (nsCharType)charType, level & 1); |
michael@0 | 1931 | |
michael@0 | 1932 | aprocessor.SetText(runVisualText.get(), subRunLength, nsBidiDirection(level & 1)); |
michael@0 | 1933 | width = aprocessor.GetWidth(); |
michael@0 | 1934 | totalWidth += width; |
michael@0 | 1935 | if (level & 1) { |
michael@0 | 1936 | xOffset -= width; |
michael@0 | 1937 | } |
michael@0 | 1938 | if (aMode == MODE_DRAW) { |
michael@0 | 1939 | aprocessor.DrawText(xOffset, width); |
michael@0 | 1940 | } |
michael@0 | 1941 | |
michael@0 | 1942 | /* |
michael@0 | 1943 | * The caller may request to calculate the visual position of one |
michael@0 | 1944 | * or more characters. |
michael@0 | 1945 | */ |
michael@0 | 1946 | for(int nPosResolve=0; nPosResolve<aPosResolveCount; ++nPosResolve) |
michael@0 | 1947 | { |
michael@0 | 1948 | nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve]; |
michael@0 | 1949 | /* |
michael@0 | 1950 | * Did we already resolve this position's visual metric? If so, skip. |
michael@0 | 1951 | */ |
michael@0 | 1952 | if (posResolve->visualLeftTwips != kNotFound) |
michael@0 | 1953 | continue; |
michael@0 | 1954 | |
michael@0 | 1955 | /* |
michael@0 | 1956 | * First find out if the logical position is within this run. |
michael@0 | 1957 | */ |
michael@0 | 1958 | if (start <= posResolve->logicalIndex && |
michael@0 | 1959 | start + subRunLength > posResolve->logicalIndex) { |
michael@0 | 1960 | /* |
michael@0 | 1961 | * If this run is only one character long, we have an easy case: |
michael@0 | 1962 | * the visual position is the x-coord of the start of the run |
michael@0 | 1963 | * less the x-coord of the start of the whole text. |
michael@0 | 1964 | */ |
michael@0 | 1965 | if (subRunLength == 1) { |
michael@0 | 1966 | posResolve->visualIndex = visualStart; |
michael@0 | 1967 | posResolve->visualLeftTwips = xOffset; |
michael@0 | 1968 | posResolve->visualWidth = width; |
michael@0 | 1969 | } |
michael@0 | 1970 | /* |
michael@0 | 1971 | * Otherwise, we need to measure the width of the run's part |
michael@0 | 1972 | * which is to the visual left of the index. |
michael@0 | 1973 | * In other words, the run is broken in two, around the logical index, |
michael@0 | 1974 | * and we measure the part which is visually left. |
michael@0 | 1975 | * If the run is right-to-left, this part will span from after the index |
michael@0 | 1976 | * up to the end of the run; if it is left-to-right, this part will span |
michael@0 | 1977 | * from the start of the run up to (and inclduing) the character before the index. |
michael@0 | 1978 | */ |
michael@0 | 1979 | else { |
michael@0 | 1980 | /* |
michael@0 | 1981 | * Here is a description of how the width of the current character |
michael@0 | 1982 | * (posResolve->visualWidth) is calculated: |
michael@0 | 1983 | * |
michael@0 | 1984 | * LTR (current char: "P"): |
michael@0 | 1985 | * S A M P L E (logical index: 3, visual index: 3) |
michael@0 | 1986 | * ^ (visualLeftPart) |
michael@0 | 1987 | * ^ (visualRightSide) |
michael@0 | 1988 | * visualLeftLength == 3 |
michael@0 | 1989 | * ^^^^^^ (subWidth) |
michael@0 | 1990 | * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide) |
michael@0 | 1991 | * ^^ (posResolve->visualWidth) |
michael@0 | 1992 | * |
michael@0 | 1993 | * RTL (current char: "M"): |
michael@0 | 1994 | * E L P M A S (logical index: 2, visual index: 3) |
michael@0 | 1995 | * ^ (visualLeftPart) |
michael@0 | 1996 | * ^ (visualRightSide) |
michael@0 | 1997 | * visualLeftLength == 3 |
michael@0 | 1998 | * ^^^^^^ (subWidth) |
michael@0 | 1999 | * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide) |
michael@0 | 2000 | * ^^ (posResolve->visualWidth) |
michael@0 | 2001 | */ |
michael@0 | 2002 | nscoord subWidth; |
michael@0 | 2003 | // The position in the text where this run's "left part" begins. |
michael@0 | 2004 | const char16_t* visualLeftPart, *visualRightSide; |
michael@0 | 2005 | if (level & 1) { |
michael@0 | 2006 | // One day, son, this could all be replaced with mBidiEngine.GetVisualIndex ... |
michael@0 | 2007 | posResolve->visualIndex = visualStart + (subRunLength - (posResolve->logicalIndex + 1 - start)); |
michael@0 | 2008 | // Skipping to the "left part". |
michael@0 | 2009 | visualLeftPart = aText + posResolve->logicalIndex + 1; |
michael@0 | 2010 | // Skipping to the right side of the current character |
michael@0 | 2011 | visualRightSide = visualLeftPart - 1; |
michael@0 | 2012 | } |
michael@0 | 2013 | else { |
michael@0 | 2014 | posResolve->visualIndex = visualStart + (posResolve->logicalIndex - start); |
michael@0 | 2015 | // Skipping to the "left part". |
michael@0 | 2016 | visualLeftPart = aText + start; |
michael@0 | 2017 | // In LTR mode this is the same as visualLeftPart |
michael@0 | 2018 | visualRightSide = visualLeftPart; |
michael@0 | 2019 | } |
michael@0 | 2020 | // The delta between the start of the run and the left part's end. |
michael@0 | 2021 | int32_t visualLeftLength = posResolve->visualIndex - visualStart; |
michael@0 | 2022 | aprocessor.SetText(visualLeftPart, visualLeftLength, nsBidiDirection(level & 1)); |
michael@0 | 2023 | subWidth = aprocessor.GetWidth(); |
michael@0 | 2024 | aprocessor.SetText(visualRightSide, visualLeftLength + 1, nsBidiDirection(level & 1)); |
michael@0 | 2025 | posResolve->visualLeftTwips = xOffset + subWidth; |
michael@0 | 2026 | posResolve->visualWidth = aprocessor.GetWidth() - subWidth; |
michael@0 | 2027 | } |
michael@0 | 2028 | } |
michael@0 | 2029 | } |
michael@0 | 2030 | |
michael@0 | 2031 | if (!(level & 1)) { |
michael@0 | 2032 | xOffset += width; |
michael@0 | 2033 | } |
michael@0 | 2034 | |
michael@0 | 2035 | --subRunCount; |
michael@0 | 2036 | start = lineOffset; |
michael@0 | 2037 | subRunLimit = typeLimit; |
michael@0 | 2038 | subRunLength = typeLimit - lineOffset; |
michael@0 | 2039 | } // while |
michael@0 | 2040 | if (level & 1) { |
michael@0 | 2041 | xOffset = xEndRun; |
michael@0 | 2042 | } |
michael@0 | 2043 | |
michael@0 | 2044 | visualStart += length; |
michael@0 | 2045 | } // for |
michael@0 | 2046 | |
michael@0 | 2047 | if (aWidth) { |
michael@0 | 2048 | *aWidth = totalWidth; |
michael@0 | 2049 | } |
michael@0 | 2050 | return NS_OK; |
michael@0 | 2051 | } |
michael@0 | 2052 | |
michael@0 | 2053 | class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor : public nsBidiPresUtils::BidiProcessor { |
michael@0 | 2054 | public: |
michael@0 | 2055 | nsIRenderingContextBidiProcessor(nsRenderingContext* aCtx, |
michael@0 | 2056 | nsRenderingContext* aTextRunConstructionContext, |
michael@0 | 2057 | const nsPoint& aPt) |
michael@0 | 2058 | : mCtx(aCtx), mTextRunConstructionContext(aTextRunConstructionContext), mPt(aPt) { } |
michael@0 | 2059 | |
michael@0 | 2060 | ~nsIRenderingContextBidiProcessor() |
michael@0 | 2061 | { |
michael@0 | 2062 | mCtx->SetTextRunRTL(false); |
michael@0 | 2063 | } |
michael@0 | 2064 | |
michael@0 | 2065 | virtual void SetText(const char16_t* aText, |
michael@0 | 2066 | int32_t aLength, |
michael@0 | 2067 | nsBidiDirection aDirection) MOZ_OVERRIDE |
michael@0 | 2068 | { |
michael@0 | 2069 | mTextRunConstructionContext->SetTextRunRTL(aDirection==NSBIDI_RTL); |
michael@0 | 2070 | mText = aText; |
michael@0 | 2071 | mLength = aLength; |
michael@0 | 2072 | } |
michael@0 | 2073 | |
michael@0 | 2074 | virtual nscoord GetWidth() MOZ_OVERRIDE |
michael@0 | 2075 | { |
michael@0 | 2076 | return mTextRunConstructionContext->GetWidth(mText, mLength); |
michael@0 | 2077 | } |
michael@0 | 2078 | |
michael@0 | 2079 | virtual void DrawText(nscoord aXOffset, |
michael@0 | 2080 | nscoord) MOZ_OVERRIDE |
michael@0 | 2081 | { |
michael@0 | 2082 | mCtx->FontMetrics()->DrawString(mText, mLength, mPt.x + aXOffset, mPt.y, |
michael@0 | 2083 | mCtx, mTextRunConstructionContext); |
michael@0 | 2084 | } |
michael@0 | 2085 | |
michael@0 | 2086 | private: |
michael@0 | 2087 | nsRenderingContext* mCtx; |
michael@0 | 2088 | nsRenderingContext* mTextRunConstructionContext; |
michael@0 | 2089 | nsPoint mPt; |
michael@0 | 2090 | const char16_t* mText; |
michael@0 | 2091 | int32_t mLength; |
michael@0 | 2092 | }; |
michael@0 | 2093 | |
michael@0 | 2094 | nsresult nsBidiPresUtils::ProcessTextForRenderingContext(const char16_t* aText, |
michael@0 | 2095 | int32_t aLength, |
michael@0 | 2096 | nsBidiLevel aBaseLevel, |
michael@0 | 2097 | nsPresContext* aPresContext, |
michael@0 | 2098 | nsRenderingContext& aRenderingContext, |
michael@0 | 2099 | nsRenderingContext& aTextRunConstructionContext, |
michael@0 | 2100 | Mode aMode, |
michael@0 | 2101 | nscoord aX, |
michael@0 | 2102 | nscoord aY, |
michael@0 | 2103 | nsBidiPositionResolve* aPosResolve, |
michael@0 | 2104 | int32_t aPosResolveCount, |
michael@0 | 2105 | nscoord* aWidth) |
michael@0 | 2106 | { |
michael@0 | 2107 | nsIRenderingContextBidiProcessor processor(&aRenderingContext, &aTextRunConstructionContext, nsPoint(aX, aY)); |
michael@0 | 2108 | nsBidi bidiEngine; |
michael@0 | 2109 | return ProcessText(aText, aLength, aBaseLevel, aPresContext, processor, |
michael@0 | 2110 | aMode, aPosResolve, aPosResolveCount, aWidth, &bidiEngine); |
michael@0 | 2111 | } |
michael@0 | 2112 | |
michael@0 | 2113 | /* static */ |
michael@0 | 2114 | void nsBidiPresUtils::WriteReverse(const char16_t* aSrc, |
michael@0 | 2115 | uint32_t aSrcLength, |
michael@0 | 2116 | char16_t* aDest) |
michael@0 | 2117 | { |
michael@0 | 2118 | char16_t* dest = aDest + aSrcLength; |
michael@0 | 2119 | mozilla::unicode::ClusterIterator iter(aSrc, aSrcLength); |
michael@0 | 2120 | |
michael@0 | 2121 | while (!iter.AtEnd()) { |
michael@0 | 2122 | iter.Next(); |
michael@0 | 2123 | for (const char16_t *cp = iter; cp > aSrc; ) { |
michael@0 | 2124 | // Here we rely on the fact that there are no non-BMP mirrored pairs |
michael@0 | 2125 | // currently in Unicode, so we don't need to look for surrogates |
michael@0 | 2126 | *--dest = mozilla::unicode::GetMirroredChar(*--cp); |
michael@0 | 2127 | } |
michael@0 | 2128 | aSrc = iter; |
michael@0 | 2129 | } |
michael@0 | 2130 | |
michael@0 | 2131 | NS_ASSERTION(dest == aDest, "Whole string not copied"); |
michael@0 | 2132 | } |
michael@0 | 2133 | |
michael@0 | 2134 | /* static */ |
michael@0 | 2135 | bool nsBidiPresUtils::WriteLogicalToVisual(const char16_t* aSrc, |
michael@0 | 2136 | uint32_t aSrcLength, |
michael@0 | 2137 | char16_t* aDest, |
michael@0 | 2138 | nsBidiLevel aBaseDirection, |
michael@0 | 2139 | nsBidi* aBidiEngine) |
michael@0 | 2140 | { |
michael@0 | 2141 | const char16_t* src = aSrc; |
michael@0 | 2142 | nsresult rv = aBidiEngine->SetPara(src, aSrcLength, aBaseDirection, nullptr); |
michael@0 | 2143 | if (NS_FAILED(rv)) { |
michael@0 | 2144 | return false; |
michael@0 | 2145 | } |
michael@0 | 2146 | |
michael@0 | 2147 | nsBidiDirection dir; |
michael@0 | 2148 | rv = aBidiEngine->GetDirection(&dir); |
michael@0 | 2149 | // NSBIDI_LTR returned from GetDirection means the whole text is LTR |
michael@0 | 2150 | if (NS_FAILED(rv) || dir == NSBIDI_LTR) { |
michael@0 | 2151 | return false; |
michael@0 | 2152 | } |
michael@0 | 2153 | |
michael@0 | 2154 | int32_t runCount; |
michael@0 | 2155 | rv = aBidiEngine->CountRuns(&runCount); |
michael@0 | 2156 | if (NS_FAILED(rv)) { |
michael@0 | 2157 | return false; |
michael@0 | 2158 | } |
michael@0 | 2159 | |
michael@0 | 2160 | int32_t runIndex, start, length; |
michael@0 | 2161 | char16_t* dest = aDest; |
michael@0 | 2162 | |
michael@0 | 2163 | for (runIndex = 0; runIndex < runCount; ++runIndex) { |
michael@0 | 2164 | rv = aBidiEngine->GetVisualRun(runIndex, &start, &length, &dir); |
michael@0 | 2165 | if (NS_FAILED(rv)) { |
michael@0 | 2166 | return false; |
michael@0 | 2167 | } |
michael@0 | 2168 | |
michael@0 | 2169 | src = aSrc + start; |
michael@0 | 2170 | |
michael@0 | 2171 | if (dir == NSBIDI_RTL) { |
michael@0 | 2172 | WriteReverse(src, length, dest); |
michael@0 | 2173 | dest += length; |
michael@0 | 2174 | } else { |
michael@0 | 2175 | do { |
michael@0 | 2176 | NS_ASSERTION(src >= aSrc && src < aSrc + aSrcLength, |
michael@0 | 2177 | "logical index out of range"); |
michael@0 | 2178 | NS_ASSERTION(dest < aDest + aSrcLength, "visual index out of range"); |
michael@0 | 2179 | *(dest++) = *(src++); |
michael@0 | 2180 | } while (--length); |
michael@0 | 2181 | } |
michael@0 | 2182 | } |
michael@0 | 2183 | |
michael@0 | 2184 | NS_ASSERTION(static_cast<uint32_t>(dest - aDest) == aSrcLength, |
michael@0 | 2185 | "whole string not copied"); |
michael@0 | 2186 | return true; |
michael@0 | 2187 | } |
michael@0 | 2188 | |
michael@0 | 2189 | void nsBidiPresUtils::CopyLogicalToVisual(const nsAString& aSource, |
michael@0 | 2190 | nsAString& aDest, |
michael@0 | 2191 | nsBidiLevel aBaseDirection, |
michael@0 | 2192 | bool aOverride) |
michael@0 | 2193 | { |
michael@0 | 2194 | aDest.SetLength(0); |
michael@0 | 2195 | uint32_t srcLength = aSource.Length(); |
michael@0 | 2196 | if (srcLength == 0) |
michael@0 | 2197 | return; |
michael@0 | 2198 | if (!aDest.SetLength(srcLength, fallible_t())) { |
michael@0 | 2199 | return; |
michael@0 | 2200 | } |
michael@0 | 2201 | nsAString::const_iterator fromBegin, fromEnd; |
michael@0 | 2202 | nsAString::iterator toBegin; |
michael@0 | 2203 | aSource.BeginReading(fromBegin); |
michael@0 | 2204 | aSource.EndReading(fromEnd); |
michael@0 | 2205 | aDest.BeginWriting(toBegin); |
michael@0 | 2206 | |
michael@0 | 2207 | if (aOverride) { |
michael@0 | 2208 | if (aBaseDirection == NSBIDI_RTL) { |
michael@0 | 2209 | // no need to use the converter -- just copy the string in reverse order |
michael@0 | 2210 | WriteReverse(fromBegin.get(), srcLength, toBegin.get()); |
michael@0 | 2211 | } else { |
michael@0 | 2212 | // if aOverride && aBaseDirection == NSBIDI_LTR, fall through to the |
michael@0 | 2213 | // simple copy |
michael@0 | 2214 | aDest.SetLength(0); |
michael@0 | 2215 | } |
michael@0 | 2216 | } else { |
michael@0 | 2217 | nsBidi bidiEngine; |
michael@0 | 2218 | if (!WriteLogicalToVisual(fromBegin.get(), srcLength, toBegin.get(), |
michael@0 | 2219 | aBaseDirection, &bidiEngine)) { |
michael@0 | 2220 | aDest.SetLength(0); |
michael@0 | 2221 | } |
michael@0 | 2222 | } |
michael@0 | 2223 | |
michael@0 | 2224 | if (aDest.IsEmpty()) { |
michael@0 | 2225 | // Either there was an error or the source is unidirectional |
michael@0 | 2226 | // left-to-right. In either case, just copy source to dest. |
michael@0 | 2227 | CopyUnicodeTo(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd), |
michael@0 | 2228 | aDest); |
michael@0 | 2229 | } |
michael@0 | 2230 | } |
michael@0 | 2231 | |
michael@0 | 2232 | /* static */ |
michael@0 | 2233 | nsBidiLevel |
michael@0 | 2234 | nsBidiPresUtils::BidiLevelFromStyle(nsStyleContext* aStyleContext) |
michael@0 | 2235 | { |
michael@0 | 2236 | if (aStyleContext->StyleTextReset()->mUnicodeBidi & |
michael@0 | 2237 | NS_STYLE_UNICODE_BIDI_PLAINTEXT) { |
michael@0 | 2238 | return NSBIDI_DEFAULT_LTR; |
michael@0 | 2239 | } |
michael@0 | 2240 | |
michael@0 | 2241 | if (aStyleContext->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { |
michael@0 | 2242 | return NSBIDI_RTL; |
michael@0 | 2243 | } |
michael@0 | 2244 | |
michael@0 | 2245 | return NSBIDI_LTR; |
michael@0 | 2246 | } |