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