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