1 | /* |
2 | * Copyright (C) 2000 Lars Knoll (knoll@kde.org) |
3 | * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2009, 2010 Apple Inc. All right reserved. |
4 | * Copyright (C) 2010 Google Inc. All rights reserved. |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Library General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Library General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Library General Public License |
17 | * along with this library; see the file COPYING.LIB. If not, write to |
18 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
19 | * Boston, MA 02110-1301, USA. |
20 | * |
21 | */ |
22 | |
23 | #pragma once |
24 | |
25 | #include "BidiRun.h" |
26 | #include "RenderBlockFlow.h" |
27 | #include "RenderChildIterator.h" |
28 | #include "RenderInline.h" |
29 | #include "RenderText.h" |
30 | #include <wtf/StdLibExtras.h> |
31 | |
32 | namespace WebCore { |
33 | |
34 | struct BidiIsolatedRun { |
35 | BidiIsolatedRun(RenderObject& object, unsigned position, RenderElement& root, BidiRun& runToReplace) |
36 | : object(object) |
37 | , root(root) |
38 | , runToReplace(runToReplace) |
39 | , position(position) |
40 | { |
41 | } |
42 | |
43 | RenderObject& object; |
44 | RenderElement& root; |
45 | BidiRun& runToReplace; |
46 | unsigned position; |
47 | }; |
48 | |
49 | // This class is used to RenderInline subtrees, stepping by character within the |
50 | // text children. InlineIterator will use bidiNext to find the next RenderText |
51 | // optionally notifying a BidiResolver every time it steps into/out of a RenderInline. |
52 | class InlineIterator { |
53 | public: |
54 | InlineIterator() |
55 | { |
56 | } |
57 | |
58 | InlineIterator(RenderElement* root, RenderObject* o, unsigned p) |
59 | : m_root(root) |
60 | , m_renderer(o) |
61 | , m_pos(p) |
62 | , m_refersToEndOfPreviousNode(false) |
63 | { |
64 | } |
65 | |
66 | void clear() |
67 | { |
68 | setRenderer(nullptr); |
69 | setOffset(0); |
70 | setNextBreakablePosition(std::numeric_limits<unsigned>::max()); |
71 | } |
72 | void moveToStartOf(RenderObject& object) |
73 | { |
74 | moveTo(object, 0); |
75 | } |
76 | |
77 | void moveTo(RenderObject& object, unsigned offset, Optional<unsigned> nextBreak = Optional<unsigned>()) |
78 | { |
79 | setRenderer(&object); |
80 | setOffset(offset); |
81 | setNextBreakablePosition(nextBreak); |
82 | } |
83 | |
84 | RenderObject* renderer() const { return m_renderer; } |
85 | void setRenderer(RenderObject* renderer) { m_renderer = renderer; } |
86 | unsigned offset() const { return m_pos; } |
87 | void setOffset(unsigned position); |
88 | RenderElement* root() const { return m_root; } |
89 | Optional<unsigned> nextBreakablePosition() const { return m_nextBreakablePosition; } |
90 | void setNextBreakablePosition(Optional<unsigned> position) { m_nextBreakablePosition = position; } |
91 | bool refersToEndOfPreviousNode() const { return m_refersToEndOfPreviousNode; } |
92 | void setRefersToEndOfPreviousNode(); |
93 | |
94 | void fastIncrementInTextNode(); |
95 | void increment(InlineBidiResolver* = nullptr); |
96 | void fastDecrement(); |
97 | bool atEnd() const; |
98 | |
99 | bool atTextParagraphSeparator() const |
100 | { |
101 | return is<RenderText>(m_renderer) && m_renderer->preservesNewline() && downcast<RenderText>(*m_renderer).characterAt(m_pos) == '\n'; |
102 | } |
103 | |
104 | bool atParagraphSeparator() const |
105 | { |
106 | return (m_renderer && m_renderer->isBR()) || atTextParagraphSeparator(); |
107 | } |
108 | |
109 | UChar current() const; |
110 | UChar previousInSameNode() const; |
111 | ALWAYS_INLINE UCharDirection direction() const; |
112 | |
113 | private: |
114 | UChar characterAt(unsigned) const; |
115 | |
116 | UCharDirection surrogateTextDirection(UChar currentCodeUnit) const; |
117 | |
118 | RenderElement* m_root { nullptr }; |
119 | RenderObject* m_renderer { nullptr }; |
120 | |
121 | Optional<unsigned> m_nextBreakablePosition; |
122 | unsigned m_pos { 0 }; |
123 | |
124 | // There are a couple places where we want to decrement an InlineIterator. |
125 | // Usually this take the form of decrementing m_pos; however, m_pos might be 0. |
126 | // However, we shouldn't ever need to decrement an InlineIterator more than |
127 | // once, so rather than implementing a decrement() function which traverses |
128 | // nodes, we can simply keep track of this state and handle it. |
129 | bool m_refersToEndOfPreviousNode { false }; |
130 | }; |
131 | |
132 | inline bool operator==(const InlineIterator& it1, const InlineIterator& it2) |
133 | { |
134 | return it1.offset() == it2.offset() && it1.renderer() == it2.renderer(); |
135 | } |
136 | |
137 | inline bool operator!=(const InlineIterator& it1, const InlineIterator& it2) |
138 | { |
139 | return it1.offset() != it2.offset() || it1.renderer() != it2.renderer(); |
140 | } |
141 | |
142 | static inline UCharDirection embedCharFromDirection(TextDirection direction, EUnicodeBidi unicodeBidi) |
143 | { |
144 | if (unicodeBidi == Embed) |
145 | return direction == TextDirection::RTL ? U_RIGHT_TO_LEFT_EMBEDDING : U_LEFT_TO_RIGHT_EMBEDDING; |
146 | return direction == TextDirection::RTL ? U_RIGHT_TO_LEFT_OVERRIDE : U_LEFT_TO_RIGHT_OVERRIDE; |
147 | } |
148 | |
149 | template <class Observer> |
150 | static inline void notifyObserverEnteredObject(Observer* observer, RenderObject* object) |
151 | { |
152 | if (!observer || !object || !object->isRenderInline()) |
153 | return; |
154 | |
155 | const RenderStyle& style = object->style(); |
156 | EUnicodeBidi unicodeBidi = style.unicodeBidi(); |
157 | if (unicodeBidi == UBNormal) { |
158 | // http://dev.w3.org/csswg/css3-writing-modes/#unicode-bidi |
159 | // "The element does not open an additional level of embedding with respect to the bidirectional algorithm." |
160 | // Thus we ignore any possible dir= attribute on the span. |
161 | return; |
162 | } |
163 | if (isIsolated(unicodeBidi)) { |
164 | // Make sure that explicit embeddings are committed before we enter the isolated content. |
165 | observer->commitExplicitEmbedding(); |
166 | observer->enterIsolate(); |
167 | // Embedding/Override characters implied by dir= will be handled when |
168 | // we process the isolated span, not when laying out the "parent" run. |
169 | return; |
170 | } |
171 | |
172 | if (!observer->inIsolate()) |
173 | observer->embed(embedCharFromDirection(style.direction(), unicodeBidi), FromStyleOrDOM); |
174 | } |
175 | |
176 | template <class Observer> |
177 | static inline void notifyObserverWillExitObject(Observer* observer, RenderObject* object) |
178 | { |
179 | if (!observer || !object || !object->isRenderInline()) |
180 | return; |
181 | |
182 | EUnicodeBidi unicodeBidi = object->style().unicodeBidi(); |
183 | if (unicodeBidi == UBNormal) |
184 | return; // Nothing to do for unicode-bidi: normal |
185 | if (isIsolated(unicodeBidi)) { |
186 | observer->exitIsolate(); |
187 | return; |
188 | } |
189 | |
190 | // Otherwise we pop any embed/override character we added when we opened this tag. |
191 | if (!observer->inIsolate()) |
192 | observer->embed(U_POP_DIRECTIONAL_FORMAT, FromStyleOrDOM); |
193 | } |
194 | |
195 | static inline bool isIteratorTarget(RenderObject* object) |
196 | { |
197 | ASSERT(object); // The iterator will of course return 0, but its not an expected argument to this function. |
198 | return object->isTextOrLineBreak() || object->isFloating() || object->isOutOfFlowPositioned() || object->isReplaced(); |
199 | } |
200 | |
201 | // This enum is only used for bidiNextShared() |
202 | enum EmptyInlineBehavior { |
203 | SkipEmptyInlines, |
204 | IncludeEmptyInlines, |
205 | }; |
206 | |
207 | static bool isEmptyInline(const RenderInline& renderer) |
208 | { |
209 | for (auto& current : childrenOfType<RenderObject>(renderer)) { |
210 | if (current.isFloatingOrOutOfFlowPositioned()) |
211 | continue; |
212 | if (is<RenderText>(current)) { |
213 | if (!downcast<RenderText>(current).isAllCollapsibleWhitespace()) |
214 | return false; |
215 | continue; |
216 | } |
217 | if (!is<RenderInline>(current) || !isEmptyInline(downcast<RenderInline>(current))) |
218 | return false; |
219 | } |
220 | return true; |
221 | } |
222 | |
223 | // FIXME: This function is misleadingly named. It has little to do with bidi. |
224 | // This function will iterate over inlines within a block, optionally notifying |
225 | // a bidi resolver as it enters/exits inlines (so it can push/pop embedding levels). |
226 | template <class Observer> |
227 | static inline RenderObject* bidiNextShared(RenderElement& root, RenderObject* current, Observer* observer = nullptr, EmptyInlineBehavior emptyInlineBehavior = SkipEmptyInlines, bool* endOfInlinePtr = nullptr) |
228 | { |
229 | RenderObject* next = nullptr; |
230 | // oldEndOfInline denotes if when we last stopped iterating if we were at the end of an inline. |
231 | bool oldEndOfInline = endOfInlinePtr ? *endOfInlinePtr : false; |
232 | bool endOfInline = false; |
233 | |
234 | while (current) { |
235 | next = nullptr; |
236 | if (!oldEndOfInline && !isIteratorTarget(current)) { |
237 | next = downcast<RenderElement>(*current).firstChild(); |
238 | notifyObserverEnteredObject(observer, next); |
239 | } |
240 | |
241 | // We hit this when either current has no children, or when current is not a renderer we care about. |
242 | if (!next) { |
243 | // If it is a renderer we care about, and we're doing our inline-walk, return it. |
244 | if (emptyInlineBehavior == IncludeEmptyInlines && !oldEndOfInline && is<RenderInline>(*current)) { |
245 | next = current; |
246 | endOfInline = true; |
247 | break; |
248 | } |
249 | |
250 | while (current && current != &root) { |
251 | notifyObserverWillExitObject(observer, current); |
252 | |
253 | next = current->nextSibling(); |
254 | if (next) { |
255 | notifyObserverEnteredObject(observer, next); |
256 | break; |
257 | } |
258 | |
259 | current = current->parent(); |
260 | if (emptyInlineBehavior == IncludeEmptyInlines && current && current != &root && is<RenderInline>(*current)) { |
261 | next = current; |
262 | endOfInline = true; |
263 | break; |
264 | } |
265 | } |
266 | } |
267 | |
268 | if (!next) |
269 | break; |
270 | |
271 | if (isIteratorTarget(next) |
272 | || (is<RenderInline>(*next) && (emptyInlineBehavior == IncludeEmptyInlines || isEmptyInline(downcast<RenderInline>(*next))))) |
273 | break; |
274 | current = next; |
275 | } |
276 | |
277 | if (endOfInlinePtr) |
278 | *endOfInlinePtr = endOfInline; |
279 | |
280 | return next; |
281 | } |
282 | |
283 | template <class Observer> |
284 | static inline RenderObject* bidiNextSkippingEmptyInlines(RenderElement& root, RenderObject* current, Observer* observer) |
285 | { |
286 | // The SkipEmptyInlines callers never care about endOfInlinePtr. |
287 | return bidiNextShared(root, current, observer, SkipEmptyInlines); |
288 | } |
289 | |
290 | // This makes callers cleaner as they don't have to specify a type for the observer when not providing one. |
291 | static inline RenderObject* bidiNextSkippingEmptyInlines(RenderElement& root, RenderObject* current) |
292 | { |
293 | InlineBidiResolver* observer = nullptr; |
294 | return bidiNextSkippingEmptyInlines(root, current, observer); |
295 | } |
296 | |
297 | static inline RenderObject* bidiNextIncludingEmptyInlines(RenderElement& root, RenderObject* current, bool* endOfInlinePtr = nullptr) |
298 | { |
299 | InlineBidiResolver* observer = nullptr; // Callers who include empty inlines, never use an observer. |
300 | return bidiNextShared(root, current, observer, IncludeEmptyInlines, endOfInlinePtr); |
301 | } |
302 | |
303 | static inline RenderObject* bidiFirstSkippingEmptyInlines(RenderElement& root, InlineBidiResolver* resolver = nullptr) |
304 | { |
305 | RenderObject* renderer = root.firstChild(); |
306 | if (!renderer) |
307 | return nullptr; |
308 | |
309 | if (is<RenderInline>(*renderer)) { |
310 | notifyObserverEnteredObject(resolver, renderer); |
311 | if (!isEmptyInline(downcast<RenderInline>(*renderer))) |
312 | renderer = bidiNextSkippingEmptyInlines(root, renderer, resolver); |
313 | else { |
314 | // Never skip empty inlines. |
315 | if (resolver) |
316 | resolver->commitExplicitEmbedding(); |
317 | return renderer; |
318 | } |
319 | } |
320 | |
321 | // FIXME: Unify this with the bidiNext call above. |
322 | if (renderer && !isIteratorTarget(renderer)) |
323 | renderer = bidiNextSkippingEmptyInlines(root, renderer, resolver); |
324 | |
325 | if (resolver) |
326 | resolver->commitExplicitEmbedding(); |
327 | return renderer; |
328 | } |
329 | |
330 | // FIXME: This method needs to be renamed when bidiNext finds a good name. |
331 | static inline RenderObject* bidiFirstIncludingEmptyInlines(RenderElement& root) |
332 | { |
333 | RenderObject* o = root.firstChild(); |
334 | // If either there are no children to walk, or the first one is correct |
335 | // then just return it. |
336 | if (!o || o->isRenderInline() || isIteratorTarget(o)) |
337 | return o; |
338 | |
339 | return bidiNextIncludingEmptyInlines(root, o); |
340 | } |
341 | |
342 | inline void InlineIterator::fastIncrementInTextNode() |
343 | { |
344 | ASSERT(m_renderer); |
345 | ASSERT(m_pos <= downcast<RenderText>(*m_renderer).text().length()); |
346 | ++m_pos; |
347 | } |
348 | |
349 | inline void InlineIterator::setOffset(unsigned position) |
350 | { |
351 | ASSERT(position <= UINT_MAX - 10); // Sanity check |
352 | m_pos = position; |
353 | } |
354 | |
355 | inline void InlineIterator::setRefersToEndOfPreviousNode() |
356 | { |
357 | ASSERT(!m_pos); |
358 | ASSERT(!m_refersToEndOfPreviousNode); |
359 | m_refersToEndOfPreviousNode = true; |
360 | } |
361 | |
362 | // FIXME: This is used by RenderBlock for simplified layout, and has nothing to do with bidi |
363 | // it shouldn't use functions called bidiFirst and bidiNext. |
364 | class InlineWalker { |
365 | public: |
366 | InlineWalker(RenderElement& root) |
367 | : m_root(root) |
368 | , m_current(nullptr) |
369 | , m_atEndOfInline(false) |
370 | { |
371 | // FIXME: This class should be taught how to do the SkipEmptyInlines codepath as well. |
372 | m_current = bidiFirstIncludingEmptyInlines(m_root); |
373 | } |
374 | |
375 | RenderElement& root() { return m_root; } |
376 | RenderObject* current() { return m_current; } |
377 | |
378 | bool atEndOfInline() { return m_atEndOfInline; } |
379 | bool atEnd() const { return !m_current; } |
380 | |
381 | RenderObject* advance() |
382 | { |
383 | // FIXME: Support SkipEmptyInlines and observer parameters. |
384 | m_current = bidiNextIncludingEmptyInlines(m_root, m_current, &m_atEndOfInline); |
385 | return m_current; |
386 | } |
387 | private: |
388 | RenderElement& m_root; |
389 | RenderObject* m_current; |
390 | bool m_atEndOfInline; |
391 | }; |
392 | |
393 | inline void InlineIterator::increment(InlineBidiResolver* resolver) |
394 | { |
395 | if (!m_renderer) |
396 | return; |
397 | if (is<RenderText>(*m_renderer)) { |
398 | fastIncrementInTextNode(); |
399 | if (m_pos < downcast<RenderText>(*m_renderer).text().length()) |
400 | return; |
401 | } |
402 | // bidiNext can return nullptr |
403 | RenderObject* bidiNext = bidiNextSkippingEmptyInlines(*m_root, m_renderer, resolver); |
404 | if (bidiNext) |
405 | moveToStartOf(*bidiNext); |
406 | else |
407 | clear(); |
408 | } |
409 | |
410 | inline void InlineIterator::fastDecrement() |
411 | { |
412 | ASSERT(!refersToEndOfPreviousNode()); |
413 | if (m_pos) |
414 | setOffset(m_pos - 1); |
415 | else |
416 | setRefersToEndOfPreviousNode(); |
417 | } |
418 | |
419 | inline bool InlineIterator::atEnd() const |
420 | { |
421 | return !m_renderer; |
422 | } |
423 | |
424 | inline UChar InlineIterator::characterAt(unsigned index) const |
425 | { |
426 | if (!is<RenderText>(m_renderer)) |
427 | return 0; |
428 | |
429 | return downcast<RenderText>(*m_renderer).characterAt(index); |
430 | } |
431 | |
432 | inline UChar InlineIterator::current() const |
433 | { |
434 | return characterAt(m_pos); |
435 | } |
436 | |
437 | inline UChar InlineIterator::previousInSameNode() const |
438 | { |
439 | return characterAt(m_pos - 1); |
440 | } |
441 | |
442 | ALWAYS_INLINE UCharDirection InlineIterator::direction() const |
443 | { |
444 | if (UNLIKELY(!m_renderer)) |
445 | return U_OTHER_NEUTRAL; |
446 | |
447 | if (LIKELY(is<RenderText>(*m_renderer))) { |
448 | UChar codeUnit = downcast<RenderText>(*m_renderer).characterAt(m_pos); |
449 | if (LIKELY(U16_IS_SINGLE(codeUnit))) |
450 | return u_charDirection(codeUnit); |
451 | return surrogateTextDirection(codeUnit); |
452 | } |
453 | |
454 | if (m_renderer->isListMarker()) |
455 | return m_renderer->style().isLeftToRightDirection() ? U_LEFT_TO_RIGHT : U_RIGHT_TO_LEFT; |
456 | |
457 | return U_OTHER_NEUTRAL; |
458 | } |
459 | |
460 | template<> |
461 | inline void InlineBidiResolver::incrementInternal() |
462 | { |
463 | m_current.increment(this); |
464 | } |
465 | |
466 | static inline bool isIsolatedInline(RenderObject& object) |
467 | { |
468 | return object.isRenderInline() && isIsolated(object.style().unicodeBidi()); |
469 | } |
470 | |
471 | static inline RenderObject* highestContainingIsolateWithinRoot(RenderObject& initialObject, RenderObject* root) |
472 | { |
473 | RenderObject* containingIsolateObject = nullptr; |
474 | for (RenderObject* object = &initialObject; object && object != root; object = object->parent()) { |
475 | if (isIsolatedInline(*object)) |
476 | containingIsolateObject = object; |
477 | } |
478 | return containingIsolateObject; |
479 | } |
480 | |
481 | static inline unsigned numberOfIsolateAncestors(const InlineIterator& iter) |
482 | { |
483 | unsigned count = 0; |
484 | typedef RenderObject* RenderObjectPtr; |
485 | for (RenderObjectPtr object = iter.renderer(), root = iter.root(); object && object != root; object = object->parent()) { |
486 | if (isIsolatedInline(*object)) |
487 | count++; |
488 | } |
489 | return count; |
490 | } |
491 | |
492 | // FIXME: This belongs on InlineBidiResolver, except it's a template specialization |
493 | // of BidiResolver which knows nothing about RenderObjects. |
494 | static inline void addPlaceholderRunForIsolatedInline(InlineBidiResolver& resolver, RenderObject& obj, unsigned pos, RenderElement& root) |
495 | { |
496 | std::unique_ptr<BidiRun> isolatedRun = std::make_unique<BidiRun>(pos, pos, obj, resolver.context(), resolver.dir()); |
497 | // FIXME: isolatedRuns() could be a hash of object->run and then we could cheaply |
498 | // ASSERT here that we didn't create multiple objects for the same inline. |
499 | resolver.setWhitespaceCollapsingTransitionForIsolatedRun(*isolatedRun, resolver.whitespaceCollapsingState().currentTransition()); |
500 | resolver.isolatedRuns().append(BidiIsolatedRun(obj, pos, root, *isolatedRun)); |
501 | resolver.runs().appendRun(WTFMove(isolatedRun)); |
502 | } |
503 | |
504 | class IsolateTracker { |
505 | public: |
506 | explicit IsolateTracker(unsigned nestedIsolateCount) |
507 | : m_nestedIsolateCount(nestedIsolateCount) |
508 | , m_haveAddedFakeRunForRootIsolate(false) |
509 | { |
510 | } |
511 | |
512 | void enterIsolate() { m_nestedIsolateCount++; } |
513 | void exitIsolate() |
514 | { |
515 | ASSERT(m_nestedIsolateCount >= 1); |
516 | m_nestedIsolateCount--; |
517 | if (!inIsolate()) |
518 | m_haveAddedFakeRunForRootIsolate = false; |
519 | } |
520 | bool inIsolate() const { return m_nestedIsolateCount; } |
521 | |
522 | // We don't care if we encounter bidi directional overrides. |
523 | void embed(UCharDirection, BidiEmbeddingSource) { } |
524 | void commitExplicitEmbedding() { } |
525 | |
526 | void addFakeRunIfNecessary(RenderObject& obj, unsigned pos, unsigned end, RenderElement& root, InlineBidiResolver& resolver) |
527 | { |
528 | // We only need to add a fake run for a given isolated span once during each call to createBidiRunsForLine. |
529 | // We'll be called for every span inside the isolated span so we just ignore subsequent calls. |
530 | // We also avoid creating a fake run until we hit a child that warrants one, e.g. we skip floats. |
531 | if (RenderBlock::shouldSkipCreatingRunsForObject(obj)) |
532 | return; |
533 | if (!m_haveAddedFakeRunForRootIsolate) { |
534 | // obj and pos together denote a single position in the inline, from which the parsing of the isolate will start. |
535 | // We don't need to mark the end of the run because this is implicit: it is either endOfLine or the end of the |
536 | // isolate, when we call createBidiRunsForLine it will stop at whichever comes first. |
537 | addPlaceholderRunForIsolatedInline(resolver, obj, pos, root); |
538 | } |
539 | m_haveAddedFakeRunForRootIsolate = true; |
540 | RenderBlockFlow::appendRunsForObject(nullptr, pos, end, obj, resolver); |
541 | } |
542 | |
543 | private: |
544 | unsigned m_nestedIsolateCount; |
545 | bool m_haveAddedFakeRunForRootIsolate; |
546 | }; |
547 | |
548 | template<> |
549 | inline void InlineBidiResolver::appendRunInternal() |
550 | { |
551 | if (!m_emptyRun && !m_eor.atEnd() && !m_reachedEndOfLine) { |
552 | // Keep track of when we enter/leave "unicode-bidi: isolate" inlines. |
553 | // Initialize our state depending on if we're starting in the middle of such an inline. |
554 | // FIXME: Could this initialize from this->inIsolate() instead of walking up the render tree? |
555 | IsolateTracker isolateTracker(numberOfIsolateAncestors(m_sor)); |
556 | int start = m_sor.offset(); |
557 | RenderObject* obj = m_sor.renderer(); |
558 | while (obj && obj != m_eor.renderer() && obj != endOfLine.renderer()) { |
559 | if (isolateTracker.inIsolate()) |
560 | isolateTracker.addFakeRunIfNecessary(*obj, start, obj->length(), *m_sor.root(), *this); |
561 | else |
562 | RenderBlockFlow::appendRunsForObject(&m_runs, start, obj->length(), *obj, *this); |
563 | // FIXME: start/obj should be an InlineIterator instead of two separate variables. |
564 | start = 0; |
565 | obj = bidiNextSkippingEmptyInlines(*m_sor.root(), obj, &isolateTracker); |
566 | } |
567 | if (obj) { |
568 | unsigned pos = obj == m_eor.renderer() ? m_eor.offset() : UINT_MAX; |
569 | if (obj == endOfLine.renderer() && endOfLine.offset() <= pos) { |
570 | m_reachedEndOfLine = true; |
571 | pos = endOfLine.offset(); |
572 | } |
573 | // It's OK to add runs for zero-length RenderObjects, just don't make the run larger than it should be |
574 | int end = obj->length() ? pos + 1 : 0; |
575 | if (isolateTracker.inIsolate()) |
576 | isolateTracker.addFakeRunIfNecessary(*obj, start, obj->length(), *m_sor.root(), *this); |
577 | else |
578 | RenderBlockFlow::appendRunsForObject(&m_runs, start, end, *obj, *this); |
579 | } |
580 | |
581 | m_eor.increment(); |
582 | m_sor = m_eor; |
583 | } |
584 | |
585 | m_direction = U_OTHER_NEUTRAL; |
586 | m_status.eor = U_OTHER_NEUTRAL; |
587 | } |
588 | |
589 | template<> |
590 | inline bool InlineBidiResolver::needsContinuePastEndInternal() const |
591 | { |
592 | // We don't collect runs beyond the endOfLine renderer. Stop traversing when the iterator moves to the next renderer to prevent O(n^2). |
593 | return m_current.renderer() == endOfLine.renderer(); |
594 | } |
595 | |
596 | } // namespace WebCore |
597 | |